uapi/scripts/migrate_uapi_upappid.py

130 lines
4.6 KiB
Python

"""
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.jsonConfig 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.",
"",
"-- 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())