""" Migration: uapi table apisetid -> upappid Removes uapiset intermediate layer. Each upapp now owns its own uapi records. For shared apisetid: copies uapi records so each upapp has its own copy. Usage (in Sage virtual env): ./py3/bin/python3 ~/repos/uapi/scripts/migrate_uapi_upappid.py --output /tmp/migrate_uapi.sql Review the output SQL, then execute on your database. """ from appPublic.getConfig import getConfig import asyncio, json, sys, argparse from sqlor.dbpools import DBPools from appPublic.uniqueID import getID config = getConfig('.') db = DBPools(config.databases) dbname = list(config.databases.keys())[0] async def generate_migration_sql(): """Generate SQL to migrate uapi.apisetid -> uapi.upappid.""" lines = [ "-- Migration: uapi table apisetid -> upappid", "-- Removes uapiset intermediate layer.", "-- Each upapp owns its own uapi records after migration.", "", "-- Step 1: Add upappid column to uapi", "ALTER TABLE uapi ADD COLUMN upappid VARCHAR(21) DEFAULT NULL COMMENT '上位系统ID' AFTER id;", "" ] # Load data async with db.sqlorContext(dbname) as sor: # Get all upapps upapps = await sor.sqlExe('select id, name, apisetid from upapp', {}) # Get all uapis uapis = await sor.sqlExe('select id, apisetid, name, httpmethod, path, headers, ioid, auth_apiname, response, params, data, chunk_match from uapi', {}) # Build mapping: apisetid -> [upapp1, upapp2, ...] apiset_to_upapps = {} for u in upapps: aid = u.get('apisetid') if aid: apiset_to_upapps.setdefault(aid, []).append(u) # Build mapping: apisetid -> [uapi_records] apiset_to_uapis = {} for a in uapis: aid = a.get('apisetid') if aid: apiset_to_uapis.setdefault(aid, []).append(a) inserts = [] for apisetid, upapp_list in apiset_to_upapps.items(): uapi_records = apiset_to_uapis.get(apisetid, []) if len(upapp_list) == 1: # Single upapp owns this apisetid -> just update upapp = upapp_list[0] for uapi in uapi_records: inserts.append( f"UPDATE uapi SET upappid = '{upapp['id']}' WHERE id = '{uapi['id']}';" ) else: # Multiple upapps share this apisetid -> pick first as owner, copy for rest owner = upapp_list[0] # Update existing records to point to owner for uapi in uapi_records: inserts.append( f"UPDATE uapi SET upappid = '{owner['id']}' WHERE id = '{uapi['id']}';" ) # Copy for other upapps for other_upapp in upapp_list[1:]: for uapi in uapi_records: new_id = getID() old_id = uapi['id'] fields = ['id', 'upappid', 'name', 'httpmethod', 'path', 'headers', 'ioid', 'response', 'params', 'data', 'chunk_match'] vals = [f"'{new_id}'", f"'{other_upapp['id']}'"] for f in fields[2:]: v = uapi.get(f) if v is None: vals.append('NULL') elif isinstance(v, str): escaped = v.replace("'", "\\'") vals.append(f"'{escaped}'") else: vals.append(str(v)) col_str = ', '.join(fields) val_str = ', '.join(vals) inserts.append(f"INSERT INTO uapi ({col_str}) VALUES ({val_str});") if inserts: lines.append("-- Step 2: Migrate data (updates + copies for shared apisetid)") lines.extend(inserts) lines.append("") lines.extend([ "-- Step 3: Drop apisetid column (verify upappid has no NULLs first)", "-- UPDATE uapi SET upappid = (SELECT ownerid FROM organization LIMIT 1) WHERE upappid IS NULL;", "-- ALTER TABLE uapi DROP COLUMN apisetid;", "", "-- Step 4: Add index", "CREATE INDEX idx_uapi_upappid ON uapi (upappid);", "" ]) return '\n'.join(lines) async def main(): parser = argparse.ArgumentParser() parser.add_argument('--output', '-o', default='-') args = parser.parse_args() sql = await generate_migration_sql() if args.output == '-': print(sql) else: with open(args.output, 'w', encoding='utf-8') as f: f.write(sql) print(f"Generated migration SQL -> {args.output}") if __name__ == '__main__': asyncio.run(main())