_gen_deliverables.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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 rohsBak = Object.keys(rohsTables).filter(t => isBak(t) && !isSys(t));
  16. const dbLowerMap = {};
  17. dbAllNames.forEach(t => { dbLowerMap[t.toLowerCase()] = t; });
  18. // Classify
  19. const newTables = rohsReal.filter(t => !dbLowerMap[t.toLowerCase()]);
  20. const overlapTables = rohsReal.filter(t => dbLowerMap[t.toLowerCase()]);
  21. function normType(t) {
  22. t = t.toUpperCase().trim().replace(/\s+/g,' ').replace(/\(\d+(?:,\d+)?\)/g,'');
  23. 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'};
  24. return m[t] || t;
  25. }
  26. // ===== EXTRACT ORIGINAL DDL FOR TABLES =====
  27. function extractDDL(tableName) {
  28. // Extract CREATE TABLE block from SQL file
  29. const escaped = tableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  30. const re = new RegExp(`CREATE TABLE "${SOURCE_SCHEMA}"\\."${escaped}"[\\s\\S]*?;`, 'g');
  31. const m = sqlContent.match(re);
  32. return m ? m[0] : null;
  33. }
  34. function convertTypeForPg(type) {
  35. // CHAR(n) -> character(n), VARCHAR(n) -> character varying(n)
  36. // Keep mostly as-is since PostgreSQL understands both
  37. return type.trim();
  38. }
  39. function toSnakeCase(str) {
  40. return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '').replace(/_{2,}/g, '_');
  41. }
  42. // ===== GENERATE NEW TABLE DDL =====
  43. let newTableDDL = `-- ============================================================
  44. -- 六项机制(ROHS) 新表 DDL — 目标 schema: ${TARGET_SCHEMA}
  45. -- 生成时间: 2026-07-10
  46. -- 共 ${newTables.length} 张表
  47. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  48. -- ============================================================
  49. `;
  50. let rollbackNewTables = `-- ============================================================
  51. -- 回滚脚本: 删除新建的 ${newTables.length} 张表
  52. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  53. -- ============================================================
  54. `;
  55. // Write 73 new tables DDL
  56. for (const tname of newTables.sort()) {
  57. const ddl = extractDDL(tname);
  58. const lowerName = tname.toLowerCase();
  59. const cols = rohsTables[tname] || [];
  60. newTableDDL += `-- ============================================================\n`;
  61. newTableDDL += `-- Table: ${lowerName} (原 ROHS 表: ${SOURCE_SCHEMA}.${tname})\n`;
  62. newTableDDL += `-- ${cols.length} columns\n`;
  63. if (ddl) {
  64. // Transform: change schema, make table name lowercase, make column names lowercase
  65. let transformed = ddl;
  66. // Change schema
  67. transformed = transformed.replace(new RegExp(`"${SOURCE_SCHEMA}"`, 'g'), `${TARGET_SCHEMA}`);
  68. // Change table name
  69. transformed = transformed.replace(new RegExp(`"${tname}"`, 'g'), `${lowerName}`);
  70. // Lowercase column names (quoted identifiers)
  71. transformed = transformed.replace(/"(\w+)"/g, (match, col) => {
  72. // Skip schema.table.column patterns already handled
  73. // Check if it's a known column name from this table
  74. const colUpper = col.toUpperCase();
  75. const isKnownCol = cols.some(c => c.name === colUpper);
  76. if (isKnownCol) {
  77. return `"${col.toLowerCase()}"`;
  78. }
  79. return match;
  80. });
  81. // Also handle unquoted column names at the start of lines (before type)
  82. for (const col of cols) {
  83. if (col.name !== col.name.toLowerCase()) {
  84. const re = new RegExp(`(\\s)"?${col.name}"?(\\s+)`, 'g');
  85. transformed = transformed.replace(re, `$1${col.name.toLowerCase()}$2`);
  86. }
  87. }
  88. newTableDDL += transformed + '\n\n';
  89. } else {
  90. // Generate DDL from parsed columns
  91. newTableDDL += `CREATE TABLE ${TARGET_SCHEMA}.${lowerName} (\n`;
  92. const colDefs = cols.map(c => ` "${c.name.toLowerCase()}" ${c.type}`).join(',\n');
  93. newTableDDL += colDefs;
  94. newTableDDL += `\n);\n\n`;
  95. }
  96. rollbackNewTables += `DROP TABLE IF EXISTS ${TARGET_SCHEMA}.${lowerName} CASCADE;\n`;
  97. }
  98. fs.writeFileSync('d:/Workspaces/slaj/deliverables/01_new_tables_ddl.sql', newTableDDL);
  99. fs.writeFileSync('d:/Workspaces/slaj/deliverables/01_new_tables_rollback.sql', rollbackNewTables);
  100. console.log('Generated: 01_new_tables_ddl.sql + rollback');
  101. // ===== GENERATE DIFF TABLE ALTER =====
  102. let alterScript = `-- ============================================================
  103. -- 差异表 ALTER 脚本 — 37张重叠表的结构对齐
  104. -- 目标: 使 slaj schema 中的表兼容 ROHS 应用
  105. -- 执行方式: psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f this_file.sql
  106. -- ============================================================
  107. `;
  108. let rollbackAlter = `-- ============================================================
  109. -- 回滚脚本: 撤销差异表的 ALTER 操作
  110. -- ============================================================
  111. `;
  112. const diffDetails = [];
  113. for (const tname of overlapTables.sort()) {
  114. const dbName = dbLowerMap[tname.toLowerCase()];
  115. const rCols = rohsTables[tname] || [];
  116. const dCols = dbOverlap[dbName] || [];
  117. const rMap = {}; rCols.forEach(c => { rMap[c.name] = c.type; });
  118. const dMap = {}; dCols.forEach(c => { dMap[c.name] = c.type; });
  119. const rOnly = Object.keys(rMap).filter(n => !(n in dMap));
  120. const dOnly = Object.keys(dMap).filter(n => !(n in rMap));
  121. const common = Object.keys(rMap).filter(n => n in dMap);
  122. // Find real type diffs
  123. const realTypeDiffs = [];
  124. const trivialDiffs = [];
  125. for (const cn of common) {
  126. const rt = normType(rMap[cn]);
  127. const dt = normType(dMap[cn]);
  128. if (rt !== dt) {
  129. realTypeDiffs.push({col: cn, rohs: rMap[cn], db: dMap[cn], rohsNorm: rt, dbNorm: dt});
  130. } else if (rMap[cn] !== dMap[cn]) {
  131. trivialDiffs.push({col: cn, rohs: rMap[cn], db: dMap[cn]});
  132. }
  133. }
  134. const hasRealDiff = rOnly.length > 0 || dOnly.length > 0 || realTypeDiffs.length > 0;
  135. if (!hasRealDiff) continue;
  136. // Special case: tables with completely different column systems (decision: keep both)
  137. const radicalTables = ['T_BUSI_REGULAR_EVALU', 'T_BUSI_REVIEW_INFO', 'T_DICT_HIDD_ENG'];
  138. if (radicalTables.includes(tname)) {
  139. // These tables: ROHS version stays separate, create as new table with _rohs suffix
  140. const lowerName = tname.toLowerCase() + '_rohs';
  141. const ddl = extractDDL(tname);
  142. alterScript += `\n-- ============================================================\n`;
  143. alterScript += `-- ${tname}: 列名体系完全不同,按决策#5两套并存 — 创建 ROHS 版本为 ${lowerName}\n`;
  144. alterScript += `-- ============================================================\n\n`;
  145. if (ddl) {
  146. let transformed = ddl;
  147. transformed = transformed.replace(new RegExp(`"${SOURCE_SCHEMA}"`, 'g'), `${TARGET_SCHEMA}`);
  148. transformed = transformed.replace(new RegExp(`"${tname}"`, 'g'), `${lowerName}`);
  149. // Lowercase known column names
  150. for (const col of rCols) {
  151. if (col.name !== col.name.toLowerCase()) {
  152. const re = new RegExp(`"${col.name}"`, 'g');
  153. transformed = transformed.replace(re, `"${col.name.toLowerCase()}"`);
  154. }
  155. }
  156. alterScript += transformed + '\n\n';
  157. }
  158. rollbackAlter += `DROP TABLE IF EXISTS ${TARGET_SCHEMA}.${lowerName} CASCADE;\n`;
  159. diffDetails.push({
  160. rohs: tname, db: dbName, type: 'radical',
  161. action: `两套并存,ROHS版创建为 ${lowerName}`,
  162. fields: rCols.map(c => ({rohs: c.name, db: c.name, action: '新建ROHS版表'})),
  163. });
  164. continue;
  165. }
  166. const lowerName = dbName;
  167. const alters = [];
  168. const rollbacks = [];
  169. // 1. Rename STR_TO_DATE -> TO_DATE (ROHS side)
  170. if (rOnly.includes('STR_TO_DATE') && dOnly.includes('TO_DATE')) {
  171. alters.push(`-- 重命名 STR_TO_DATE -> TO_DATE (决策#4) [注意: 此列从ROHS代码视角改名,安监代码不受影响]`);
  172. // We keep TO_DATE, no ALTER needed on DB side since DB already has TO_DATE.
  173. // But we need to tell the ROHS code team that this column is TO_DATE, not STR_TO_DATE.
  174. // Actually, the ROHS code expects STR_TO_DATE and DB has TO_DATE.
  175. // Decision was: unify to TO_DATE. So ROHS code must change.
  176. // On DB side, nothing to do for the column rename (DB already has TO_DATE).
  177. // But ROHS has STR_TO_DATE which doesn't exist in DB. Nothing to drop.
  178. alters.push(`-- [仅代码侧改动] ROHS代码需将 STR_TO_DATE 改为 TO_DATE,DB侧无需操作`);
  179. rOnly.splice(rOnly.indexOf('STR_TO_DATE'), 1);
  180. }
  181. // 2. Add columns that ROHS has but DB doesn't
  182. for (const cn of rOnly) {
  183. const colType = rMap[cn];
  184. const lowerCol = cn.toLowerCase();
  185. // Use wider compatible type
  186. let pgType = colType;
  187. if (colType === 'BPCHAR' || colType === 'CHAR(1)') pgType = 'VARCHAR(4)';
  188. if (colType.startsWith('CHAR(')) pgType = 'VARCHAR(' + colType.match(/\d+/)[0] + ')';
  189. alters.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ADD COLUMN "${lowerCol}" ${pgType};`);
  190. rollbacks.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} DROP COLUMN IF EXISTS "${lowerCol}";`);
  191. }
  192. // 3. Add columns that DB has but ROHS doesn't (for ROHS's future use, nullable)
  193. for (const cn of dOnly) {
  194. const colType = dMap[cn];
  195. const lowerCol = cn.toLowerCase();
  196. let pgType = colType;
  197. if (pgType === 'BPCHAR') pgType = 'VARCHAR(4)';
  198. if (pgType === 'CHAR') pgType = 'VARCHAR(4)';
  199. alters.push(`-- [安监独有列,ROHS兼容] ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ADD COLUMN IF NOT EXISTS "${lowerCol}" ${pgType};`);
  200. // No rollback — we're not adding, just documenting. DB already has it.
  201. alters.push(`-- 列 "${lowerCol}" 在安监中已存在,无需操作`);
  202. }
  203. // 4. Type changes for real diffs
  204. for (const td of realTypeDiffs) {
  205. const lowerCol = td.col.toLowerCase();
  206. // Determine target type (use wider)
  207. let targetType = td.rohsNorm === 'TIMESTAMP' ? 'TIMESTAMP' : 'VARCHAR(255)';
  208. if (td.rohsNorm === 'NUMERIC' && td.dbNorm === 'NUMERIC') targetType = 'NUMERIC';
  209. if (td.rohsNorm === 'DATE' && td.dbNorm === 'TIMESTAMP') targetType = 'TIMESTAMP';
  210. if (td.dbNorm === 'DATE' && td.rohsNorm === 'TIMESTAMP') targetType = 'TIMESTAMP';
  211. if (td.rohsNorm === 'VARCHAR' && td.dbNorm === 'CHAR') targetType = 'VARCHAR(100)';
  212. if (td.rohsNorm === 'NUMERIC' && td.dbNorm === 'VARCHAR') targetType = 'VARCHAR(100)';
  213. alters.push(`-- 类型对齐: ${td.rohs} -> ${td.db} (统一为 ${targetType})`);
  214. alters.push(`ALTER TABLE ${TARGET_SCHEMA}.${lowerName} ALTER COLUMN "${lowerCol}" TYPE ${targetType} USING "${lowerCol}"::${targetType};`);
  215. rollbacks.push(`-- 回滚类型变更 [${lowerCol}]: 需手动恢复原类型 ${td.db}`);
  216. }
  217. if (alters.length > 0) {
  218. alterScript += `\n-- ============================================================\n`;
  219. alterScript += `-- ${tname} <-> ${dbName} (${rCols.length}c / ${dCols.length}c)\n`;
  220. alterScript += `-- ============================================================\n\n`;
  221. alterScript += alters.join('\n') + '\n';
  222. }
  223. if (rollbacks.length > 0) {
  224. rollbackAlter += `\n-- ${tname} rollback\n`;
  225. rollbackAlter += rollbacks.join('\n') + '\n';
  226. }
  227. // Build field mapping
  228. const allFields = [...new Set([...Object.keys(rMap), ...Object.keys(dMap)])];
  229. const fields = allFields.map(f => {
  230. const rType = rMap[f] || '(不存在)';
  231. const dType = dMap[f] || '(不存在)';
  232. let action = '';
  233. if (!(f in dMap)) action = '新增列(ROHS独有)';
  234. else if (!(f in rMap)) action = '安监独有(ROHS可忽略)';
  235. else if (normType(rType) !== normType(dType)) action = `类型变更: ${dType} → ${rType}`;
  236. else if (f === 'STR_TO_DATE' && 'TO_DATE' in dMap) action = 'ROHS侧改为 TO_DATE';
  237. else action = '一致';
  238. return {rohs: f, db: f === 'STR_TO_DATE' && 'TO_DATE' in dMap ? 'TO_DATE' : f, rohsType: rType, dbType: dType, action};
  239. });
  240. diffDetails.push({rohs: tname, db: dbName, type: 'diff', action: `${alters.length}个ALTER`, fields});
  241. }
  242. fs.writeFileSync('d:/Workspaces/slaj/deliverables/02_diff_tables_alter.sql', alterScript);
  243. fs.writeFileSync('d:/Workspaces/slaj/deliverables/02_diff_tables_rollback.sql', rollbackAlter);
  244. console.log('Generated: 02_diff_tables_alter.sql + rollback');
  245. // ===== GENERATE FIELD MAPPING =====
  246. let mappingMD = `# 字段映射表 — ROHS → 安监(slaj)
  247. > 给改 ROHS 代码的团队使用。列名变更、类型变更汇总。
  248. ## 一、新表(${newTables.length}张)
  249. 以下表在安监中不存在,直接新建。ROHS 代码侧需将连接 schema 从 SLAJ_ROHS 改为 slaj,表名改为小写。
  250. | ROHS 表名 | 安监表名 | 列数 | 备注 |
  251. |-----------|---------|------|------|
  252. `;
  253. for (const t of newTables.sort()) {
  254. newTableDDL += '';
  255. const cols = rohsTables[t] || [];
  256. mappingMD += `| ${t} | ${t.toLowerCase()} | ${cols.length} | 新表 |\n`;
  257. }
  258. mappingMD += `\n## 二、重叠表字段映射(${diffDetails.length}张)\n\n`;
  259. for (const d of diffDetails) {
  260. mappingMD += `### ${d.rohs} → ${d.db}\n\n`;
  261. mappingMD += `**操作**: ${d.action}\n\n`;
  262. mappingMD += `| ROHS列名 | 安监列名 | ROHS类型 | 安监类型 | 备注 |\n`;
  263. mappingMD += `|----------|---------|----------|---------|------|\n`;
  264. for (const f of d.fields) {
  265. mappingMD += `| ${f.rohs} | ${f.db} | ${f.rohsType} | ${f.dbType} | ${f.action} |\n`;
  266. }
  267. mappingMD += '\n';
  268. }
  269. // Also document all overlap tables (including 28 full matches)
  270. const fullMatchTables = overlapTables.filter(t => {
  271. const dbName = dbLowerMap[t.toLowerCase()];
  272. const rCols = rohsTables[t] || [];
  273. const dCols = dbOverlap[dbName] || [];
  274. const rMap = {}; rCols.forEach(c => { rMap[c.name] = c.type; });
  275. const dMap = {}; dCols.forEach(c => { dMap[c.name] = c.type; });
  276. const rOnly = Object.keys(rMap).filter(n => !(n in dMap));
  277. const dOnly = Object.keys(dMap).filter(n => !(n in rMap));
  278. const common = Object.keys(rMap).filter(n => n in dMap);
  279. const realTypeDiffs = common.filter(cn => normType(rMap[cn]) !== normType(dMap[cn]));
  280. return rOnly.length === 0 && dOnly.length === 0 && realTypeDiffs.length === 0;
  281. });
  282. mappingMD += `\n## 三、完全一致的表(${fullMatchTables.length}张,无需任何改动)\n\n`;
  283. mappingMD += `| ROHS表名 | 安监表名 | 列数 |\n`;
  284. mappingMD += `|----------|---------|------|\n`;
  285. for (const t of fullMatchTables.sort()) {
  286. const dbName = dbLowerMap[t.toLowerCase()];
  287. const cols = rohsTables[t] || [];
  288. mappingMD += `| ${t} | ${dbName} | ${cols.length} |\n`;
  289. }
  290. mappingMD += `\n## 四、ROHS 代码改动清单\n\n`;
  291. mappingMD += `### 必须改动\n\n`;
  292. mappingMD += `1. **Schema 改名**: \`SLAJ_ROHS\` → \`slaj\`(数据库连接配置)\n`;
  293. mappingMD += `2. **表名小写**: 所有表名从大写改为小写(如 \`ATT_ENG_BASE\` → \`att_eng_base\`)\n`;
  294. mappingMD += `3. **列名小写**: 所有列名从大写改为小写\n`;
  295. mappingMD += `4. **STR_TO_DATE → TO_DATE**: 涉及 ${diffDetails.filter(d => d.fields.some(f => f.action.includes('TO_DATE'))).length} 张表\n`;
  296. mappingMD += `5. **3张独立表**: \`T_BUSI_REGULAR_EVALU\` → \`t_busi_regular_evalu_rohs\`, 同理 \`T_BUSI_REVIEW_INFO\`, \`T_DICT_HIDD_ENG\`\n\n`;
  297. mappingMD += `### 建议改动\n\n`;
  298. mappingMD += `1. TIMESTAMP 类型字段:如果代码中将 TIMESTAMP 转为 Date 处理,部分字段在安监侧是 DATE 类型,需确认兼容性\n`;
  299. mappingMD += `2. 新增列(取并集):代码中如果有 \`SELECT *\` 需确认是否受影响\n`;
  300. fs.writeFileSync('d:/Workspaces/slaj/deliverables/03_field_mapping.md', mappingMD);
  301. console.log('Generated: 03_field_mapping.md');
  302. // ===== GENERATE MASTER EXECUTION GUIDE =====
  303. let guide = `# 合并执行指南
  304. ## 执行顺序
  305. ### 第一步: 备份
  306. \`\`\`bash
  307. pg_dump -h 39.98.38.2 -p 5866 -U highgo -d highgo -n slaj --schema-only > slaj_schema_backup_$(date +%Y%m%d).sql
  308. \`\`\`
  309. ### 第二步: 执行新表DDL(无风险)
  310. \`\`\`bash
  311. psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/01_new_tables_ddl.sql
  312. \`\`\`
  313. 涉及 ${newTables.length} 张新表。若失败,执行回滚:
  314. \`\`\`bash
  315. psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/01_new_tables_rollback.sql
  316. \`\`\`
  317. ### 第三步: 执行差异表ALTER
  318. \`\`\`bash
  319. psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/02_diff_tables_alter.sql
  320. \`\`\`
  321. 涉及 ${diffDetails.length} 张重叠表的字段调整。若失败,执行回滚:
  322. \`\`\`bash
  323. psql -h 39.98.38.2 -p 5866 -U highgo -d highgo -f deliverables/02_diff_tables_rollback.sql
  324. \`\`\`
  325. ### 第四步: ROHS 代码改动
  326. 参照 \`deliverables/03_field_mapping.md\`,另一团队修改 ROHS 代码适配新 schema。
  327. ## 交付物清单
  328. | 文件 | 说明 |
  329. |------|------|
  330. | \`01_new_tables_ddl.sql\` | ${newTables.length} 张新表建表语句 |
  331. | \`01_new_tables_rollback.sql\` | 新表回滚(DROP TABLE) |
  332. | \`02_diff_tables_alter.sql\` | ${diffDetails.length} 张重叠表结构对齐 ALTER |
  333. | \`02_diff_tables_rollback.sql\` | ALTER 回滚脚本 |
  334. | \`03_field_mapping.md\` | 字段映射表(给改代码团队) |
  335. `;
  336. fs.writeFileSync('d:/Workspaces/slaj/deliverables/README.md', guide);
  337. console.log('Generated: README.md (execution guide)');
  338. console.log('\n=== DONE ===');
  339. console.log(`New tables: ${newTables.length}`);
  340. console.log(`Diff tables: ${diffDetails.length}`);
  341. console.log(`Full match: ${fullMatchTables.length}`);