Source code for optiwindnet.db.migrate

# SPDX-License-Identifier: MIT
# https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/
"""Migrate a v2 (Pony ORM) database to v3 (Peewee).

Uses sqlite3 to read the v2 file (no Pony dependency), writes v3 with Peewee.

Usage:
    python -m optiwindnet.db.migrate input.v2.sqlite output.v3.sqlite
"""

import json
import os
import sqlite3
import sys

from .model import (
    Machine,
    Method,
    NodeSet,
    RouteSet,
    database_proxy,
)
from .storage import open_database


def _get_v2_table_name(v2_conn, candidate_names):
    """Find the actual table name in the v2 database from candidates."""
    cursor = v2_conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
    existing = {row[0] for row in cursor}
    for name in candidate_names:
        if name in existing:
            return name
    raise ValueError(
        f'None of {candidate_names} found in v2 database. Existing tables: {existing}'
    )


[docs] def migrate(v2_path, v3_path): """Migrate a v2 Pony ORM database to v3 Peewee format. Args: v2_path: Path to existing v2 database file. v3_path: Path for the new v3 database file (must not exist). """ v2_path = os.path.abspath(os.path.expanduser(v2_path)) v3_path = os.path.abspath(os.path.expanduser(v3_path)) if not os.path.exists(v2_path): raise OSError(f'v2 database not found: {v2_path}') if os.path.exists(v3_path): raise OSError(f'v3 database already exists: {v3_path}') # Open v2 with raw sqlite3 v2_conn = sqlite3.connect(v2_path) v2_conn.row_factory = sqlite3.Row # Discover table names (Pony uses PascalCase) ns_table = _get_v2_table_name(v2_conn, ['NodeSet', 'nodeset']) method_table = _get_v2_table_name(v2_conn, ['Method', 'method']) machine_table = _get_v2_table_name(v2_conn, ['Machine', 'machine']) rs_table = _get_v2_table_name(v2_conn, ['RouteSet', 'routeset']) # Create v3 database v3_db = open_database(v3_path, create_db=True) with database_proxy.atomic(): # --- Migrate NodeSet --- v2_rows = v2_conn.execute(f'SELECT * FROM "{ns_table}"').fetchall() for row in v2_rows: row_dict = dict(row) # IntArray columns are stored as JSON text in Pony for col in ('constraint_groups', 'constraint_vertices'): val = row_dict[col] if isinstance(val, str): row_dict[col] = json.loads(val) NodeSet.create(**row_dict) ns_count = len(v2_rows) print(f' NodeSet: {ns_count} rows migrated') # --- Migrate Method --- v2_rows = v2_conn.execute(f'SELECT * FROM "{method_table}"').fetchall() for row in v2_rows: row_dict = dict(row) # options is stored as JSON text val = row_dict.get('options') if isinstance(val, str): row_dict['options'] = json.loads(val) Method.create(**row_dict) method_count = len(v2_rows) print(f' Method: {method_count} rows migrated') # --- Migrate Machine --- v2_rows = v2_conn.execute(f'SELECT * FROM "{machine_table}"').fetchall() for row in v2_rows: row_dict = dict(row) val = row_dict.get('attrs') if isinstance(val, str): row_dict['attrs'] = json.loads(val) Machine.create(**row_dict) machine_count = len(v2_rows) print(f' Machine: {machine_count} rows migrated') # --- Migrate RouteSet --- v2_rows = v2_conn.execute(f'SELECT * FROM "{rs_table}"').fetchall() # JSON columns in RouteSet json_cols = { 'num_gates', 'edges', 'tentative', 'rogue', 'clone2prime', 'misc', } for row in v2_rows: row_dict = dict(row) # Parse JSON text columns for col in json_cols: if col in row_dict: val = row_dict[col] if isinstance(val, str): row_dict[col] = json.loads(val) # Ensure misc is never NULL if row_dict.get('misc') is None: row_dict['misc'] = {} RouteSet.create(**row_dict) rs_count = len(v2_rows) print(f' RouteSet: {rs_count} rows migrated') v2_conn.close() # Verify counts assert NodeSet.select().count() == ns_count assert Method.select().count() == method_count assert Machine.select().count() == machine_count assert RouteSet.select().count() == rs_count print(f'\nMigration complete: {v3_path}') print( f' NodeSet: {ns_count}, Method: {method_count}, ' f'Machine: {machine_count}, RouteSet: {rs_count}' ) v3_db.close()
[docs] def main(): if len(sys.argv) != 3: print('Usage: python -m optiwindnet.db.migrate <v2.sqlite> <v3.sqlite>') sys.exit(1) migrate(sys.argv[1], sys.argv[2])
if __name__ == '__main__': main()