提交
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import fs from 'node:fs';
|
||||
import {spawnSync} from 'node:child_process';
|
||||
const dir = new URL('./', import.meta.url);
|
||||
const schema=process.argv.includes('--firstleg')?'baiyue_firstleg':'baiyue_lastleg';
|
||||
const prefix=schema==='baiyue_firstleg'?'firstleg-':'';
|
||||
function definitions(body) {
|
||||
let depth=0,quote='',start=0;const parts=[];
|
||||
for(let i=0;i<body.length;i++) {const c=body[i];
|
||||
if(quote){if(c==='\\'){i++;continue;}if(c===quote){if(body[i+1]===quote)i++;else quote='';}continue;}
|
||||
if(c==="'"||c==='"'||c==='`'){quote=c;continue;}
|
||||
if(c==='(')depth++;else if(c===')')depth--;else if(c===','&&depth===0){parts.push(body.slice(start,i).trim());start=i+1;}
|
||||
}
|
||||
parts.push(body.slice(start).trim());return parts;
|
||||
}
|
||||
function mysql(sql) {
|
||||
const r=spawnSync('D:/baiyue-runtime/mysql/server/bin/mysql.exe', ['--host=127.0.0.1','--user=root','--database='+schema,'--batch','--skip-column-names','--default-character-set=utf8mb4'], {input:sql,encoding:'utf8',env:process.env,maxBuffer:20*1024*1024});
|
||||
if(r.status!==0) throw new Error(r.stderr); return r.stdout.trim();
|
||||
}
|
||||
const source=fs.readFileSync('deploy/sql/'+(prefix?'头程数据库统一增量-MySQL8.sql':'海外区域统一增量-MySQL8.sql'),'utf8');
|
||||
let db='';const tables=[];
|
||||
for(const m of source.matchAll(/USE (\w+);|CREATE TABLE\s+(?:IF NOT EXISTS\s+)?(\w+)\s*\(([\s\S]*?)\n\) ENGINE=[^;]+;/g)) {if(m[1])db=m[1];else if(db===schema)tables.push([m[0],m[2],m[3]]);}
|
||||
if(tables.length<(prefix?100:170)) throw new Error('基线解析不完整');
|
||||
const actual=mysql("SELECT table_name,column_name FROM information_schema.columns WHERE table_schema=DATABASE() ORDER BY table_name,ordinal_position;");
|
||||
fs.writeFileSync(new URL(prefix+'columns-before.tsv',dir),actual);
|
||||
const current=new Map();
|
||||
for(const line of actual.split(/\r?\n/)) {const [t,c]=line.split('\t'); if(!current.has(t))current.set(t,new Set()); current.get(t).add(c);}
|
||||
const plan=[], addedTables=[], addedColumns=[], unsafe=[], counts=new Map();
|
||||
for(const m of tables) {
|
||||
const [,table,body]=m;
|
||||
if(!current.has(table)){plan.push(m[0]);addedTables.push(table);continue;}
|
||||
for(const line of definitions(body)) {
|
||||
const field=line.match(/^`?([a-z][a-z0-9_]*)`?\s+([\s\S]+)$/i);
|
||||
if(!field || !/^(?:bigint|int|integer|tinyint|smallint|mediumint|varchar|char|text|longtext|mediumtext|datetime|timestamp|date|time|decimal|double|float|json|boolean|bool|binary|varbinary|blob|enum|bit)\b/i.test(field[2]) || current.get(table).has(field[1]))continue;
|
||||
const definition=line.trim().replace(/,$/,'');
|
||||
if(!counts.has(table))counts.set(table,Number(mysql('SELECT COUNT(*) FROM `'+table+'`;')));
|
||||
const count=counts.get(table);
|
||||
let appliedDefinition=definition;
|
||||
if(count>0 && /NOT NULL/i.test(definition) && !/DEFAULT|AUTO_INCREMENT/i.test(definition)) {
|
||||
unsafe.push({table,column:field[1],count,definition,handling:'历史行保留NULL,不伪造业务事实;新写入由应用校验'});
|
||||
appliedDefinition=definition.replace(/NOT NULL/i,'NULL');
|
||||
}
|
||||
plan.push('ALTER TABLE `'+table+'` ADD COLUMN '+appliedDefinition+';');addedColumns.push(table+'.'+field[1]);
|
||||
}
|
||||
}
|
||||
const report={baselineTables:tables.length,actualTables:current.size,addedTables,addedColumns,unsafe};
|
||||
fs.writeFileSync(new URL(prefix+'plan.json',dir),JSON.stringify(report,null,2));
|
||||
fs.writeFileSync(new URL(prefix+'incremental.sql',dir),'-- 本地服务结构补齐;仅创建缺失表、添加缺失字段,不删除现有数据。\nSET NAMES utf8mb4;\n'+plan.join('\n\n')+'\n');
|
||||
console.log(JSON.stringify(report,null,2));
|
||||
if(process.argv.includes('--apply')) {
|
||||
mysql('SET SESSION lock_wait_timeout=15;\n'+plan.join('\n'));fs.appendFileSync(new URL(prefix+'applied.log',dir),`${new Date().toISOString()} ${plan.length} DDL applied\n`);
|
||||
console.log('APPLIED '+plan.length);
|
||||
}
|
||||
Reference in New Issue
Block a user