chore: snapshot production code before Apple Design UI refinement
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user