init
This commit is contained in:
parent
20bff2b6cd
commit
55ff56a1a7
149
README.md
149
README.md
@ -6,22 +6,43 @@
|
|||||||
|
|
||||||
本工具通过**12个转换步骤**,解决PostgreSQL到达梦迁移过程中的**所有常见语法兼容性问题**:
|
本工具通过**12个转换步骤**,解决PostgreSQL到达梦迁移过程中的**所有常见语法兼容性问题**:
|
||||||
|
|
||||||
|
### 🚀 批量处理能力
|
||||||
|
- ✅ **智能批量转换** - 无参数自动处理input目录所有SQL文件
|
||||||
|
- ✅ **进度可视化** - 实时显示转换进度和统计信息
|
||||||
|
- ✅ **错误容错** - 单文件失败不影响其他文件转换
|
||||||
|
- ✅ **详细报告** - 批量转换完成后生成总体统计报告
|
||||||
|
|
||||||
### 1️⃣ 模式与命名空间处理
|
### 1️⃣ 模式与命名空间处理
|
||||||
- ✅ **移除`pg_catalog`模式前缀** - 达梦不识别PostgreSQL的系统模式
|
- ✅ **移除`pg_catalog`模式前缀** - 达梦不识别PostgreSQL的系统模式
|
||||||
- ✅ **移除数据类型引号** - 达梦不需要给数据类型加引号
|
- ✅ **移除数据类型引号** - 达梦不需要给数据类型加引号
|
||||||
|
|
||||||
### 2️⃣ 数据类型转换
|
### 2️⃣ 数据类型转换 (支持13种类型)
|
||||||
- ✅ **基础类型映射**
|
- ✅ **基础类型映射**
|
||||||
- `int8` → `BIGINT`
|
- `int8` → `BIGINT`
|
||||||
- `int4` → `INT`
|
- `int4` → `INT`
|
||||||
- `int2` → `SMALLINT`
|
- `int2` → `SMALLINT`
|
||||||
- `bool` → `BIT`
|
- `bool` → `BIT`
|
||||||
- `numeric` → `DECIMAL`
|
- `numeric` → `DECIMAL`
|
||||||
- ✅ **TEXT类型特殊处理** - `text` → `VARCHAR(8000)`
|
- ✅ **浮点类型支持** 🆕
|
||||||
- 关键修复:达梦的TEXT是CLOB类型,不能建索引
|
- `float8` → `DOUBLE` (双精度浮点)
|
||||||
- 解决:统一转换为VARCHAR(8000),保证可以建立索引
|
- `float4` → `REAL` (单精度浮点)
|
||||||
- ✅ **时间戳精度处理** - `timestamp(6)` → `TIMESTAMP`
|
- `float` → `REAL`
|
||||||
- 达梦不支持timestamp精度参数,自动移除
|
- ✅ **字符类型完整支持** 🆕
|
||||||
|
- `text` → `VARCHAR(8000)` (关键修复:达梦TEXT是CLOB不能建索引)
|
||||||
|
- `bpchar` → `CHAR` (定长字符串)
|
||||||
|
- `varchar` → `VARCHAR` (变长字符串)
|
||||||
|
- ✅ **时间戳类型完整支持** 🆕
|
||||||
|
- `timestamp(6)` → `TIMESTAMP` (移除精度参数)
|
||||||
|
- `timestamptz` → `TIMESTAMP` (时间戳带时区)
|
||||||
|
- 自动移除 `without time zone` / `with time zone` 子句
|
||||||
|
- ✅ **DECIMAL精度自动修正** 🆕
|
||||||
|
- 检测超过38位的精度定义
|
||||||
|
- 自动调整为达梦最大支持精度(38位)
|
||||||
|
- 保持小数位数不变
|
||||||
|
- ✅ **带括号类型引号处理** 🆕
|
||||||
|
- 正确移除 `"VARCHAR(8000)"` → `VARCHAR(8000)`
|
||||||
|
- 正确移除 `"DECIMAL(20,6)"` → `DECIMAL(20,6)`
|
||||||
|
- 正确移除 `"CHAR"` → `CHAR`
|
||||||
|
|
||||||
### 3️⃣ 自增序列转换
|
### 3️⃣ 自增序列转换
|
||||||
- ✅ **序列语法转换** - `DEFAULT nextval('seq'::regclass)` → `IDENTITY(1,1)`
|
- ✅ **序列语法转换** - `DEFAULT nextval('seq'::regclass)` → `IDENTITY(1,1)`
|
||||||
@ -51,6 +72,10 @@
|
|||||||
- 检测超过816字符限制的函数索引
|
- 检测超过816字符限制的函数索引
|
||||||
- 自动简化或发出警告
|
- 自动简化或发出警告
|
||||||
- 移除COALESCE包装,保留原始列名
|
- 移除COALESCE包装,保留原始列名
|
||||||
|
- ✅ **索引注释移除** 🆕
|
||||||
|
- 自动移除所有 `COMMENT ON INDEX` 语句
|
||||||
|
- 达梦不支持索引注释语法
|
||||||
|
- 清理多余空行保持格式整洁
|
||||||
|
|
||||||
### 7️⃣ 智能日志与报告
|
### 7️⃣ 智能日志与报告
|
||||||
- ✅ 生成详细的转换日志(JSON格式)
|
- ✅ 生成详细的转换日志(JSON格式)
|
||||||
@ -76,7 +101,29 @@ npm install
|
|||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
### 1. 单文件转换
|
### 1. 批量转换(推荐)⭐
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 无参数:自动批量转换input目录下所有.sql文件
|
||||||
|
node converter.js
|
||||||
|
|
||||||
|
# 输出示例:
|
||||||
|
# 📁 批量转换目录: ./input
|
||||||
|
# ==================================================
|
||||||
|
# 找到 5 个SQL文件
|
||||||
|
#
|
||||||
|
# [1/5] 处理: schema1.sql
|
||||||
|
# --------------------------------------------------
|
||||||
|
# ✓ 转换完成: ./output/schema1_dm.sql
|
||||||
|
# ...
|
||||||
|
# ==================================================
|
||||||
|
# 📊 批量转换完成
|
||||||
|
# ==================================================
|
||||||
|
# ✓ 成功: 5 个文件
|
||||||
|
# 📂 输出目录: ./output
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 单文件转换
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 基本用法
|
# 基本用法
|
||||||
@ -85,17 +132,17 @@ node converter.js input/your_schema.sql
|
|||||||
# 输出: output/your_schema_dm.sql
|
# 输出: output/your_schema_dm.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 指定输出文件
|
### 3. 指定输出文件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node converter.js input/schema.sql output/custom_output.sql
|
node converter.js input/schema.sql output/custom_output.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 批量转换
|
### 4. 批量转换指定目录
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 转换input目录下所有SQL文件
|
# 转换指定目录下所有SQL文件
|
||||||
node converter.js input/*.sql
|
node converter.js ./mydata
|
||||||
```
|
```
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
@ -225,6 +272,64 @@ CREATE UNIQUE INDEX idx ON table(
|
|||||||
"created_at" TIMESTAMP
|
"created_at" TIMESTAMP
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ❌ 问题9: DECIMAL精度超出范围 🆕
|
||||||
|
**错误信息**: `-6121: 数据精度超出范围`
|
||||||
|
|
||||||
|
**原因**: PostgreSQL的DECIMAL最大精度1000位,达梦只支持38位
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```sql
|
||||||
|
-- PostgreSQL (转换前)
|
||||||
|
"coefficient" DECIMAL(50,0)
|
||||||
|
|
||||||
|
-- 达梦 (转换后) - 自动调整为38位
|
||||||
|
"coefficient" DECIMAL(38,0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 问题10: 带括号的类型有引号 🆕
|
||||||
|
**错误信息**: `-3719: 非法的基类名[VARCHAR(8000)]`
|
||||||
|
|
||||||
|
**原因**: text转换为VARCHAR(8000)后,类型引号移除逻辑无法处理带括号的类型
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```sql
|
||||||
|
-- 转换中间结果 (错误)
|
||||||
|
"demand_order_no" "VARCHAR(8000)",
|
||||||
|
|
||||||
|
-- 达梦 (转换后) - 正确移除引号
|
||||||
|
"demand_order_no" VARCHAR(8000),
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 问题11: 索引注释不支持 🆕
|
||||||
|
**错误信息**: `-2007: 语法分析出错 [INDEX]附近出现错误`
|
||||||
|
|
||||||
|
**原因**: 达梦不支持 `COMMENT ON INDEX` 语法
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```sql
|
||||||
|
-- PostgreSQL (转换前)
|
||||||
|
CREATE INDEX "idx_name" ON "schema"."table" ("column" ASC);
|
||||||
|
COMMENT ON INDEX "schema"."idx_name" IS '索引注释';
|
||||||
|
|
||||||
|
-- 达梦 (转换后) - 移除索引注释
|
||||||
|
CREATE INDEX "idx_name" ON "schema"."table" ("column" ASC);
|
||||||
|
-- 注释已被自动移除
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 问题12: bpchar类型未识别 🆕
|
||||||
|
**错误信息**: `-3719: 非法的基类名[bpchar]`
|
||||||
|
|
||||||
|
**原因**: `bpchar` 是 PostgreSQL 的 blank-padded char 内部类型名,对应 CHAR 类型
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
```sql
|
||||||
|
-- PostgreSQL (转换前)
|
||||||
|
"open_alert" "bpchar",
|
||||||
|
|
||||||
|
-- 达梦 (转换后)
|
||||||
|
"open_alert" CHAR,
|
||||||
|
```
|
||||||
|
|
||||||
## 转换规则详解
|
## 转换规则详解
|
||||||
|
|
||||||
### 1. 数据类型映射
|
### 1. 数据类型映射
|
||||||
@ -236,8 +341,15 @@ CREATE UNIQUE INDEX idx ON table(
|
|||||||
| int2 | SMALLINT | 2字节整数 |
|
| int2 | SMALLINT | 2字节整数 |
|
||||||
| numeric | DECIMAL | 精确数值 |
|
| numeric | DECIMAL | 精确数值 |
|
||||||
| bool | BIT | 布尔值 |
|
| bool | BIT | 布尔值 |
|
||||||
|
| float8 | DOUBLE | 双精度浮点 🆕 |
|
||||||
|
| float4 | REAL | 单精度浮点 🆕 |
|
||||||
|
| float | REAL | 通用浮点 🆕 |
|
||||||
| text | VARCHAR(8000) | **关键**:避免CLOB不能建索引 |
|
| text | VARCHAR(8000) | **关键**:避免CLOB不能建索引 |
|
||||||
|
| bpchar | CHAR | 定长字符串 🆕 |
|
||||||
|
| varchar | VARCHAR | 变长字符串 |
|
||||||
|
| timestamptz | TIMESTAMP | 时间戳带时区 🆕 |
|
||||||
| timestamp(n) | TIMESTAMP | 移除精度参数 |
|
| timestamp(n) | TIMESTAMP | 移除精度参数 |
|
||||||
|
| DECIMAL(>38,n) | DECIMAL(38,n) | 自动修正精度 🆕 |
|
||||||
|
|
||||||
### 2. 序列转换
|
### 2. 序列转换
|
||||||
|
|
||||||
@ -456,6 +568,21 @@ const pattern = /COALESCE\s*\(\s*"?(\w+)"?\s*,\s*'[^']+'\s*\)/gi;
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
- **v1.2.0 (2025-11-15)** - 批量转换增强版 🆕
|
||||||
|
- ✅ **批量转换功能** - 支持目录级批量处理
|
||||||
|
- 无参数自动处理input目录
|
||||||
|
- 支持指定目录批量转换
|
||||||
|
- 详细进度显示和统计报告
|
||||||
|
- ✅ **完整数据类型支持** - 新增多种类型映射
|
||||||
|
- `bpchar` → `CHAR` (定长字符串)
|
||||||
|
- `float8` → `DOUBLE` (双精度浮点)
|
||||||
|
- `float4` → `REAL` (单精度浮点)
|
||||||
|
- `timestamptz` → `TIMESTAMP` (时间戳带时区)
|
||||||
|
- ✅ **DECIMAL精度自动修正** - 最大38位限制
|
||||||
|
- ✅ **带括号类型引号处理** - 修复VARCHAR(8000)等
|
||||||
|
- ✅ **移除索引注释** - COMMENT ON INDEX自动清理
|
||||||
|
- ✅ **timestamp时区子句清理** - without/with time zone
|
||||||
|
|
||||||
- **v1.0.0 (2025-11-15)** - 生产版本
|
- **v1.0.0 (2025-11-15)** - 生产版本
|
||||||
- ✅ 完整的12步转换流程
|
- ✅ 完整的12步转换流程
|
||||||
- ✅ 解决8大类常见迁移问题
|
- ✅ 解决8大类常见迁移问题
|
||||||
|
|||||||
@ -10,6 +10,7 @@ module.exports = {
|
|||||||
'int2': 'SMALLINT',
|
'int2': 'SMALLINT',
|
||||||
'numeric': 'DECIMAL',
|
'numeric': 'DECIMAL',
|
||||||
'varchar': 'VARCHAR',
|
'varchar': 'VARCHAR',
|
||||||
|
'bpchar': 'CHAR', // PostgreSQL blank-padded char
|
||||||
'timestamp': 'TIMESTAMP',
|
'timestamp': 'TIMESTAMP',
|
||||||
'timestamptz': 'TIMESTAMP', // PostgreSQL timestamp with time zone
|
'timestamptz': 'TIMESTAMP', // PostgreSQL timestamp with time zone
|
||||||
'bool': 'BIT',
|
'bool': 'BIT',
|
||||||
|
|||||||
220
converter.js
220
converter.js
@ -44,7 +44,7 @@ class PG2DMConverter {
|
|||||||
let converted = sql;
|
let converted = sql;
|
||||||
|
|
||||||
// 1. 转换基本类型(包括浮点类型和时间戳类型)
|
// 1. 转换基本类型(包括浮点类型和时间戳类型)
|
||||||
const typePattern = /\b(int8|int4|int2|numeric|bool|float8|float4|float|timestamptz|text)\b/gi;
|
const typePattern = /\b(int8|int4|int2|numeric|bool|float8|float4|float|timestamptz|text|bpchar)\b/gi;
|
||||||
|
|
||||||
converted = converted.replace(typePattern, (match) => {
|
converted = converted.replace(typePattern, (match) => {
|
||||||
const lowerMatch = match.toLowerCase();
|
const lowerMatch = match.toLowerCase();
|
||||||
@ -199,13 +199,13 @@ class PG2DMConverter {
|
|||||||
let converted = sql;
|
let converted = sql;
|
||||||
|
|
||||||
// 移除引号中的数据类型(达梦不需要给类型加引号)
|
// 移除引号中的数据类型(达梦不需要给类型加引号)
|
||||||
// 1. 先处理带括号的类型:VARCHAR(8000), DECIMAL(20,6)等
|
// 1. 先处理带括号的类型:VARCHAR(8000), DECIMAL(20,6), CHAR(10)等
|
||||||
converted = converted.replace(/\s"(VARCHAR|CHAR|DECIMAL|NUMERIC)\s*\([^)]+\)"\s/gi, ' $1 ');
|
converted = converted.replace(/\s"(VARCHAR|CHAR|DECIMAL|NUMERIC)\s*\([^)]+\)"\s/gi, ' $1 ');
|
||||||
converted = converted.replace(/\s"(VARCHAR|CHAR|DECIMAL|NUMERIC)\s*\([^)]+\)"([,\n\r])/gi, ' $1$2');
|
converted = converted.replace(/\s"(VARCHAR|CHAR|DECIMAL|NUMERIC)\s*\([^)]+\)"([,\n\r])/gi, ' $1$2');
|
||||||
|
|
||||||
// 2. 再处理简单类型
|
// 2. 再处理简单类型(包括不带长度的CHAR)
|
||||||
converted = converted.replace(/\s"(BIGINT|INT|SMALLINT|TINYINT|VARCHAR|CHAR|TEXT|DATE|TIME|TIMESTAMP|BIT|BOOLEAN|BOOL|BLOB|CLOB)"\s/gi, ' $1 ');
|
converted = converted.replace(/\s"(BIGINT|INT|SMALLINT|TINYINT|VARCHAR|CHAR|TEXT|DATE|TIME|TIMESTAMP|BIT|BOOLEAN|BOOL|BLOB|CLOB|DOUBLE|REAL)"\s/gi, ' $1 ');
|
||||||
converted = converted.replace(/\s"(BIGINT|INT|SMALLINT|TINYINT|VARCHAR|CHAR|TEXT|DATE|TIME|TIMESTAMP|BIT|BOOLEAN|BOOL|BLOB|CLOB)"([,\n\r])/gi, ' $1$2');
|
converted = converted.replace(/\s"(BIGINT|INT|SMALLINT|TINYINT|VARCHAR|CHAR|TEXT|DATE|TIME|TIMESTAMP|BIT|BOOLEAN|BOOL|BLOB|CLOB|DOUBLE|REAL)"([,\n\r])/gi, ' $1$2');
|
||||||
|
|
||||||
this.log('移除数据类型引号');
|
this.log('移除数据类型引号');
|
||||||
|
|
||||||
@ -231,6 +231,28 @@ class PG2DMConverter {
|
|||||||
return converted;
|
return converted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除索引注释(达梦不支持COMMENT ON INDEX)
|
||||||
|
*/
|
||||||
|
removeIndexComments(sql) {
|
||||||
|
let converted = sql;
|
||||||
|
|
||||||
|
// 匹配并移除 COMMENT ON INDEX 语句
|
||||||
|
// 格式: COMMENT ON INDEX "schema"."index_name" IS '注释内容';
|
||||||
|
const commentPattern = /COMMENT\s+ON\s+INDEX\s+"[^"]+"\."[^"]+"\s+IS\s+'[^']*'\s*;/gi;
|
||||||
|
|
||||||
|
const matches = sql.match(commentPattern);
|
||||||
|
if (matches) {
|
||||||
|
this.log(`移除 ${matches.length} 个索引注释(达梦不支持COMMENT ON INDEX语法)`);
|
||||||
|
converted = converted.replace(commentPattern, '');
|
||||||
|
|
||||||
|
// 清理可能产生的多余空行
|
||||||
|
converted = converted.replace(/\n\n\n+/g, '\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简化索引语法
|
* 简化索引语法
|
||||||
*/
|
*/
|
||||||
@ -412,7 +434,11 @@ class PG2DMConverter {
|
|||||||
this.log('步骤10: 处理COALESCE函数索引...');
|
this.log('步骤10: 处理COALESCE函数索引...');
|
||||||
converted = this.processCoalesceIndexes(converted);
|
converted = this.processCoalesceIndexes(converted);
|
||||||
|
|
||||||
// 11. 添加转换说明
|
// 11. 移除索引注释(达梦不支持COMMENT ON INDEX)
|
||||||
|
this.log('步骤11: 移除索引注释...');
|
||||||
|
converted = this.removeIndexComments(converted);
|
||||||
|
|
||||||
|
// 12. 添加转换说明
|
||||||
if (config.output.addConversionComment) {
|
if (config.output.addConversionComment) {
|
||||||
converted = this.addConversionHeader(converted, originalFile);
|
converted = this.addConversionHeader(converted, originalFile);
|
||||||
}
|
}
|
||||||
@ -448,45 +474,9 @@ function ensureDir(dirPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主函数
|
* 转换单个文件
|
||||||
*/
|
*/
|
||||||
function main() {
|
function convertSingleFile(inputFile, outputFile) {
|
||||||
const args = process.argv.slice(2);
|
|
||||||
|
|
||||||
if (args.length === 0) {
|
|
||||||
console.log(`
|
|
||||||
PostgreSQL到达梦数据库SQL转换器
|
|
||||||
======================================
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
node converter.js <input-file.sql> [output-file.sql]
|
|
||||||
node converter.js input/*.sql
|
|
||||||
|
|
||||||
示例:
|
|
||||||
node converter.js input/schema.sql
|
|
||||||
node converter.js input/schema.sql output/schema_dm.sql
|
|
||||||
node converter.js input/*.sql
|
|
||||||
|
|
||||||
说明:
|
|
||||||
- 如果不指定输出文件,将自动在output目录生成 *_dm.sql 文件
|
|
||||||
- 支持通配符批量处理多个文件
|
|
||||||
- 会自动生成转换日志文件 *_conversion.log.json
|
|
||||||
`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保input和output目录存在
|
|
||||||
ensureDir('./input');
|
|
||||||
ensureDir('./output');
|
|
||||||
|
|
||||||
const inputFile = args[0];
|
|
||||||
|
|
||||||
// 检查文件是否存在
|
|
||||||
if (!fs.existsSync(inputFile)) {
|
|
||||||
console.error(`错误: 文件不存在: ${inputFile}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取输入文件
|
// 读取输入文件
|
||||||
console.log(`\n读取文件: ${inputFile}`);
|
console.log(`\n读取文件: ${inputFile}`);
|
||||||
const sqlContent = fs.readFileSync(inputFile, 'utf8');
|
const sqlContent = fs.readFileSync(inputFile, 'utf8');
|
||||||
@ -496,15 +486,17 @@ PostgreSQL到达梦数据库SQL转换器
|
|||||||
const convertedSql = converter.convert(sqlContent, inputFile);
|
const convertedSql = converter.convert(sqlContent, inputFile);
|
||||||
|
|
||||||
// 确定输出文件路径
|
// 确定输出文件路径
|
||||||
const outputFile = args[1] || path.join(
|
if (!outputFile) {
|
||||||
'./output',
|
outputFile = path.join(
|
||||||
path.basename(inputFile, '.sql') + '_dm.sql'
|
'./output',
|
||||||
);
|
path.basename(inputFile, '.sql') + '_dm.sql'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 写入输出文件
|
// 写入输出文件
|
||||||
ensureDir(path.dirname(outputFile));
|
ensureDir(path.dirname(outputFile));
|
||||||
fs.writeFileSync(outputFile, convertedSql, 'utf8');
|
fs.writeFileSync(outputFile, convertedSql, 'utf8');
|
||||||
console.log(`\n✓ 转换完成,输出文件: ${outputFile}`);
|
console.log(`✓ 转换完成: ${outputFile}`);
|
||||||
|
|
||||||
// 生成日志
|
// 生成日志
|
||||||
if (config.output.generateLog) {
|
if (config.output.generateLog) {
|
||||||
@ -513,18 +505,144 @@ PostgreSQL到达梦数据库SQL转换器
|
|||||||
|
|
||||||
// 显示警告
|
// 显示警告
|
||||||
if (converter.warnings.length > 0) {
|
if (converter.warnings.length > 0) {
|
||||||
console.log('\n⚠ 警告信息:');
|
console.log('⚠ 警告信息:');
|
||||||
converter.warnings.forEach((warn, i) => {
|
converter.warnings.forEach((warn, i) => {
|
||||||
console.log(` ${i + 1}. ${warn}`);
|
console.log(` ${i + 1}. ${warn}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\n转换统计:');
|
// 显示统计
|
||||||
|
console.log('转换统计:');
|
||||||
console.log(` - 数据类型转换: ${converter.stats.dataTypes}`);
|
console.log(` - 数据类型转换: ${converter.stats.dataTypes}`);
|
||||||
console.log(` - 序列转IDENTITY: ${converter.stats.sequences}`);
|
console.log(` - 序列转IDENTITY: ${converter.stats.sequences}`);
|
||||||
console.log(` - COLLATE移除: ${converter.stats.collates}`);
|
console.log(` - COLLATE移除: ${converter.stats.collates}`);
|
||||||
console.log(` - 索引简化: ${converter.stats.indexes}`);
|
console.log(` - 索引简化: ${converter.stats.indexes}`);
|
||||||
console.log(` - COALESCE索引处理: ${converter.stats.coalesceIndexes}`);
|
console.log(` - COALESCE索引处理: ${converter.stats.coalesceIndexes}`);
|
||||||
|
|
||||||
|
return { success: true, warnings: converter.warnings.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量转换目录下所有SQL文件
|
||||||
|
*/
|
||||||
|
function batchConvert(inputDir) {
|
||||||
|
console.log(`\n📁 批量转换目录: ${inputDir}`);
|
||||||
|
console.log('='.repeat(50));
|
||||||
|
|
||||||
|
// 读取目录下所有.sql文件
|
||||||
|
const files = fs.readdirSync(inputDir)
|
||||||
|
.filter(file => file.toLowerCase().endsWith('.sql'))
|
||||||
|
.map(file => path.join(inputDir, file));
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log(`\n⚠ 目录中没有找到.sql文件: ${inputDir}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n找到 ${files.length} 个SQL文件`);
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let failCount = 0;
|
||||||
|
let totalWarnings = 0;
|
||||||
|
|
||||||
|
// 逐个转换
|
||||||
|
files.forEach((file, index) => {
|
||||||
|
try {
|
||||||
|
console.log(`\n[${index + 1}/${files.length}] 处理: ${path.basename(file)}`);
|
||||||
|
console.log('-'.repeat(50));
|
||||||
|
|
||||||
|
const result = convertSingleFile(file, null);
|
||||||
|
successCount++;
|
||||||
|
totalWarnings += result.warnings;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`✗ 转换失败: ${error.message}`);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示总结
|
||||||
|
console.log('\n' + '='.repeat(50));
|
||||||
|
console.log('📊 批量转换完成');
|
||||||
|
console.log('='.repeat(50));
|
||||||
|
console.log(`✓ 成功: ${successCount} 个文件`);
|
||||||
|
if (failCount > 0) {
|
||||||
|
console.log(`✗ 失败: ${failCount} 个文件`);
|
||||||
|
}
|
||||||
|
if (totalWarnings > 0) {
|
||||||
|
console.log(`⚠ 总警告: ${totalWarnings} 条`);
|
||||||
|
}
|
||||||
|
console.log(`📂 输出目录: ./output`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主函数
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
// 确保input和output目录存在
|
||||||
|
ensureDir('./input');
|
||||||
|
ensureDir('./output');
|
||||||
|
|
||||||
|
// 无参数:批量处理input目录
|
||||||
|
if (args.length === 0) {
|
||||||
|
if (fs.existsSync('./input')) {
|
||||||
|
batchConvert('./input');
|
||||||
|
} else {
|
||||||
|
console.log(`
|
||||||
|
PostgreSQL到达梦数据库SQL转换器
|
||||||
|
======================================
|
||||||
|
|
||||||
|
使用方法:
|
||||||
|
node converter.js # 批量转换input目录下所有.sql文件
|
||||||
|
node converter.js <input-file.sql> # 转换单个文件
|
||||||
|
node converter.js <input-dir> # 批量转换指定目录
|
||||||
|
node converter.js <input-file> <output> # 指定输出文件
|
||||||
|
|
||||||
|
示例:
|
||||||
|
node converter.js # 批量转换input/*.sql
|
||||||
|
node converter.js input/schema.sql # 转换单个文件
|
||||||
|
node converter.js ./mydata # 批量转换mydata目录
|
||||||
|
node converter.js input/schema.sql output/schema_dm.sql
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 批量模式会自动在output目录生成 *_dm.sql 文件
|
||||||
|
- 会自动生成转换日志文件 *_conversion.log.json
|
||||||
|
- 批量模式会显示详细的进度和统计信息
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputPath = args[0];
|
||||||
|
|
||||||
|
// 检查路径是否存在
|
||||||
|
if (!fs.existsSync(inputPath)) {
|
||||||
|
console.error(`✗ 错误: 路径不存在: ${inputPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是文件还是目录
|
||||||
|
const stat = fs.statSync(inputPath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// 批量转换目录
|
||||||
|
batchConvert(inputPath);
|
||||||
|
} else if (stat.isFile()) {
|
||||||
|
// 单个文件转换
|
||||||
|
const outputFile = args[1];
|
||||||
|
try {
|
||||||
|
convertSingleFile(inputPath, outputFile);
|
||||||
|
console.log('\n✓ 转换成功!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`\n✗ 转换失败: ${error.message}`);
|
||||||
|
console.error(error.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`✗ 错误: 不支持的路径类型: ${inputPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 运行主函数
|
// 运行主函数
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user