_gen_v2.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. const fs = require('fs');
  2. // ===== LOAD DATA =====
  3. const rohsTables = JSON.parse(fs.readFileSync('d:/Workspaces/slaj/_rohs_tables.json', 'utf8'));
  4. const dbOverlap = JSON.parse(fs.readFileSync('d:/Workspaces/slaj/_db_overlap_cols.json', 'utf8'));
  5. const dbAllNames = JSON.parse(fs.readFileSync('d:/Workspaces/slaj/_slaj_table_names.json', 'utf8'));
  6. const sqlContent = fs.readFileSync('d:/Workspaces/slaj/docs/slaj-rohs_tbl-all_without-data_hg.sql', 'utf8');
  7. // ===== CONFIG =====
  8. const TARGET_SCHEMA = 'slaj';
  9. const SOURCE_SCHEMA = 'SLAJ_ROHS';
  10. // ===== HELPERS =====
  11. const isSys = t => /^SYS_/i.test(t);
  12. const skipPat = /^(MLOG\$_|MV_|T_VWMS_)|_(BAK\d*$|MW\d*$|MW$|0731$|1125$|BAK25$|0326$)|_20250[47]\d{2}$|BAK2025|_2025011[03]$|_20250110$|_20250113$/;
  13. const isBak = t => skipPat.test(t);
  14. const rohsReal = Object.keys(rohsTables).filter(t => !isBak(t) && !isSys(t));
  15. const dbLowerMap = {};
  16. dbAllNames.forEach(t => { dbLowerMap[t.toLowerCase()] = t; });
  17. const newTables = rohsReal.filter(t => !dbLowerMap[t.toLowerCase()]);
  18. const overlapTables = rohsReal.filter(t => dbLowerMap[t.toLowerCase()]);
  19. function normType(t) {
  20. t = t.toUpperCase().trim().replace(/\s+/g,' ').replace(/\(\d+(?:,\d+)?\)/g,'');
  21. 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'};
  22. return m[t] || t;
  23. }
  24. // ===== PARSE ALL COMMENTS FROM ORIGINAL SQL =====
  25. function parseComments() {
  26. const tableComments = {}; // TABLE_NAME -> comment
  27. const columnComments = {}; // TABLE_NAME -> {COL_NAME: comment}
  28. // Match COMMENT ON TABLE
  29. const tRe = /COMMENT ON TABLE "SLAJ_ROHS"\."(\w+)" IS '([^']*)';/g;
  30. let m;
  31. while ((m = tRe.exec(sqlContent)) !== null) {
  32. tableComments[m[1]] = m[2];
  33. }
  34. // Match COMMENT ON COLUMN
  35. const cRe = /COMMENT ON COLUMN "SLAJ_ROHS"\."(\w+)"\."(\w+)" IS '([^']*)';/g;
  36. while ((m = cRe.exec(sqlContent)) !== null) {
  37. const tname = m[1], col = m[2], comment = m[3];
  38. if (!columnComments[tname]) columnComments[tname] = {};
  39. columnComments[tname][col] = comment;
  40. }
  41. return { tableComments, columnComments };
  42. }
  43. const { tableComments, columnComments } = parseComments();
  44. function getTableComment(tname) {
  45. return tableComments[tname] || '';
  46. }
  47. function getColumnComments(tname, columns) {
  48. const colComments = columnComments[tname] || {};
  49. const result = {};
  50. for (const col of columns) {
  51. const comment = colComments[col] || '';
  52. if (comment) result[col] = comment;
  53. }
  54. return result;
  55. }
  56. // ===== EXTRACT ORIGINAL DDL =====
  57. function extractDDL(tableName) {
  58. const escaped = tableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  59. const re = new RegExp(`CREATE TABLE "${SOURCE_SCHEMA}"\\."${escaped}"[\\s\\S]*?;`, 'g');
  60. const m = sqlContent.match(re);
  61. return m ? m[0] : null;
  62. }
  63. // ===== GENERATE NEW TABLE DDL =====
  64. let newTableDDL = `-- ============================================================
  65. -- 六项机制(ROHS) 新表 DDL — 目标 schema: ${TARGET_SCHEMA}
  66. -- 生成时间: 2026-07-10
  67. -- 共 ${newTables.length} 张表
  68. -- 本次迁移将 ROHS 独有业务表建到安监 slaj schema 中,
  69. -- 供两个系统共享使用。
  70. --
  71. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  72. -- 执行前请先备份: pg_dump -h 39.98.38.2 -p 5866 -U highgo -d highgo -n slaj --schema-only > slaj_backup.sql
  73. -- ============================================================
  74. `;
  75. let rollbackNewTables = `-- ============================================================
  76. -- 回滚脚本: 删除新建的 ${newTables.length} 张表
  77. -- 警告: CASCADE 会同时删除依赖这些表的视图、函数等
  78. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  79. -- ============================================================
  80. `;
  81. for (const tname of newTables.sort()) {
  82. const ddl = extractDDL(tname);
  83. const lowerName = tname.toLowerCase();
  84. const cols = rohsTables[tname] || [];
  85. const tableComment = getTableComment(tname);
  86. const colComments = getColumnComments(tname, cols.map(c => c.name));
  87. newTableDDL += `-- ============================================================\n`;
  88. newTableDDL += `-- 原表: ${SOURCE_SCHEMA}.${tname} → 新表: ${TARGET_SCHEMA}.${lowerName}\n`;
  89. if (tableComment) newTableDDL += `-- 表说明: ${tableComment}\n`;
  90. newTableDDL += `-- 列数: ${cols.length}\n`;
  91. newTableDDL += `-- ============================================================\n\n`;
  92. // Build CREATE TABLE
  93. if (ddl) {
  94. let transformed = ddl;
  95. transformed = transformed.replace(new RegExp(`"${SOURCE_SCHEMA}"`, 'g'), `${TARGET_SCHEMA}`);
  96. transformed = transformed.replace(new RegExp(`"${tname}"`, 'g'), lowerName);
  97. // Lowercase column names
  98. for (const col of cols) {
  99. if (col.name !== col.name.toLowerCase()) {
  100. // Match quoted column name at line start (after whitespace)
  101. const re = new RegExp(`(\\s+)"${col.name}"(\\s+)`, 'g');
  102. transformed = transformed.replace(re, `$1"${col.name.toLowerCase()}"$2`);
  103. }
  104. }
  105. newTableDDL += transformed + '\n\n';
  106. } else {
  107. newTableDDL += `CREATE TABLE ${TARGET_SCHEMA}.${lowerName} (\n`;
  108. const colDefs = cols.map(c => ` "${c.name.toLowerCase()}" ${c.type}`).join(',\n');
  109. newTableDDL += colDefs + '\n);\n\n';
  110. }
  111. // Add COMMENT ON TABLE
  112. if (tableComment) {
  113. newTableDDL += `COMMENT ON TABLE ${TARGET_SCHEMA}.${lowerName} IS '${tableComment}';\n`;
  114. }
  115. // Add COMMENT ON COLUMN
  116. for (const col of cols) {
  117. const comment = colComments[col.name];
  118. if (comment) {
  119. newTableDDL += `COMMENT ON COLUMN ${TARGET_SCHEMA}.${lowerName}."${col.name.toLowerCase()}" IS '${comment}';\n`;
  120. }
  121. }
  122. if (tableComment || Object.keys(colComments).length > 0) newTableDDL += '\n';
  123. rollbackNewTables += `-- ${tname} → ${lowerName}\n`;
  124. rollbackNewTables += `DROP TABLE IF EXISTS ${TARGET_SCHEMA}.${lowerName} CASCADE;\n\n`;
  125. }
  126. fs.writeFileSync('d:/Workspaces/slaj/deliverables/01_new_tables_ddl.sql', newTableDDL);
  127. fs.writeFileSync('d:/Workspaces/slaj/deliverables/01_new_tables_rollback.sql', rollbackNewTables);
  128. console.log('Generated: 01_new_tables_ddl.sql + rollback (with comments)');
  129. // ===== GENERATE DIFF TABLE ALTER =====
  130. let alterScript = `-- ============================================================
  131. -- 差异表 ALTER 脚本 — 37张重叠表的结构对齐
  132. -- 目标: 使 ${TARGET_SCHEMA} 中的表兼容 ROHS 和安监两个系统的读写
  133. --
  134. -- 改动类型:
  135. -- a) DATE → TIMESTAMP 类型升级(ROHS使用TIMESTAMP,安监使用DATE,统一为TIMESTAMP)
  136. -- b) 取并集:添加对方独有的列
  137. -- c) 单列类型放宽(如 BPCHAR→VARCHAR)
  138. -- d) 列名体系完全不同的3张表:新建 _rohs 后缀版,两套并存
  139. -- e) STR_TO_DATE→TO_DATE:仅代码侧改名,DB侧无需操作(已在注释中标注)
  140. --
  141. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  142. -- ============================================================
  143. `;
  144. let rollbackAlter = `-- ============================================================
  145. -- 回滚脚本: 撤销差异表的 ALTER 操作
  146. -- 注意: 部分操作(如ADD COLUMN后已有数据写入)回滚可能失败
  147. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  148. -- ============================================================
  149. `;
  150. const diffDetails = [];
  151. for (const tname of overlapTables.sort()) {
  152. const dbName = dbLowerMap[tname.toLowerCase()];
  153. const rCols = rohsTables[tname] || [];
  154. const dCols = dbOverlap[dbName] || [];
  155. const rMap = {}; rCols.forEach(c => { rMap[c.name] = c.type; });
  156. const dMap = {}; dCols.forEach(c => { dMap[c.name] = c.type; });
  157. const rOnly = Object.keys(rMap).filter(n => !(n in dMap));
  158. const dOnly = Object.keys(dMap).filter(n => !(n in rMap));
  159. const common = Object.keys(rMap).filter(n => n in dMap);
  160. const realTypeDiffs = [];
  161. for (const cn of common) {
  162. const rt = normType(rMap[cn]);
  163. const dt = normType(dMap[cn]);
  164. if (rt !== dt) {
  165. realTypeDiffs.push({col: cn, rohs: rMap[cn], db: dMap[cn], rohsNorm: rt, dbNorm: dt});
  166. }
  167. }
  168. const hasRealDiff = rOnly.length > 0 || dOnly.length > 0 || realTypeDiffs.length > 0;
  169. if (!hasRealDiff) continue;
  170. // Special case: tables with completely different column systems
  171. const radicalTables = ['T_BUSI_REGULAR_EVALU', 'T_BUSI_REVIEW_INFO', 'T_DICT_HIDD_ENG'];
  172. if (radicalTables.includes(tname)) {
  173. const lowerName = tname.toLowerCase() + '_rohs';
  174. const ddl = extractDDL(tname);
  175. const tableComment = getTableComment(tname);
  176. const colComments = getColumnComments(tname, rCols.map(c => c.name));
  177. alterScript += `\n-- ============================================================\n`;
  178. alterScript += `-- 【决策#5 - 两套并存】\n`;
  179. if (tableComment) alterScript += `-- 原表说明: ${tableComment}\n`;
  180. alterScript += `-- ${tname}: ROHS版与安监版列名体系完全不同,各自独立维护\n`;
  181. alterScript += `-- 创建 ROHS 版本为: ${lowerName}\n`;
  182. alterScript += `-- 原安监表 ${dbName} 保持不变\n`;
  183. alterScript += `-- ============================================================\n\n`;
  184. if (ddl) {
  185. let transformed = ddl;
  186. transformed = transformed.replace(new RegExp(`"${SOURCE_SCHEMA}"`, 'g'), `${TARGET_SCHEMA}`);
  187. transformed = transformed.replace(new RegExp(`"${tname}"`, 'g'), lowerName);
  188. for (const col of rCols) {
  189. if (col.name !== col.name.toLowerCase()) {
  190. const re = new RegExp(`"${col.name}"`, 'g');
  191. transformed = transformed.replace(re, `"${col.name.toLowerCase()}"`);
  192. }
  193. }
  194. alterScript += transformed + '\n\n';
  195. }
  196. // Comments
  197. if (tableComment) {
  198. alterScript += `COMMENT ON TABLE ${TARGET_SCHEMA}.${lowerName} IS '${tableComment}(ROHS版本)';\n`;
  199. }
  200. for (const col of rCols) {
  201. const comment = colComments[col.name];
  202. if (comment) {
  203. alterScript += `COMMENT ON COLUMN ${TARGET_SCHEMA}.${lowerName}."${col.name.toLowerCase()}" IS '${comment}';\n`;
  204. }
  205. }
  206. alterScript += '\n';
  207. rollbackAlter += `-- ${tname} ROHS版回滚\n`;
  208. rollbackAlter += `DROP TABLE IF EXISTS ${TARGET_SCHEMA}.${lowerName} CASCADE;\n\n`;
  209. diffDetails.push({
  210. rohs: tname, db: dbName, type: 'radical',
  211. action: `两套并存,ROHS版创建为 ${lowerName}`,
  212. fields: rCols.map(c => ({rohs: c.name, db: c.name, rohsType: c.type, dbType: '(新表)', action: '新建ROHS版表'})),
  213. });
  214. continue;
  215. }
  216. const lowerName = dbName;
  217. const alters = [];
  218. const rollbacks = [];
  219. const tableComment = getTableComment(tname);
  220. const colComments = getColumnComments(tname, rCols.map(c => c.name));
  221. // Build header
  222. let headerComment = `\n-- ============================================================\n`;
  223. headerComment += `-- 表: ${tname} ↔ ${dbName}\n`;
  224. if (tableComment) headerComment += `-- 表说明: ${tableComment}\n`;
  225. headerComment += `-- ROHS列数: ${rCols.length} | 安监列数: ${dCols.length}\n`;
  226. headerComment += `-- ============================================================\n`;
  227. let hasActualAlter = false;
  228. // 1. STR_TO_DATE → TO_DATE (ROHS code side only)
  229. if (rOnly.includes('STR_TO_DATE') && dOnly.includes('TO_DATE')) {
  230. headerComment += `--\n`;
  231. headerComment += `-- 【决策#4 - 列名统一】STR_TO_DATE → TO_DATE\n`;
  232. headerComment += `-- DB侧: 安监已有 TO_DATE 列,无需操作\n`;
  233. headerComment += `-- 代码侧: ROHS代码需将 'STR_TO_DATE' 改为 'TO_DATE'\n`;
  234. headerComment += `--\n`;
  235. rOnly.splice(rOnly.indexOf('STR_TO_DATE'), 1);
  236. } else if (rOnly.includes('STR_TO_DATE') && !dOnly.includes('TO_DATE')) {
  237. // STR_TO_DATE exists in ROHS but DB has neither STR_TO_DATE nor TO_DATE
  238. // Add it as a new column
  239. // (This case is for ATT_PERS_BASE etc.)
  240. }
  241. // 2. Add ROHS-only columns to DB
  242. for (const cn of rOnly) {
  243. const colType = rMap[cn];
  244. const lowerCol = cn.toLowerCase();
  245. let pgType = colType;
  246. if (pgType === 'BPCHAR' || pgType === 'CHAR(1)') pgType = 'VARCHAR(4)';
  247. else if (pgType.startsWith('CHAR(')) pgType = 'VARCHAR(' + pgType.match(/\d+/)[0] + ')';
  248. else if (pgType.startsWith('DECIMAL')) pgType = pgType.replace('DECIMAL', 'NUMERIC');
  249. const comment = colComments[cn] || '';
  250. const commentSql = comment ? ` -- ${comment}` : '';
  251. alters.push(`-- ROHS独有列: ${cn} (ROHS类型: ${colType})`);
  252. alters.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ADD COLUMN "${lowerCol}" ${pgType};${commentSql}`);
  253. if (comment) {
  254. alters.push(`COMMENT ON COLUMN ${TARGET_SCHEMA}.${lowerName}."${lowerCol}" IS '${comment}';`);
  255. }
  256. alters.push('');
  257. rollbacks.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} DROP COLUMN IF EXISTS "${lowerCol}";`);
  258. hasActualAlter = true;
  259. }
  260. // 3. Document DB-only columns (already exist, no action needed)
  261. if (dOnly.length > 0) {
  262. headerComment += `--\n`;
  263. headerComment += `-- 【安监独有列 - DB侧已存在,无需操作】\n`;
  264. for (const cn of dOnly) {
  265. headerComment += `-- "${cn.toLowerCase()}" (${dMap[cn]}) — 安监系统使用,ROHS代码可忽略\n`;
  266. }
  267. headerComment += `--\n`;
  268. }
  269. // 4. Type changes
  270. for (const td of realTypeDiffs) {
  271. const lowerCol = td.col.toLowerCase();
  272. let targetType;
  273. let reason = '';
  274. if (td.rohsNorm === 'TIMESTAMP' && td.dbNorm === 'DATE') {
  275. targetType = 'TIMESTAMP';
  276. reason = 'ROHS使用TIMESTAMP存储时间,安监使用DATE,统一升级为TIMESTAMP(向下兼容)';
  277. } else if (td.dbNorm === 'TIMESTAMP' && td.rohsNorm === 'DATE') {
  278. targetType = 'TIMESTAMP';
  279. reason = '统一为TIMESTAMP';
  280. } else if (td.rohsNorm === 'VARCHAR' && td.dbNorm === 'CHAR') {
  281. targetType = 'VARCHAR(100)';
  282. reason = 'CHAR→VARCHAR放宽,兼容两边写入';
  283. } else if (td.rohsNorm === 'NUMERIC' && td.dbNorm === 'VARCHAR') {
  284. targetType = 'VARCHAR(100)';
  285. reason = '类型不一致,统一为VARCHAR';
  286. } else {
  287. targetType = 'VARCHAR(255)';
  288. reason = `类型不一致: ROHS=${td.rohs} vs DB=${td.db},统一为VARCHAR`;
  289. }
  290. alters.push(`-- 类型对齐: "${lowerCol}" ROHS=${td.rohs} DB=${td.db}`);
  291. alters.push(`-- ${reason}`);
  292. alters.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ALTER COLUMN "${lowerCol}" TYPE ${targetType} USING "${lowerCol}"::${targetType};`);
  293. alters.push('');
  294. rollbacks.push(`-- 回滚类型变更 ["${lowerCol}"]: 需手动恢复为原类型 ${td.db}`);
  295. hasActualAlter = true;
  296. }
  297. if (hasActualAlter) {
  298. alterScript += headerComment;
  299. if (headerComment.includes('--\n--')) alterScript += '\n';
  300. alterScript += alters.join('\n') + '\n';
  301. } else {
  302. // Only info comments, no actual ALTER
  303. alterScript += headerComment;
  304. alterScript += `-- [仅代码侧改动,DB侧无操作]\n\n`;
  305. }
  306. if (rollbacks.length > 0) {
  307. rollbackAlter += `\n-- ${tname} 回滚\n`;
  308. rollbackAlter += rollbacks.join('\n') + '\n';
  309. }
  310. // Build field mapping
  311. const allFields = [...new Set([...Object.keys(rMap), ...Object.keys(dMap)])];
  312. const fields = allFields.map(f => {
  313. const rType = rMap[f] || '(不存在)';
  314. const dType = dMap[f] || '(不存在)';
  315. let action = '';
  316. if (!(f in dMap)) action = '新增列(ROHS独有)';
  317. else if (!(f in rMap)) action = '安监独有(ROHS代码可忽略)';
  318. else if (normType(rType) !== normType(dType)) action = `类型变更: ${dType} → ${rType}`;
  319. else if (f === 'STR_TO_DATE' && 'TO_DATE' in dMap) action = 'ROHS代码侧改为 TO_DATE';
  320. else action = '一致';
  321. return {rohs: f, db: f === 'STR_TO_DATE' && 'TO_DATE' in dMap ? 'TO_DATE' : f, rohsType: rType, dbType: dType, action};
  322. });
  323. diffDetails.push({rohs: tname, db: dbName, type: 'diff', action: hasActualAlter ? `${alters.filter(l => l.startsWith('ALTER')).length}个ALTER` : '仅代码侧改动', fields, tableComment: tableComment || ''});
  324. }
  325. fs.writeFileSync('d:/Workspaces/slaj/deliverables/02_diff_tables_alter.sql', alterScript);
  326. fs.writeFileSync('d:/Workspaces/slaj/deliverables/02_diff_tables_rollback.sql', rollbackAlter);
  327. console.log('Generated: 02_diff_tables_alter.sql + rollback (with comments)');
  328. // ===== GENERATE FIELD MAPPING =====
  329. let mappingMD = `# 字段映射表 — ROHS → 安监(slaj)
  330. > 给改 ROHS 代码的团队使用。包含所有列名变更、类型变更的完整对照。
  331. ## 一、新表(${newTables.length}张)
  332. 以下表在安监中不存在,本次新建。ROHS 代码侧需:
  333. 1. 连接 schema 从 \`SLAJ_ROHS\` 改为 \`slaj\`
  334. 2. 表名和列名统一改为小写
  335. | ROHS 表名 | 安监表名 | 列数 | 表说明 |
  336. |-----------|---------|------|--------|
  337. `;
  338. for (const t of newTables.sort()) {
  339. const cols = rohsTables[t] || [];
  340. const comment = getTableComment(t) || '-';
  341. mappingMD += `| ${t} | \`${t.toLowerCase()}\` | ${cols.length} | ${comment} |\n`;
  342. }
  343. mappingMD += `\n## 二、重叠表字段映射(${diffDetails.length}张)\n\n`;
  344. for (const d of diffDetails) {
  345. mappingMD += `### ${d.rohs} → \`${d.db}\`\n\n`;
  346. if (d.tableComment) mappingMD += `**表说明**: ${d.tableComment}\n\n`;
  347. mappingMD += `**操作**: ${d.action}\n\n`;
  348. mappingMD += `| ROHS列名 | 安监列名 | ROHS类型 | 安监类型 | 备注 |\n`;
  349. mappingMD += `|----------|---------|----------|---------|------|\n`;
  350. for (const f of d.fields) {
  351. mappingMD += `| ${f.rohs} | ${f.db} | ${f.rohsType} | ${f.dbType} | ${f.action} |\n`;
  352. }
  353. mappingMD += '\n';
  354. }
  355. // Full match tables
  356. const fullMatchTables = overlapTables.filter(t => {
  357. const dbName = dbLowerMap[t.toLowerCase()];
  358. const rCols = rohsTables[t] || [];
  359. const dCols = dbOverlap[dbName] || [];
  360. const rMap = {}; rCols.forEach(c => { rMap[c.name] = c.type; });
  361. const dMap = {}; dCols.forEach(c => { dMap[c.name] = c.type; });
  362. const rOnly = Object.keys(rMap).filter(n => !(n in dMap));
  363. const dOnly = Object.keys(dMap).filter(n => !(n in rMap));
  364. const common = Object.keys(rMap).filter(n => n in dMap);
  365. const realTypeDiffs = common.filter(cn => normType(rMap[cn]) !== normType(dMap[cn]));
  366. return rOnly.length === 0 && dOnly.length === 0 && realTypeDiffs.length === 0;
  367. });
  368. mappingMD += `\n## 三、完全一致的表(${fullMatchTables.length}张,无需任何改动)\n\n`;
  369. mappingMD += `| ROHS表名 | 安监表名 | 列数 | 表说明 |\n`;
  370. mappingMD += `|----------|---------|------|--------|\n`;
  371. for (const t of fullMatchTables.sort()) {
  372. const dbName = dbLowerMap[t.toLowerCase()];
  373. const cols = rohsTables[t] || [];
  374. const comment = getTableComment(t) || '-';
  375. mappingMD += `| ${t} | \`${dbName}\` | ${cols.length} | ${comment} |\n`;
  376. }
  377. mappingMD += `\n## 四、ROHS 代码改动清单\n\n`;
  378. mappingMD += `### 必须改动\n\n`;
  379. mappingMD += `1. **Schema 改名**: \`SLAJ_ROHS\` → \`slaj\`(数据库连接配置/jdbc url)\n`;
  380. mappingMD += `2. **表名小写**: 所有表名从大写改为小写(如 \`ATT_ENG_BASE\` → \`att_eng_base\`)\n`;
  381. mappingMD += `3. **列名小写**: 所有列名从大写改为小写\n`;
  382. mappingMD += `4. **STR_TO_DATE → TO_DATE**: 涉及以下表,ROHS代码中所有 \`STR_TO_DATE\` 改为 \`TO_DATE\`\n`;
  383. const strToDateTables = diffDetails.filter(d => d.fields.some(f => f.action.includes('TO_DATE')));
  384. for (const d of strToDateTables) {
  385. mappingMD += ` - ${d.rohs} → \`${d.db}\`\n`;
  386. }
  387. mappingMD += `5. **3张独立表改名**:\n`;
  388. mappingMD += ` - \`T_BUSI_REGULAR_EVALU\` → \`t_busi_regular_evalu_rohs\`\n`;
  389. mappingMD += ` - \`T_BUSI_REVIEW_INFO\` → \`t_busi_review_info_rohs\`\n`;
  390. mappingMD += ` - \`T_DICT_HIDD_ENG\` → \`t_dict_hidd_eng_rohs\`\n`;
  391. mappingMD += `\n### 建议检查\n\n`;
  392. mappingMD += `1. TIMESTAMP 类型字段:如果代码中将数据库字段映射为 LocalDate,需改为 LocalDateTime\n`;
  393. mappingMD += `2. 新增列(取并集):如果代码中有 \`SELECT *\` 查询,确认是否受影响\n`;
  394. mappingMD += `3. 安监独有列:ROHS代码可选择忽略这些列,写入时留 NULL 即可\n`;
  395. fs.writeFileSync('d:/Workspaces/slaj/deliverables/03_field_mapping.md', mappingMD);
  396. console.log('Generated: 03_field_mapping.md');
  397. // ===== README =====
  398. let guide = `# 合并执行指南
  399. ## 前置条件
  400. - [ ] 数据库备份已完成
  401. - [ ] 安监系统已通知停机窗口(建议低峰期执行)
  402. - [ ] ROHS 代码改动团队已就位
  403. ## 执行顺序
  404. ### 第一步: 备份(必须)
  405. \`\`\`bash
  406. 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
  407. \`\`\`
  408. ### 第二步: 执行新表DDL(无风险,${newTables.length}张)
  409. \`\`\`bash
  410. psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/01_new_tables_ddl.sql
  411. \`\`\`
  412. 验证: \`SELECT count(*) FROM information_schema.tables WHERE table_schema='slaj' AND table_name IN (SELECT lower_name FROM new_tables);\`
  413. 回滚: \`psql ... -f deliverables/01_new_tables_rollback.sql\`
  414. ### 第三步: 执行差异表ALTER(${diffDetails.length}张)
  415. \`\`\`bash
  416. psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/02_diff_tables_alter.sql
  417. \`\`\`
  418. 回滚: \`psql ... -f deliverables/02_diff_tables_rollback.sql\`
  419. ### 第四步: ROHS 代码改动
  420. 参照 \`deliverables/03_field_mapping.md\`,另一团队修改 ROHS 代码。
  421. ## 交付物
  422. | 文件 | 行数 | 说明 |
  423. |------|------|------|
  424. | \`01_new_tables_ddl.sql\` | - | ${newTables.length}张新表建表 + 注释 |
  425. | \`01_new_tables_rollback.sql\` | - | DROP TABLE 回滚 |
  426. | \`02_diff_tables_alter.sql\` | - | ${diffDetails.length}张表结构对齐 ALTER |
  427. | \`02_diff_tables_rollback.sql\` | - | ALTER 回滚 |
  428. | \`03_field_mapping.md\` | - | 字段映射(给改代码团队) |
  429. `;
  430. fs.writeFileSync('d:/Workspaces/slaj/deliverables/README.md', guide);
  431. console.log('Generated: README.md');
  432. console.log('\n=== GENERATION COMPLETE ===');
  433. console.log(`New tables: ${newTables.length}`);
  434. console.log(`Diff tables: ${diffDetails.length} (${diffDetails.filter(d => d.type === 'radical').length} radical, ${diffDetails.filter(d => d.type === 'diff').length} normal)`);
  435. console.log(`Full match: ${fullMatchTables.length}`);