Skip to content

Latest commit

 

History

History
251 lines (227 loc) · 14.7 KB

File metadata and controls

251 lines (227 loc) · 14.7 KB

说明

本项目使用了antlr4进行开发,针对ORACLE中部分SQL改写为GaussDB支持的SQL。
项目使用 Maven 构建(也支持离线构建),依赖 JDK 11 (11.0.24) 或以上版本。

已支持的改写规则

  • 对于不对称多行注释 ,将中间多余的/*改成*
  • 对于table($1)函数,替换为(select * from unnest_table($1) column_value)。注意array类型不支持使用unnest_table,openGauss也不支持,如需替换成unnest,修改config.properties中的tablefunctionreplace参数
  • 对于NEW构造器,去掉NEW关键字,并检测构造函数是否带括号,如无括号则补上
  • 去掉宏编译代码 (非等价改写,必然执行IF内的命令,且ELSE里的也会执行,检测到此类语法时会打印warning,需人工确认是否符合预期)
  • select列表分隔符、 函数传参列表分隔符 、where in列表、from列表、order by列表、group by 列表 、insert into目标列的列表、values 列表 的全角逗号(uFF0C)转换为半角逗号
  • 全角空格(u3000)转换为半角空格
  • 关键字作为字段别名但没带AS时,自动补上AS,支持的关键字列表见.\src\main\java\PlSqlParser.g4:non_reserved_keywords_pre12c/non_reserved_keywords_in_12c/non_reserved_keywords_in_18c/non_reserved_keywords_in_gaussdb,支持case when 表达式的别名为关键字
  • 移除重复的declare变量,保留最后一个 - 对于laglead函数,当第三个参数为数字,第一个参数没有除法时,第3个参数加.0;有除法时,第3个参数加 /1
  • 对于laglead函数,当第三个参数为数字时,把第三个参数加上单引号
  • 移除函数、过程参数中的nocopy
  • select unique($1)转成select distinct($1)
  • 改写 a [not] member of b[not](a=any(b))
  • 对于having子句在group by前面的,把having子句 放到group by子句后面
  • result_cache [relies_on] 替换成immutable
  • deteministic 换成 immutable
  • 按名称传参时,=>紧跟注释或+-时,=>后面加空格
  • 处理异常名称替换,需维护配置文件exception_mapping.properties,支持配置自定义异常,已预置invalid_numbervalue_errordup_val_on_index
  • 替换raise_application_errorreport_application_error,并交换两个参数的顺序 (report_application_error在openGauss被屏蔽使用)
  • 支持函数调用的替换,需维护配置文件general_element.propertes,已预置SYS_CONTEXT('USERENV','SESSIONID')->pg_current_sessid(),SYS_CONTEXT('USERENV','IP_ADDRESS')->inet_out(inet_client_addr()),SYS_CONTEXT('USERENV','INSTANCE_NAME')->current_setting('pgxc_node_name')
  • 对于join (tablename) 的用法,把表名两边的括号换成空格
  • .getclobval().getclobval 替换成 ::clob
  • 移除xmlparse函数内的wellformed(非等价改写,如果字段里有xml保留符号会报错)
  • 支持识别merge into ... using table() on进行改写
  • 支持基于token的全词替换,比如可配置高级包和系统视图的名称替换,配置文件为token_mappig.properties (不考虑语法,仅考虑词法,配置需慎重)
  • 将 call_statement 中的 dbms_obfuscation_toolkit.md5(input=> v1,checksum=>v2)替换成 v2:=hextoraw(md5(dbe_raw.cast_to_varchar2(v1)))
  • 将 expression 中的 dbms_obfuscation_toolkit.md5(input=>v1)替换成 hextoraw(md5(dbe_raw.cast_to_varchar2(v1)))
  • 支持自定义正则表达式替换,在patterns.yaml文件中配置规则, 该规则先于其他所有规则执行
  • grouping_id函数 改成grouping
  • to_date() -to_date() 改成 intervaltonum(to_date()-to_date())
  • 目标库保留关键字作为字段名或变量名,支持配置映射文件修改,参数文件regular_id_mapping.properties,针对所有regular_id节点,已配置authid
  • 表名替换(含系统视图名称),参数文件regular_id_mapping.properties
  • 支持自定义数据类型映射,包括函数参数类型,函数返回类型,包、函数、匿名块声明的变量、常量类型,is table of的类型, index by的类型,cast函数内的类型,参数文件datatype_mapping.properties,预置了pls_integerchar
  • 处理数据字典(user_col_comments/user_tables/user_objects/user_types/all_tab_columns/all_objects)名称替换参数文件regular_id_mapping.properties
  • 指定字段加函数进行转换(比如数据字典字段转大写)参数文件atom_mapping.properties
  • 如果要转换owner字段名为别的字段名或表达式,配置config.properties中的ownerconvert参数为期望替换成为的值(注意gaussdb的兼容系统视图可能会少列)
  • 处理for update of [tablename.|aliasname.]{column_name|aliasname},如果有表名,替换成表名;如果没有表名,从select列表中找一个匹配的;如果from后只有一个表,则取这个表名 ,支持多个字段的转换
  • 去掉声明游标和打开游标中select into(实际ORACLE中并不会执行游标中的into,去掉的时候会弹个warning)
  • 支持指定的查询语句进行语句替换,参数文件query_block_mapping.properties,key需要去掉所有空格和换行,=号需要转义,value需要转义特殊符号(包括空格和换行)
  • 重复的表别名,暂无法自动改写,识别到会弹出警告Duplicate table aliases found,并且输出该查询块的hashCode值及该查询块的原始文本,可结合基于hashCode替换的功能进行自定义的整段替换 建议辅助人工改造
  • 支持基于查询块hashCode值的整段替换,替换后的SQL以hashCode的值作为文件名放在config/query_block_hash_map目录下
  • regexp_substr函数中的第二个参数中如果包含 {} 且没有转义,且里面的格式不满足m,m,,m,n,则对{}进行转义,同时会输出warning信息。
  • 支持识别并转换全角括号成半角括号
  • 支持指定输入和输出的字符集
  • replace函数指定3个参数名称传参的,改成按定义的参数顺序传递,去掉参数名称
  • 对于存在于udtlist.properties中的自定义类型,修改select语句中type_name(c1,...)形式的表达式为cast((c1,...) as type_name)
  • 对于两表left joinright join时,连接条件带了多余的(+)的,去掉(+)
  • 支持对于execute immediate里的动态SQL指定正则替换或移除规则,规则文件dynamic_sql.yaml
  • q转义的字符串改写成不需要q转义
  • 支持对于声明的char/varchar2变量或常量默认值为常量字符串时,检测声明的长度是否足够,如果不足则增加到默认值的字节长度(长度计算基于输出目标的字符集)
  • update/insert/delete子句中的return替换成returning

构建和测试

mvn构建项目

# mvn clean generate-sources # 生成解析器 修改g4文件后需再次执行)
# 全量构建且自动测试
mvn clean package

# 快速构建跳过测试
mvn package -DskipTests

离线构建(不需要mvn,但需要根据lib/download_url.txt下载依赖库放到lib目录下)

windows

# regen_parser.bat #生成解析器(修改g4文件后需再次执行)
build_offline.bat

Linux (or git-bash)

# ./regen_parser.sh #生成解析器(修改g4文件后需再次执行)
./build_offline.sh

运行测试

# 运行所有测试
mvn test

# 运行特定测试
mvn test -Dtest=com.plsqlrewriter.core.PlSqlRewriterTest#testBasicConversion

# windows手动测试 
## 单文件
convert.bat src\test\resources\sql_tests\input\fulltest.sql src\test\resources\sql_tests\output\fulltest.sql
## 批量
batch_convert.bat src\test\resources\sql_tests\input src\test\resources\sql_tests\output
## 比较output目录内所有文件是否和expected一致
src\test\resources\sql_tests\compare.bat 

# linux手动测试 
## 单文件
./convert.sh src/test/resources/sql_tests/input/fulltest.sql src/test/resources/sql_tests/output/fulltest.sql
## 批量
./batch_convert.sh src/test/resources/sql_tests/input src/test/resources/sql_tests/output
## 比较output目录内所有文件是否和expected一致
src/test/resources/sql_tests/compare.sh 

添加新的测试用例到mvn自动测试

src/test/resources/sql_tests 目录下的inputexpected目录下创建配对的文件:

  • input/sample.sql:输入文件
  • expected/sample.sql:期望输出文件

使用方式

  1. 单个文件转换:
# 使用默认 UTF-8 编码
java -cp target/plsql-rewriter-1.0-SNAPSHOT-jar-with-dependencies.jar com.plsqlrewriter.core.PlSqlRewriter input.sql output.sql

# 指定编码
java -cp target/plsql-rewriter-1.0-SNAPSHOT-jar-with-dependencies.jar com.plsqlrewriter.core.PlSqlRewriter input.sql output.sql GBK UTF-8

# 使用便捷脚本
./convert.sh input.sql output.sql GBK UTF-8  # Linux/Mac
convert.bat input.sql output.sql GBK UTF-8   # Windows
  1. 批量转换:
# 使用默认 UTF-8 编码,10个并发
java -jar target/plsql-rewriter-1.0-SNAPSHOT-jar-with-dependencies.jar com.plsqlrewriter.core.BatchProcessor input_dir output_dir 10

# 指定编码
java -jar target/plsql-rewriter-1.0-SNAPSHOT-jar-with-dependencies.jar com.plsqlrewriter.core.BatchProcessor input_dir output_dir 10 GBK UTF-8

# 使用便捷脚本
./batch_convert.sh input_dir output_dir 10 GBK UTF-8  # Linux/Mac
batch_convert.bat input_dir output_dir 10 GBK UTF-8   # Windows
  1. 查看语法树:
# Linux/Mac
./grun_tree.sh test.sql                 # 显示语法树
./grun_tree.sh -tokens test.sql         # 显示词法标记
./grun_tree.sh -gui test.sql            # 图形化显示语法树
./grun_tree.sh -encoding UTF-8 test.sql # 指定编码

# Windows
grun_tree.bat test.sql
grun_tree.bat -tokens test.sql
grun_tree.bat -gui test.sql
grun_tree.bat -encoding UTF-8 test.sql

项目目录结构

PlsqlRewrite4GaussDB
├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─plsqlrewriter
│  │  │          ├─core
│  │  │          │  └─PlSqlRewriter.java   # 主程序源码
│  │  │          ├─parser
│  │  │          │  └─antlr
│  │  │          │      ├─PlSqlLexerBase.java  # 词法基类
│  │  │          │      └─PlSqlParserBase.java # 语法基类
│  │  │          └─util
│  │  │              ├─BatchProcessor.java  # 批量处理器
│  │  │              ├─PlSqlTreeViewer.java # 语法树查看器
│  │  │              └─CustomErrorListener.java # 定制错误监听器
│  │  └─resources
│  │      ├─antlr4                # ANTLR4语法文件目录
│  │      |   ├─PlSqlLexer.g4   # 词法解析规则
│  │      |   └─PlSqlParser.g4  # 语法解析规则
│  │      └─logback.xml # 日志配置
│  │
│  └─test
│      ├─java
│      │  └─com
│      │      └─plsqlrewriter
│      │          └─core
│      │              └─PlSqlRewriterTest.java  # 单元测试
│      │
│      └─resources
│          └─sql_tests              # 测试用例
│               ├─input                     # 测试输入文件
│               ├─output                    # 测试输出文件
│               ├─expected                  # 测试期望结果
│               ├─compare.bat              # Windows比较脚本
│               └─compare.sh               # Linux比较脚本
│
├─config                          # 配置文件目录
│  ├─query_block_hash_map        # 基于哈希值的查询块替换配置
│  ├─atom_mapping.properties      # 字段函数转换配置
│  ├─config.properties           # 通用配置
│  ├─datatype_mapping.properties # 数据类型映射
│  ├─exception_mapping.properties # 异常名称映射
│  ├─general_element_mapping.properties # 函数表达式映射
│  ├─patterns.yaml              # 正则替换规则
│  ├─patterns.properties        # 正则替换规则(旧版,已废弃)
│  ├─query_block_mapping.properties # 查询块规则
│  ├─regular_id_mapping.properties # 标识符映射
│  ├─token_mapping.properties    # 词法标记映射
│  └─udtlist.properties         # 自定义类型列表
│
├─lib                           # 离线构建依赖库
│
├─target                        # 编译输出目录
│  ├─classes                   # 编译后的类文件
│  ├─generated-sources         # 生成的源代码
│  │  └─antlr4                # ANTLR4生成的代码
│  ├─test-classes             # 编译后的测试类文件
│  └─*.jar                    # 打包的JAR文件
│
├─batch_convert.bat            # Windows批量转换脚本
├─batch_convert.sh            # Linux批量转换脚本
├─build_offline.bat           # Windows离线构建脚本
├─build_offline.sh           # Linux离线构建脚本
├─convert.bat                # Windows单文件转换脚本
├─convert.sh                # Linux单文件转换脚本
├─grun_tree.bat             # Windows语法树查看脚本
├─grun_tree.sh             # Linux语法树查看脚本
├─regen_parser.bat         # Windows重新生成解析器脚本
├─regen_parser.sh         # Linux重新生成解析器脚本
├─pom.xml                 # Maven项目配置
└─README.md               # 项目说明文档

配置文件说明

所有配置文件位于 config 目录下:

  1. atom_mapping.properties: 指定字段使用函数转换
  2. config.properties: 通用配置参数
  3. datatype_mapping.properties: 数据类型映射关系
  4. exception_mapping.properties: 异常名称映射
  5. general_element_mapping.properties: 函数表达式映射
  6. patterns.yaml: 正则表达式替换规则
  7. query_block_mapping.properties: 查询块替换规则
  8. regular_id_mapping.properties: 标识符映射关系
  9. token_mapping.properties: 词法标记映射
  10. udtlist.properties: 自定义类型列表

遗留问题

  • SQLCODE数据类型不一致,暂无法自动处理,识别到就弹个warning,建议人工改造(可配置general_element_mapping,创建自定义函数转换成数值)
  • dbms_utility的堆栈函数信息无法嵌套传递,暂无法自动处理,识别到就弹个warning,建议人工改造
  • forall var.first .. var.last 改成 forall 1 .. var.count,原因是gaussdb的var.first在var未初始化时会报错 (当var是索引表类型时,该方式非等价改写,只能对不带index by的类型改写,建议人工改造)
  • cursorname.columnname.objectattname ,多层引用需要改成通过变量中转(暂无法自动改写,也很难识别)
  • select cast(mutilset as typename) into val 改写成select bulk collect into (需要知道into变量类型的元素类型)