const fs = require('fs'); // ===== LOAD DATA ===== const rohsTables = JSON.parse(fs.readFileSync('d:/Workspaces/slaj/_rohs_tables.json', 'utf8')); const dbOverlap = JSON.parse(fs.readFileSync('d:/Workspaces/slaj/_db_overlap_cols.json', 'utf8')); const dbAllNames = JSON.parse(fs.readFileSync('d:/Workspaces/slaj/_slaj_table_names.json', 'utf8')); const sqlContent = fs.readFileSync('d:/Workspaces/slaj/docs/slaj-rohs_tbl-all_without-data_hg.sql', 'utf8'); // ===== CONFIG ===== const TARGET_SCHEMA = 'slaj'; const SOURCE_SCHEMA = 'SLAJ_ROHS'; // ===== HELPERS ===== const isSys = t => /^SYS_/i.test(t); const skipPat = /^(MLOG\$_|MV_|T_VWMS_)|_(BAK\d*$|MW\d*$|MW$|0731$|1125$|BAK25$|0326$)|_20250[47]\d{2}$|BAK2025|_2025011[03]$|_20250110$|_20250113$/; const isBak = t => skipPat.test(t); const rohsReal = Object.keys(rohsTables).filter(t => !isBak(t) && !isSys(t)); const dbLowerMap = {}; dbAllNames.forEach(t => { dbLowerMap[t.toLowerCase()] = t; }); const newTables = rohsReal.filter(t => !dbLowerMap[t.toLowerCase()]); const overlapTables = rohsReal.filter(t => dbLowerMap[t.toLowerCase()]); function normType(t) { t = t.toUpperCase().trim().replace(/\s+/g,' ').replace(/\(\d+(?:,\d+)?\)/g,''); const m = {BPCHAR:'CHAR',VARCHAR:'VARCHAR',NUMERIC:'NUMERIC',DECIMAL:'NUMERIC',INT4:'INTEGER',INT8:'BIGINT',BOOL:'BOOLEAN',FLOAT8:'DOUBLE PRECISION',TIMESTAMPTZ:'TIMESTAMP',FLOAT4:'REAL',INT2:'SMALLINT'}; return m[t] || t; } // ===== PARSE ALL COMMENTS FROM ORIGINAL SQL ===== function parseComments() { const tableComments = {}; // TABLE_NAME -> comment const columnComments = {}; // TABLE_NAME -> {COL_NAME: comment} // Match COMMENT ON TABLE const tRe = /COMMENT ON TABLE "SLAJ_ROHS"\."(\w+)" IS '([^']*)';/g; let m; while ((m = tRe.exec(sqlContent)) !== null) { tableComments[m[1]] = m[2]; } // Match COMMENT ON COLUMN const cRe = /COMMENT ON COLUMN "SLAJ_ROHS"\."(\w+)"\."(\w+)" IS '([^']*)';/g; while ((m = cRe.exec(sqlContent)) !== null) { const tname = m[1], col = m[2], comment = m[3]; if (!columnComments[tname]) columnComments[tname] = {}; columnComments[tname][col] = comment; } return { tableComments, columnComments }; } const { tableComments, columnComments } = parseComments(); function getTableComment(tname) { return tableComments[tname] || ''; } function getColumnComments(tname, columns) { const colComments = columnComments[tname] || {}; const result = {}; for (const col of columns) { const comment = colComments[col] || ''; if (comment) result[col] = comment; } return result; } // ===== EXTRACT ORIGINAL DDL ===== function extractDDL(tableName) { const escaped = tableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const re = new RegExp(`CREATE TABLE "${SOURCE_SCHEMA}"\\."${escaped}"[\\s\\S]*?;`, 'g'); const m = sqlContent.match(re); return m ? m[0] : null; } // ===== GENERATE NEW TABLE DDL ===== let newTableDDL = `-- ============================================================ -- 六项机制(ROHS) 新表 DDL — 目标 schema: ${TARGET_SCHEMA} -- 生成时间: 2026-07-10 -- 共 ${newTables.length} 张表 -- 本次迁移将 ROHS 独有业务表建到安监 slaj schema 中, -- 供两个系统共享使用。 -- -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql -- 执行前请先备份: pg_dump -h 39.98.38.2 -p 5866 -U highgo -d highgo -n slaj --schema-only > slaj_backup.sql -- ============================================================ `; let rollbackNewTables = `-- ============================================================ -- 回滚脚本: 删除新建的 ${newTables.length} 张表 -- 警告: CASCADE 会同时删除依赖这些表的视图、函数等 -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql -- ============================================================ `; for (const tname of newTables.sort()) { const ddl = extractDDL(tname); const lowerName = tname.toLowerCase(); const cols = rohsTables[tname] || []; const tableComment = getTableComment(tname); const colComments = getColumnComments(tname, cols.map(c => c.name)); newTableDDL += `-- ============================================================\n`; newTableDDL += `-- 原表: ${SOURCE_SCHEMA}.${tname} → 新表: ${TARGET_SCHEMA}.${lowerName}\n`; if (tableComment) newTableDDL += `-- 表说明: ${tableComment}\n`; newTableDDL += `-- 列数: ${cols.length}\n`; newTableDDL += `-- ============================================================\n\n`; // Build CREATE TABLE if (ddl) { let transformed = ddl; transformed = transformed.replace(new RegExp(`"${SOURCE_SCHEMA}"`, 'g'), `${TARGET_SCHEMA}`); transformed = transformed.replace(new RegExp(`"${tname}"`, 'g'), lowerName); // Lowercase column names for (const col of cols) { if (col.name !== col.name.toLowerCase()) { // Match quoted column name at line start (after whitespace) const re = new RegExp(`(\\s+)"${col.name}"(\\s+)`, 'g'); transformed = transformed.replace(re, `$1"${col.name.toLowerCase()}"$2`); } } newTableDDL += transformed + '\n\n'; } else { newTableDDL += `CREATE TABLE ${TARGET_SCHEMA}.${lowerName} (\n`; const colDefs = cols.map(c => ` "${c.name.toLowerCase()}" ${c.type}`).join(',\n'); newTableDDL += colDefs + '\n);\n\n'; } // Add COMMENT ON TABLE if (tableComment) { newTableDDL += `COMMENT ON TABLE ${TARGET_SCHEMA}.${lowerName} IS '${tableComment}';\n`; } // Add COMMENT ON COLUMN for (const col of cols) { const comment = colComments[col.name]; if (comment) { newTableDDL += `COMMENT ON COLUMN ${TARGET_SCHEMA}.${lowerName}."${col.name.toLowerCase()}" IS '${comment}';\n`; } } if (tableComment || Object.keys(colComments).length > 0) newTableDDL += '\n'; rollbackNewTables += `-- ${tname} → ${lowerName}\n`; rollbackNewTables += `DROP TABLE IF EXISTS ${TARGET_SCHEMA}.${lowerName} CASCADE;\n\n`; } fs.writeFileSync('d:/Workspaces/slaj/deliverables/01_new_tables_ddl.sql', newTableDDL); fs.writeFileSync('d:/Workspaces/slaj/deliverables/01_new_tables_rollback.sql', rollbackNewTables); console.log('Generated: 01_new_tables_ddl.sql + rollback (with comments)'); // ===== GENERATE DIFF TABLE ALTER ===== let alterScript = `-- ============================================================ -- 差异表 ALTER 脚本 — 37张重叠表的结构对齐 -- 目标: 使 ${TARGET_SCHEMA} 中的表兼容 ROHS 和安监两个系统的读写 -- -- 改动类型: -- a) DATE → TIMESTAMP 类型升级(ROHS使用TIMESTAMP,安监使用DATE,统一为TIMESTAMP) -- b) 取并集:添加对方独有的列 -- c) 单列类型放宽(如 BPCHAR→VARCHAR) -- d) 列名体系完全不同的3张表:新建 _rohs 后缀版,两套并存 -- e) STR_TO_DATE→TO_DATE:仅代码侧改名,DB侧无需操作(已在注释中标注) -- -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql -- ============================================================ `; let rollbackAlter = `-- ============================================================ -- 回滚脚本: 撤销差异表的 ALTER 操作 -- 注意: 部分操作(如ADD COLUMN后已有数据写入)回滚可能失败 -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql -- ============================================================ `; const diffDetails = []; for (const tname of overlapTables.sort()) { const dbName = dbLowerMap[tname.toLowerCase()]; const rCols = rohsTables[tname] || []; const dCols = dbOverlap[dbName] || []; const rMap = {}; rCols.forEach(c => { rMap[c.name] = c.type; }); const dMap = {}; dCols.forEach(c => { dMap[c.name] = c.type; }); const rOnly = Object.keys(rMap).filter(n => !(n in dMap)); const dOnly = Object.keys(dMap).filter(n => !(n in rMap)); const common = Object.keys(rMap).filter(n => n in dMap); const realTypeDiffs = []; for (const cn of common) { const rt = normType(rMap[cn]); const dt = normType(dMap[cn]); if (rt !== dt) { realTypeDiffs.push({col: cn, rohs: rMap[cn], db: dMap[cn], rohsNorm: rt, dbNorm: dt}); } } const hasRealDiff = rOnly.length > 0 || dOnly.length > 0 || realTypeDiffs.length > 0; if (!hasRealDiff) continue; // Special case: tables with completely different column systems const radicalTables = ['T_BUSI_REGULAR_EVALU', 'T_BUSI_REVIEW_INFO', 'T_DICT_HIDD_ENG']; if (radicalTables.includes(tname)) { const lowerName = tname.toLowerCase() + '_rohs'; const ddl = extractDDL(tname); const tableComment = getTableComment(tname); const colComments = getColumnComments(tname, rCols.map(c => c.name)); alterScript += `\n-- ============================================================\n`; alterScript += `-- 【决策#5 - 两套并存】\n`; if (tableComment) alterScript += `-- 原表说明: ${tableComment}\n`; alterScript += `-- ${tname}: ROHS版与安监版列名体系完全不同,各自独立维护\n`; alterScript += `-- 创建 ROHS 版本为: ${lowerName}\n`; alterScript += `-- 原安监表 ${dbName} 保持不变\n`; alterScript += `-- ============================================================\n\n`; if (ddl) { let transformed = ddl; transformed = transformed.replace(new RegExp(`"${SOURCE_SCHEMA}"`, 'g'), `${TARGET_SCHEMA}`); transformed = transformed.replace(new RegExp(`"${tname}"`, 'g'), lowerName); for (const col of rCols) { if (col.name !== col.name.toLowerCase()) { const re = new RegExp(`"${col.name}"`, 'g'); transformed = transformed.replace(re, `"${col.name.toLowerCase()}"`); } } alterScript += transformed + '\n\n'; } // Comments if (tableComment) { alterScript += `COMMENT ON TABLE ${TARGET_SCHEMA}.${lowerName} IS '${tableComment}(ROHS版本)';\n`; } for (const col of rCols) { const comment = colComments[col.name]; if (comment) { alterScript += `COMMENT ON COLUMN ${TARGET_SCHEMA}.${lowerName}."${col.name.toLowerCase()}" IS '${comment}';\n`; } } alterScript += '\n'; rollbackAlter += `-- ${tname} ROHS版回滚\n`; rollbackAlter += `DROP TABLE IF EXISTS ${TARGET_SCHEMA}.${lowerName} CASCADE;\n\n`; diffDetails.push({ rohs: tname, db: dbName, type: 'radical', action: `两套并存,ROHS版创建为 ${lowerName}`, fields: rCols.map(c => ({rohs: c.name, db: c.name, rohsType: c.type, dbType: '(新表)', action: '新建ROHS版表'})), }); continue; } const lowerName = dbName; const alters = []; const rollbacks = []; const tableComment = getTableComment(tname); const colComments = getColumnComments(tname, rCols.map(c => c.name)); // Build header let headerComment = `\n-- ============================================================\n`; headerComment += `-- 表: ${tname} ↔ ${dbName}\n`; if (tableComment) headerComment += `-- 表说明: ${tableComment}\n`; headerComment += `-- ROHS列数: ${rCols.length} | 安监列数: ${dCols.length}\n`; headerComment += `-- ============================================================\n`; let hasActualAlter = false; // 1. STR_TO_DATE → TO_DATE (ROHS code side only) if (rOnly.includes('STR_TO_DATE') && dOnly.includes('TO_DATE')) { headerComment += `--\n`; headerComment += `-- 【决策#4 - 列名统一】STR_TO_DATE → TO_DATE\n`; headerComment += `-- DB侧: 安监已有 TO_DATE 列,无需操作\n`; headerComment += `-- 代码侧: ROHS代码需将 'STR_TO_DATE' 改为 'TO_DATE'\n`; headerComment += `--\n`; rOnly.splice(rOnly.indexOf('STR_TO_DATE'), 1); } else if (rOnly.includes('STR_TO_DATE') && !dOnly.includes('TO_DATE')) { // STR_TO_DATE exists in ROHS but DB has neither STR_TO_DATE nor TO_DATE // Add it as a new column // (This case is for ATT_PERS_BASE etc.) } // 2. Add ROHS-only columns to DB for (const cn of rOnly) { const colType = rMap[cn]; const lowerCol = cn.toLowerCase(); let pgType = colType; if (pgType === 'BPCHAR' || pgType === 'CHAR(1)') pgType = 'VARCHAR(4)'; else if (pgType.startsWith('CHAR(')) pgType = 'VARCHAR(' + pgType.match(/\d+/)[0] + ')'; else if (pgType.startsWith('DECIMAL')) pgType = pgType.replace('DECIMAL', 'NUMERIC'); const comment = colComments[cn] || ''; const commentSql = comment ? ` -- ${comment}` : ''; alters.push(`-- ROHS独有列: ${cn} (ROHS类型: ${colType})`); alters.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ADD COLUMN "${lowerCol}" ${pgType};${commentSql}`); if (comment) { alters.push(`COMMENT ON COLUMN ${TARGET_SCHEMA}.${lowerName}."${lowerCol}" IS '${comment}';`); } alters.push(''); rollbacks.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} DROP COLUMN IF EXISTS "${lowerCol}";`); hasActualAlter = true; } // 3. Document DB-only columns (already exist, no action needed) if (dOnly.length > 0) { headerComment += `--\n`; headerComment += `-- 【安监独有列 - DB侧已存在,无需操作】\n`; for (const cn of dOnly) { headerComment += `-- "${cn.toLowerCase()}" (${dMap[cn]}) — 安监系统使用,ROHS代码可忽略\n`; } headerComment += `--\n`; } // 4. Type changes for (const td of realTypeDiffs) { const lowerCol = td.col.toLowerCase(); let targetType; let reason = ''; if (td.rohsNorm === 'TIMESTAMP' && td.dbNorm === 'DATE') { targetType = 'TIMESTAMP'; reason = 'ROHS使用TIMESTAMP存储时间,安监使用DATE,统一升级为TIMESTAMP(向下兼容)'; } else if (td.dbNorm === 'TIMESTAMP' && td.rohsNorm === 'DATE') { targetType = 'TIMESTAMP'; reason = '统一为TIMESTAMP'; } else if (td.rohsNorm === 'VARCHAR' && td.dbNorm === 'CHAR') { targetType = 'VARCHAR(100)'; reason = 'CHAR→VARCHAR放宽,兼容两边写入'; } else if (td.rohsNorm === 'NUMERIC' && td.dbNorm === 'VARCHAR') { targetType = 'VARCHAR(100)'; reason = '类型不一致,统一为VARCHAR'; } else { targetType = 'VARCHAR(255)'; reason = `类型不一致: ROHS=${td.rohs} vs DB=${td.db},统一为VARCHAR`; } alters.push(`-- 类型对齐: "${lowerCol}" ROHS=${td.rohs} DB=${td.db}`); alters.push(`-- ${reason}`); alters.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ALTER COLUMN "${lowerCol}" TYPE ${targetType} USING "${lowerCol}"::${targetType};`); alters.push(''); rollbacks.push(`-- 回滚类型变更 ["${lowerCol}"]: 需手动恢复为原类型 ${td.db}`); hasActualAlter = true; } if (hasActualAlter) { alterScript += headerComment; if (headerComment.includes('--\n--')) alterScript += '\n'; alterScript += alters.join('\n') + '\n'; } else { // Only info comments, no actual ALTER alterScript += headerComment; alterScript += `-- [仅代码侧改动,DB侧无操作]\n\n`; } if (rollbacks.length > 0) { rollbackAlter += `\n-- ${tname} 回滚\n`; rollbackAlter += rollbacks.join('\n') + '\n'; } // Build field mapping const allFields = [...new Set([...Object.keys(rMap), ...Object.keys(dMap)])]; const fields = allFields.map(f => { const rType = rMap[f] || '(不存在)'; const dType = dMap[f] || '(不存在)'; let action = ''; if (!(f in dMap)) action = '新增列(ROHS独有)'; else if (!(f in rMap)) action = '安监独有(ROHS代码可忽略)'; else if (normType(rType) !== normType(dType)) action = `类型变更: ${dType} → ${rType}`; else if (f === 'STR_TO_DATE' && 'TO_DATE' in dMap) action = 'ROHS代码侧改为 TO_DATE'; else action = '一致'; return {rohs: f, db: f === 'STR_TO_DATE' && 'TO_DATE' in dMap ? 'TO_DATE' : f, rohsType: rType, dbType: dType, action}; }); diffDetails.push({rohs: tname, db: dbName, type: 'diff', action: hasActualAlter ? `${alters.filter(l => l.startsWith('ALTER')).length}个ALTER` : '仅代码侧改动', fields, tableComment: tableComment || ''}); } fs.writeFileSync('d:/Workspaces/slaj/deliverables/02_diff_tables_alter.sql', alterScript); fs.writeFileSync('d:/Workspaces/slaj/deliverables/02_diff_tables_rollback.sql', rollbackAlter); console.log('Generated: 02_diff_tables_alter.sql + rollback (with comments)'); // ===== GENERATE FIELD MAPPING ===== let mappingMD = `# 字段映射表 — ROHS → 安监(slaj) > 给改 ROHS 代码的团队使用。包含所有列名变更、类型变更的完整对照。 ## 一、新表(${newTables.length}张) 以下表在安监中不存在,本次新建。ROHS 代码侧需: 1. 连接 schema 从 \`SLAJ_ROHS\` 改为 \`slaj\` 2. 表名和列名统一改为小写 | ROHS 表名 | 安监表名 | 列数 | 表说明 | |-----------|---------|------|--------| `; for (const t of newTables.sort()) { const cols = rohsTables[t] || []; const comment = getTableComment(t) || '-'; mappingMD += `| ${t} | \`${t.toLowerCase()}\` | ${cols.length} | ${comment} |\n`; } mappingMD += `\n## 二、重叠表字段映射(${diffDetails.length}张)\n\n`; for (const d of diffDetails) { mappingMD += `### ${d.rohs} → \`${d.db}\`\n\n`; if (d.tableComment) mappingMD += `**表说明**: ${d.tableComment}\n\n`; mappingMD += `**操作**: ${d.action}\n\n`; mappingMD += `| ROHS列名 | 安监列名 | ROHS类型 | 安监类型 | 备注 |\n`; mappingMD += `|----------|---------|----------|---------|------|\n`; for (const f of d.fields) { mappingMD += `| ${f.rohs} | ${f.db} | ${f.rohsType} | ${f.dbType} | ${f.action} |\n`; } mappingMD += '\n'; } // Full match tables const fullMatchTables = overlapTables.filter(t => { const dbName = dbLowerMap[t.toLowerCase()]; const rCols = rohsTables[t] || []; const dCols = dbOverlap[dbName] || []; const rMap = {}; rCols.forEach(c => { rMap[c.name] = c.type; }); const dMap = {}; dCols.forEach(c => { dMap[c.name] = c.type; }); const rOnly = Object.keys(rMap).filter(n => !(n in dMap)); const dOnly = Object.keys(dMap).filter(n => !(n in rMap)); const common = Object.keys(rMap).filter(n => n in dMap); const realTypeDiffs = common.filter(cn => normType(rMap[cn]) !== normType(dMap[cn])); return rOnly.length === 0 && dOnly.length === 0 && realTypeDiffs.length === 0; }); mappingMD += `\n## 三、完全一致的表(${fullMatchTables.length}张,无需任何改动)\n\n`; mappingMD += `| ROHS表名 | 安监表名 | 列数 | 表说明 |\n`; mappingMD += `|----------|---------|------|--------|\n`; for (const t of fullMatchTables.sort()) { const dbName = dbLowerMap[t.toLowerCase()]; const cols = rohsTables[t] || []; const comment = getTableComment(t) || '-'; mappingMD += `| ${t} | \`${dbName}\` | ${cols.length} | ${comment} |\n`; } mappingMD += `\n## 四、ROHS 代码改动清单\n\n`; mappingMD += `### 必须改动\n\n`; mappingMD += `1. **Schema 改名**: \`SLAJ_ROHS\` → \`slaj\`(数据库连接配置/jdbc url)\n`; mappingMD += `2. **表名小写**: 所有表名从大写改为小写(如 \`ATT_ENG_BASE\` → \`att_eng_base\`)\n`; mappingMD += `3. **列名小写**: 所有列名从大写改为小写\n`; mappingMD += `4. **STR_TO_DATE → TO_DATE**: 涉及以下表,ROHS代码中所有 \`STR_TO_DATE\` 改为 \`TO_DATE\`\n`; const strToDateTables = diffDetails.filter(d => d.fields.some(f => f.action.includes('TO_DATE'))); for (const d of strToDateTables) { mappingMD += ` - ${d.rohs} → \`${d.db}\`\n`; } mappingMD += `5. **3张独立表改名**:\n`; mappingMD += ` - \`T_BUSI_REGULAR_EVALU\` → \`t_busi_regular_evalu_rohs\`\n`; mappingMD += ` - \`T_BUSI_REVIEW_INFO\` → \`t_busi_review_info_rohs\`\n`; mappingMD += ` - \`T_DICT_HIDD_ENG\` → \`t_dict_hidd_eng_rohs\`\n`; mappingMD += `\n### 建议检查\n\n`; mappingMD += `1. TIMESTAMP 类型字段:如果代码中将数据库字段映射为 LocalDate,需改为 LocalDateTime\n`; mappingMD += `2. 新增列(取并集):如果代码中有 \`SELECT *\` 查询,确认是否受影响\n`; mappingMD += `3. 安监独有列:ROHS代码可选择忽略这些列,写入时留 NULL 即可\n`; fs.writeFileSync('d:/Workspaces/slaj/deliverables/03_field_mapping.md', mappingMD); console.log('Generated: 03_field_mapping.md'); // ===== README ===== let guide = `# 合并执行指南 ## 前置条件 - [ ] 数据库备份已完成 - [ ] 安监系统已通知停机窗口(建议低峰期执行) - [ ] ROHS 代码改动团队已就位 ## 执行顺序 ### 第一步: 备份(必须) \`\`\`bash pg_dump -h 39.98.38.2 -p 5866 -U highgo -d highgo -n slaj --schema-only > slaj_schema_backup_$(date +%Y%m%d_%H%M%S).sql \`\`\` ### 第二步: 执行新表DDL(无风险,${newTables.length}张) \`\`\`bash psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/01_new_tables_ddl.sql \`\`\` 验证: \`SELECT count(*) FROM information_schema.tables WHERE table_schema='slaj' AND table_name IN (SELECT lower_name FROM new_tables);\` 回滚: \`psql ... -f deliverables/01_new_tables_rollback.sql\` ### 第三步: 执行差异表ALTER(${diffDetails.length}张) \`\`\`bash psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/02_diff_tables_alter.sql \`\`\` 回滚: \`psql ... -f deliverables/02_diff_tables_rollback.sql\` ### 第四步: ROHS 代码改动 参照 \`deliverables/03_field_mapping.md\`,另一团队修改 ROHS 代码。 ## 交付物 | 文件 | 行数 | 说明 | |------|------|------| | \`01_new_tables_ddl.sql\` | - | ${newTables.length}张新表建表 + 注释 | | \`01_new_tables_rollback.sql\` | - | DROP TABLE 回滚 | | \`02_diff_tables_alter.sql\` | - | ${diffDetails.length}张表结构对齐 ALTER | | \`02_diff_tables_rollback.sql\` | - | ALTER 回滚 | | \`03_field_mapping.md\` | - | 字段映射(给改代码团队) | `; fs.writeFileSync('d:/Workspaces/slaj/deliverables/README.md', guide); console.log('Generated: README.md'); console.log('\n=== GENERATION COMPLETE ==='); console.log(`New tables: ${newTables.length}`); console.log(`Diff tables: ${diffDetails.length} (${diffDetails.filter(d => d.type === 'radical').length} radical, ${diffDetails.filter(d => d.type === 'diff').length} normal)`); console.log(`Full match: ${fullMatchTables.length}`);