134 lines
4.7 KiB
Python
134 lines
4.7 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
|
|
try:
|
|
async with db.sqlorContext(dbname) as sor:
|
|
# Get all upapps
|
|
upapps = await sor.sqlExe('select id, name, apisetid from upapp', {})
|
|
# Get all uapis (removed non-existent auth_apiname field)
|
|
uapis = await sor.sqlExe('select id, apisetid, name, httpmethod, path, headers, ioid, response, params, data, chunk_match from uapi', {})
|
|
except Exception as e:
|
|
print(f"Error querying database: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# 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())
|