From 8bf581387622c60ccb58cacb02606ea4e30b9ef3 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Sat, 15 Nov 2025 16:30:32 +0800 Subject: [PATCH] init --- converter.js | 154 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 138 insertions(+), 16 deletions(-) diff --git a/converter.js b/converter.js index b58185c..ddb3281 100644 --- a/converter.js +++ b/converter.js @@ -217,35 +217,82 @@ class PG2DMConverter { */ removeEmptyPartition(sql) { let converted = sql; + let removedCount = 0; - // 移除空的PARTITION BY子句 - // 格式1: )\nPARTITION BY (\n)\n; - // 格式2: ) PARTITION BY (); + // 1. 移除 PARTITION BY LIST (column) 后面没有具体分区的情况 + // 格式: )\nPARTITION BY LIST (\n "column"\n)\n; + const listPattern = /\)\s*PARTITION\s+BY\s+LIST\s*\([^)]+\)\s*;/gi; + const listMatches = converted.match(listPattern); + if (listMatches) { + converted = converted.replace(listPattern, ');'); + removedCount += listMatches.length; + this.log(`移除 ${listMatches.length} 个空的PARTITION BY LIST子句`); + } + + // 2. 移除 PARTITION BY RANGE (column) 后面没有具体分区的情况 + const rangePattern = /\)\s*PARTITION\s+BY\s+RANGE\s*\([^)]+\)\s*;/gi; + const rangeMatches = converted.match(rangePattern); + if (rangeMatches) { + converted = converted.replace(rangePattern, ');'); + removedCount += rangeMatches.length; + this.log(`移除 ${rangeMatches.length} 个空的PARTITION BY RANGE子句`); + } + + // 3. 移除 PARTITION BY HASH (column) 后面没有具体分区的情况 + const hashPattern = /\)\s*PARTITION\s+BY\s+HASH\s*\([^)]+\)\s*;/gi; + const hashMatches = converted.match(hashPattern); + if (hashMatches) { + converted = converted.replace(hashPattern, ');'); + removedCount += hashMatches.length; + this.log(`移除 ${hashMatches.length} 个空的PARTITION BY HASH子句`); + } + + // 4. 移除空括号的PARTITION BY converted = converted.replace(/\)\s*PARTITION\s+BY\s+\([^)]*\)\s*;/gi, ');\n'); - const matches = sql.match(/PARTITION\s+BY\s+\(/gi); - if (matches) { - this.log(`移除 ${matches.length} 个空的PARTITION BY子句`); + if (removedCount > 0) { + this.log(`总共移除 ${removedCount} 个空的PARTITION BY子句`); } return converted; } /** - * 移除索引注释(达梦不支持COMMENT ON INDEX) + * 移除所有COMMENT语句(达梦不支持COMMENT ON语法) */ removeIndexComments(sql) { let converted = sql; + let totalRemoved = 0; - // 匹配并移除 COMMENT ON INDEX 语句 - // 格式: COMMENT ON INDEX "schema"."index_name" IS '注释内容'; - const commentPattern = /COMMENT\s+ON\s+INDEX\s+"[^"]+"\."[^"]+"\s+IS\s+'[^']*'\s*;/gi; + // 1. 移除 COMMENT ON COLUMN + const columnPattern = /COMMENT\s+ON\s+COLUMN\s+"[^"]+"\."[^"]+"\."[^"]+"\s+IS\s+'[^']*'\s*;/gi; + const columnMatches = sql.match(columnPattern); + if (columnMatches) { + converted = converted.replace(columnPattern, ''); + totalRemoved += columnMatches.length; + this.log(`移除 ${columnMatches.length} 个列注释`); + } - const matches = sql.match(commentPattern); - if (matches) { - this.log(`移除 ${matches.length} 个索引注释(达梦不支持COMMENT ON INDEX语法)`); - converted = converted.replace(commentPattern, ''); - + // 2. 移除 COMMENT ON TABLE + const tablePattern = /COMMENT\s+ON\s+TABLE\s+"[^"]+"\."[^"]+"\s+IS\s+'[^']*'\s*;/gi; + const tableMatches = converted.match(tablePattern); + if (tableMatches) { + converted = converted.replace(tablePattern, ''); + totalRemoved += tableMatches.length; + this.log(`移除 ${tableMatches.length} 个表注释`); + } + + // 3. 移除 COMMENT ON INDEX + const indexPattern = /COMMENT\s+ON\s+INDEX\s+"[^"]+"\."[^"]+"\s+IS\s+'[^']*'\s*;/gi; + const indexMatches = converted.match(indexPattern); + if (indexMatches) { + converted = converted.replace(indexPattern, ''); + totalRemoved += indexMatches.length; + this.log(`移除 ${indexMatches.length} 个索引注释`); + } + + if (totalRemoved > 0) { + this.log(`总共移除 ${totalRemoved} 个COMMENT语句(达梦不支持)`); // 清理可能产生的多余空行 converted = converted.replace(/\n\n\n+/g, '\n\n'); } @@ -253,6 +300,73 @@ class PG2DMConverter { return converted; } + /** + * 移除分区附加语句(达梦不支持ATTACH PARTITION) + */ + removeAttachPartition(sql) { + let converted = sql; + + // 匹配 ALTER TABLE ... ATTACH PARTITION ... FOR VALUES ...; + const attachPattern = /ALTER\s+TABLE\s+"[^"]+"\."[^"]+"\s+ATTACH\s+PARTITION\s+"[^"]+"\."[^"]+"\s+FOR\s+VALUES[^;]*;/gi; + + const matches = sql.match(attachPattern); + if (matches) { + this.log(`移除 ${matches.length} 个ATTACH PARTITION语句(达梦不支持)`); + converted = converted.replace(attachPattern, ''); + + // 清理多余空行 + converted = converted.replace(/\n\n\n+/g, '\n\n'); + } + + return converted; + } + + /** + * 移除与主键约束同名的唯一索引 + * PostgreSQL导出时会同时包含索引和约束,但在达梦中会冲突 + */ + removeDuplicatePrimaryKeyIndexes(sql) { + let converted = sql; + let removedCount = 0; + + // 1. 提取所有主键约束的名称 + const pkConstraintPattern = /ADD\s+CONSTRAINT\s+"([^"]+)"\s+PRIMARY\s+KEY/gi; + const constraintNames = new Set(); + + let match; + while ((match = pkConstraintPattern.exec(sql)) !== null) { + constraintNames.add(match[1]); + } + + if (constraintNames.size === 0) { + return converted; + } + + // 2. 移除与这些约束同名的UNIQUE INDEX + constraintNames.forEach(constraintName => { + // 匹配: CREATE UNIQUE INDEX "constraint_name" ON ...; + const indexPattern = new RegExp( + `CREATE\\s+UNIQUE\\s+INDEX\\s+"${constraintName}"\\s+ON\\s+[^;]+;`, + 'gi' + ); + + const indexMatches = converted.match(indexPattern); + if (indexMatches) { + converted = converted.replace(indexPattern, ''); + removedCount += indexMatches.length; + this.log(`移除与主键约束同名的唯一索引: ${constraintName}`); + } + }); + + if (removedCount > 0) { + this.log(`总共移除 ${removedCount} 个与主键同名的唯一索引`); + // 清理多余空行 + converted = converted.replace(/\n\n\n+/g, '\n\n'); + } + + return converted; + } + /** * 简化索引语法 */ @@ -438,7 +552,15 @@ class PG2DMConverter { this.log('步骤11: 移除索引注释...'); converted = this.removeIndexComments(converted); - // 12. 添加转换说明 + // 12. 移除分区附加语句(达梦不支持ATTACH PARTITION) + this.log('步骤12: 移除分区附加语句...'); + converted = this.removeAttachPartition(converted); + + // 13. 移除与主键约束同名的唯一索引(避免冲突) + this.log('步骤13: 移除与主键约束同名的唯一索引...'); + converted = this.removeDuplicatePrimaryKeyIndexes(converted); + + // 14. 添加转换说明 if (config.output.addConversionComment) { converted = this.addConversionHeader(converted, originalFile); }