init
This commit is contained in:
parent
8f9e16df7b
commit
a5153438a6
12
config.js
12
config.js
@ -13,7 +13,10 @@ module.exports = {
|
|||||||
'timestamp': 'TIMESTAMP',
|
'timestamp': 'TIMESTAMP',
|
||||||
'bool': 'BIT',
|
'bool': 'BIT',
|
||||||
'text': 'TEXT',
|
'text': 'TEXT',
|
||||||
'bytea': 'BLOB'
|
'bytea': 'BLOB',
|
||||||
|
'float8': 'DOUBLE', // PostgreSQL double precision
|
||||||
|
'float4': 'REAL', // PostgreSQL real/float
|
||||||
|
'float': 'REAL' // 通用float
|
||||||
},
|
},
|
||||||
|
|
||||||
// 序列DEFAULT值转换规则
|
// 序列DEFAULT值转换规则
|
||||||
@ -49,5 +52,12 @@ module.exports = {
|
|||||||
addConversionComment: true, // 添加转换说明注释
|
addConversionComment: true, // 添加转换说明注释
|
||||||
generateLog: true, // 生成转换日志
|
generateLog: true, // 生成转换日志
|
||||||
warningOnComplexIndex: true // 复杂索引发出警告
|
warningOnComplexIndex: true // 复杂索引发出警告
|
||||||
|
},
|
||||||
|
|
||||||
|
// SQL优化配置
|
||||||
|
optimization: {
|
||||||
|
removeComments: false, // 保留COMMENT语句(设为true可移除以加速执行)
|
||||||
|
addTransaction: true, // 添加事务包装
|
||||||
|
batchSize: 100 // 批量提交大小(每N个DDL语句提交一次)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
216
converter.js
216
converter.js
@ -43,8 +43,8 @@ class PG2DMConverter {
|
|||||||
convertDataTypes(sql) {
|
convertDataTypes(sql) {
|
||||||
let converted = sql;
|
let converted = sql;
|
||||||
|
|
||||||
// 1. 转换基本类型
|
// 1. 转换基本类型(包括浮点类型)
|
||||||
const typePattern = /\b(int8|int4|int2|numeric|bool)\b/gi;
|
const typePattern = /\b(int8|int4|int2|numeric|bool|float8|float4|float)\b/gi;
|
||||||
|
|
||||||
converted = converted.replace(typePattern, (match) => {
|
converted = converted.replace(typePattern, (match) => {
|
||||||
const lowerMatch = match.toLowerCase();
|
const lowerMatch = match.toLowerCase();
|
||||||
@ -275,6 +275,208 @@ class PG2DMConverter {
|
|||||||
return converted;
|
return converted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换PostgreSQL视图中的LATERAL VALUES为达梦UNPIVOT
|
||||||
|
*/
|
||||||
|
convertLateralUnpivotViews(sql) {
|
||||||
|
let converted = sql;
|
||||||
|
|
||||||
|
// 移除类型转换(先做,避免干扰后续匹配)
|
||||||
|
converted = converted.replace(/::(text|character\s+varying|varchar|integer|bigint)/gi, '');
|
||||||
|
|
||||||
|
// 匹配完整的LATERAL JOIN模式(修复:使用更精确的匹配避免遗漏列)
|
||||||
|
// 格式: LEFT JOIN LATERAL ( VALUES (...), (...), ... ) s0(...) ON (true)
|
||||||
|
const lateralPattern = /LEFT\s+JOIN\s+LATERAL\s*\(\s*VALUES\s+([\s\S]+?)\s*\)\s*(\w+)\s*\(([^)]+)\)\s+ON\s+\([^)]*true[^)]*\)/gi;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
let unpivotCount = 0;
|
||||||
|
|
||||||
|
while ((match = lateralPattern.exec(converted)) !== null) {
|
||||||
|
const [fullMatch, valuesContent, alias, aliasColumns] = match;
|
||||||
|
unpivotCount++;
|
||||||
|
|
||||||
|
this.log(`检测到LATERAL VALUES UNPIVOT模式 #${unpivotCount}`);
|
||||||
|
|
||||||
|
// 解析VALUES内容,提取所有列名
|
||||||
|
// 格式: ('bk_qty_001', t.bk_qty_001), ('bk_qty_002', t.bk_qty_002), ...
|
||||||
|
const valuePattern = /\(\s*'([^']+)'\s*,\s*t\.(\w+)\s*\)/g;
|
||||||
|
const columns = [];
|
||||||
|
let valueMatch;
|
||||||
|
|
||||||
|
while ((valueMatch = valuePattern.exec(valuesContent)) !== null) {
|
||||||
|
const columnName = valueMatch[2];
|
||||||
|
columns.push(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.length === 0) {
|
||||||
|
this.warn('无法解析VALUES内容,跳过该视图转换');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(`提取到 ${columns.length} 个列用于UNPIVOT转换`);
|
||||||
|
|
||||||
|
// 解析别名列名
|
||||||
|
const aliasCols = aliasColumns.split(',').map(c => c.trim());
|
||||||
|
const fieldNameCol = aliasCols[0] || 'bucket_field';
|
||||||
|
const fieldValueCol = aliasCols[1] || 'bucket_qty';
|
||||||
|
|
||||||
|
// 生成UNPIVOT子句(不包含LEFT JOIN,因为UNPIVOT本身不需要JOIN)
|
||||||
|
const unpivotClause = `
|
||||||
|
UNPIVOT (
|
||||||
|
${fieldValueCol} FOR ${fieldNameCol} IN (
|
||||||
|
${columns.join(', ')}
|
||||||
|
)
|
||||||
|
) ${alias}`;
|
||||||
|
|
||||||
|
// 替换:移除整个LEFT JOIN LATERAL ... ON (true),替换为UNPIVOT
|
||||||
|
// 注意:不包含JOIN关键字,因为UNPIVOT直接跟在表后面
|
||||||
|
const replacement = unpivotClause + '\n ';
|
||||||
|
converted = converted.replace(fullMatch, replacement);
|
||||||
|
|
||||||
|
this.log(`✓ 已转换为达梦UNPIVOT语法`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unpivotCount > 0) {
|
||||||
|
this.log(`共转换 ${unpivotCount} 个LATERAL VALUES为UNPIVOT`);
|
||||||
|
|
||||||
|
// 清理括号 - 完全重构
|
||||||
|
// 1. FROM ((table -> FROM (table
|
||||||
|
converted = converted.replace(/FROM\s+\(\(/gi, 'FROM (');
|
||||||
|
|
||||||
|
// 2. 移除UNPIVOT后的多余右括号
|
||||||
|
converted = converted.replace(/(\)\s+s\d+)\s*\n\s*\)\s*\n\s*(JOIN)/gi, '$1\n $2');
|
||||||
|
|
||||||
|
// 3. 清理ON条件 - 完全重写为简单形式
|
||||||
|
// ON ((((col = col) AND (col = col) ...))) -> ON (col = col AND col = col ...)
|
||||||
|
const onPattern = /JOIN\s+([^\s]+)\s+ON\s+\(+([^W]+?)\)+\s*WHERE/gi;
|
||||||
|
converted = converted.replace(onPattern, (match, tableName, conditions) => {
|
||||||
|
// 移除所有括号和多余空格
|
||||||
|
let cleanConditions = conditions
|
||||||
|
.replace(/\(/g, '')
|
||||||
|
.replace(/\)/g, '')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
return `JOIN ${tableName} ON (${cleanConditions})\n WHERE`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内联COMMENT到CREATE TABLE语句中
|
||||||
|
*/
|
||||||
|
inlineComments(sql) {
|
||||||
|
let converted = sql;
|
||||||
|
|
||||||
|
// 第一步:提取所有COMMENT映射
|
||||||
|
const commentMap = new Map();
|
||||||
|
const tableCommentMap = new Map();
|
||||||
|
|
||||||
|
// 提取列注释:COMMENT ON COLUMN "schema"."table"."column" IS 'comment';
|
||||||
|
const columnCommentPattern = /COMMENT\s+ON\s+COLUMN\s+"([^"]+)"\."([^"]+)"\."([^"]+)"\s+IS\s+'([^']+)';/gi;
|
||||||
|
let match;
|
||||||
|
while ((match = columnCommentPattern.exec(sql)) !== null) {
|
||||||
|
const [, schema, table, column, comment] = match;
|
||||||
|
const key = `${schema}.${table}.${column}`;
|
||||||
|
commentMap.set(key, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取表注释:COMMENT ON TABLE "schema"."table" IS 'comment';
|
||||||
|
const tableCommentPattern = /COMMENT\s+ON\s+TABLE\s+"([^"]+)"\."([^"]+)"\s+IS\s+'([^']+)';/gi;
|
||||||
|
while ((match = tableCommentPattern.exec(sql)) !== null) {
|
||||||
|
const [, schema, table, comment] = match;
|
||||||
|
const key = `${schema}.${table}`;
|
||||||
|
tableCommentMap.set(key, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(`提取到 ${commentMap.size} 个列注释和 ${tableCommentMap.size} 个表注释`);
|
||||||
|
|
||||||
|
// 第二步:将注释内联到CREATE TABLE中
|
||||||
|
const createTablePattern = /CREATE\s+TABLE\s+"([^"]+)"\."([^"]+)"\s*\(([\s\S]*?)\)\s*;/gi;
|
||||||
|
|
||||||
|
converted = converted.replace(createTablePattern, (tableMatch, schema, table, columnsDef) => {
|
||||||
|
// 处理每一列
|
||||||
|
const lines = columnsDef.split('\n');
|
||||||
|
const newLines = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
const trimmedLine = line.trim();
|
||||||
|
|
||||||
|
// 跳过空行
|
||||||
|
if (!trimmedLine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配列定义: "column_name" TYPE ...
|
||||||
|
const colMatch = line.match(/^\s*"([^"]+)"\s+(.+?)$/);
|
||||||
|
if (colMatch) {
|
||||||
|
const [, columnName, rest] = colMatch;
|
||||||
|
const key = `${schema}.${table}.${columnName}`;
|
||||||
|
const comment = commentMap.get(key);
|
||||||
|
|
||||||
|
// 检查是否是最后一个非空列定义
|
||||||
|
let isLastColumn = true;
|
||||||
|
for (let j = i + 1; j < lines.length; j++) {
|
||||||
|
if (lines[j].trim() && lines[j].trim().match(/^\s*"/)) {
|
||||||
|
isLastColumn = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除原有的行尾逗号
|
||||||
|
let restCleaned = rest.trim();
|
||||||
|
if (restCleaned.endsWith(',')) {
|
||||||
|
restCleaned = restCleaned.slice(0, -1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建新行
|
||||||
|
let newLine = ` "${columnName}" ${restCleaned}`;
|
||||||
|
|
||||||
|
// 添加COMMENT(如果有)
|
||||||
|
if (comment) {
|
||||||
|
newLine += ` COMMENT '${comment}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加逗号(除了最后一列)
|
||||||
|
if (!isLastColumn) {
|
||||||
|
newLine += ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
newLines.push(newLine);
|
||||||
|
} else {
|
||||||
|
// 保留非列定义行(如空行、注释等)
|
||||||
|
if (trimmedLine) {
|
||||||
|
newLines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加表注释
|
||||||
|
const tableKey = `${schema}.${table}`;
|
||||||
|
const tableComment = tableCommentMap.get(tableKey);
|
||||||
|
let result = `CREATE TABLE "${schema}"."${table}" (\n${newLines.join('\n')}\n)`;
|
||||||
|
|
||||||
|
if (tableComment) {
|
||||||
|
result += ` COMMENT '${tableComment}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result + ';';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第三步:移除所有独立的COMMENT语句
|
||||||
|
converted = converted.replace(/COMMENT\s+ON\s+(COLUMN|TABLE)\s+[^;]+;/gi, '');
|
||||||
|
|
||||||
|
// 第四步:清理多余空行(3个或以上连续空行变成2个)
|
||||||
|
converted = converted.replace(/\n{4,}/g, '\n\n\n');
|
||||||
|
|
||||||
|
this.log(`COMMENT已内联到CREATE TABLE,移除了 ${commentMap.size + tableCommentMap.size} 条独立COMMENT语句`);
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理COALESCE函数索引
|
* 处理COALESCE函数索引
|
||||||
*/
|
*/
|
||||||
@ -394,7 +596,15 @@ class PG2DMConverter {
|
|||||||
this.log('步骤10: 处理COALESCE函数索引...');
|
this.log('步骤10: 处理COALESCE函数索引...');
|
||||||
converted = this.processCoalesceIndexes(converted);
|
converted = this.processCoalesceIndexes(converted);
|
||||||
|
|
||||||
// 11. 添加转换说明
|
// 11. 转换LATERAL视图语法
|
||||||
|
this.log('步骤11: 转换LATERAL视图语法...');
|
||||||
|
converted = this.convertLateralUnpivotViews(converted);
|
||||||
|
|
||||||
|
// 12. 内联COMMENT到CREATE TABLE
|
||||||
|
this.log('步骤12: 内联COMMENT到CREATE TABLE...');
|
||||||
|
converted = this.inlineComments(converted);
|
||||||
|
|
||||||
|
// 13. 添加转换说明
|
||||||
if (config.output.addConversionComment) {
|
if (config.output.addConversionComment) {
|
||||||
converted = this.addConversionHeader(converted, originalFile);
|
converted = this.addConversionHeader(converted, originalFile);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user