"""Consistent logical backup of the complete current MySQL database; no DB writes.""" import gzip, hashlib, json, os, pathlib, time import pymysql from db_access import connect os.umask(0o077) root=pathlib.Path('/opt/lingniu-go-native/backups/mileage-reconciliation-20260916') root.mkdir(parents=True,exist_ok=True) path=root/'database.sql.gz' if path.exists(): raise RuntimeError('Backup already exists; do not overwrite') c=connect(); manifest={'started_at':time.strftime('%Y-%m-%dT%H:%M:%S%z'),'tables':{}} with c.cursor() as q: q.execute('SELECT DATABASE() AS db'); db=q.fetchone()['db'];manifest['database']=db q.execute('SHOW FULL TABLES'); entries=q.fetchall() tables=[list(r.values())[0] for r in entries if list(r.values())[1]=='BASE TABLE'] q.execute('SHOW TRIGGERS'); triggers=q.fetchall() q.execute('START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY') def literal(v): if isinstance(v,bytes): return '0x'+v.hex() return c.escape(v) with gzip.open(str(path)+'.partial','wt',encoding='utf-8',compresslevel=3) as f: f.write('SET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS=0;\n') for table in tables: q.execute('SHOW CREATE TABLE `'+table+'`');ddl=list(q.fetchone().values())[1] f.write('DROP TABLE IF EXISTS `'+table+'`;\n'+ddl+';\n') q.execute('SHOW COLUMNS FROM `'+table+'`');cols=[r['Field'] for r in q.fetchall() if 'GENERATED' not in r['Extra']] names=','.join('`'+v+'`' for v in cols);count=0 with c.cursor(pymysql.cursors.SSCursor) as stream: stream.execute('SELECT '+names+' FROM `'+table+'`') while True: rows=stream.fetchmany(500) if not rows:break f.write('INSERT INTO `'+table+'` ('+names+') VALUES\n'+',\n'.join('('+','.join(literal(v) for v in row)+')' for row in rows)+';\n') count+=len(rows) manifest['tables'][table]=count print(json.dumps({'table':table,'rows':count}),flush=True) for row in entries: if list(row.values())[1]=='VIEW': name=list(row.values())[0];q.execute('SHOW CREATE VIEW `'+name+'`');ddl=q.fetchone()['Create View'];f.write(ddl+';\n') for trigger in triggers: q.execute('SHOW CREATE TRIGGER `'+trigger['Trigger']+'`');ddl=q.fetchone()['SQL Original Statement'];f.write('DELIMITER ;;\n'+ddl+';;\nDELIMITER ;\n') f.write('SET FOREIGN_KEY_CHECKS=1;\n') c.rollback();c.close() os.rename(str(path)+'.partial',path) h=hashlib.sha256() with path.open('rb') as f: for block in iter(lambda:f.read(1024*1024),b''):h.update(block) manifest.update(sha256=h.hexdigest(),bytes=path.stat().st_size,finished_at=time.strftime('%Y-%m-%dT%H:%M:%S%z')) (root/'manifest.json').write_text(json.dumps(manifest,indent=2)) print(json.dumps({'complete':True,'bytes':manifest['bytes'],'sha256':manifest['sha256']}),flush=True)