简介
pkg2java项目是一个专注于将PL/SQL转换为java代码的智能转换工具。系统由python编写通过antlr4框架对PL/SQL进行词法和语法解析生成对应的语法树根据语法树的节点信息对节点进行层层转换最后生成对应的java代码当然也可能包括mybatis的xml文件等以文件的形式组成java源码项目达到去O的目的实现java+mysql的方案进行无缝切换。
流程分析
1.项目启动入口
src.PkgConverter.pkg_list_convert
需要指定需要翻译的PLSQL的头部文件和PLSQL的body文件DDL文件路径以及翻译生成的代码保存路径。
2.执行处理的
处理过程主要分为4个部分翻译DDL文件翻译Head文件翻译body文件以及第三方package
2.1 翻译过程详解
1.根据文件路径读取对应文件并转为输入流inputStream
2.使用PlSqlParser对象对输入流进行读取分析通过一系列词法和语法分析调用sql_script()
将文件转为语法树Sql_scriptContext
3.语法树demo
# 原始DDL
create table t_testc(
money number(10, 2) primary key
);
# 翻译语法树
(sql_script
(unit_statement
(create_table create table
(tableview_name
(identifier
(id_expression
(regular_id t_testc)
)
)
)
(relational_table
(
(relational_property
(column_definition
(column_name
(identifier
(id_expression
(regular_id money)
)
)
)
(datatype
(native_datatype_element number)
(precision_part
( (numeric 10) , (numeric 2)
)
)
)
(inline_constraint primary key)
)
)
)
)
;
)
)
<EOF>
)
3.翻译过程
3.1 构建TreeWalker对象和对应的Listener对象
3.2 树遍历
treeWalker内部封装了语法树节点访问方式语法树由顶部开始访问对整棵树进行深度遍历代码实现上采用了递归迭代若当前的节点存在非结束不需要再处理的子节点则需要进行递归访问子节点。在节点访问过程中会调用
访问开始enter和访问结束exit的方法。在整个树遍历的过程大致逻辑如下
enter根树 > enter子树 > enter子子树 > … > exit子子树 > exit子树 > exit 根树
3.3 节点处理
treeWalker的节点访问开始方法enter其实是调用节点对象本身的enterRule方法在节点方法内部则是调用listener的匹配的方法匹配的方法名称一般是enter+节点类名-Context
如Sql_scriptContext节点就会执行对应的enterSql_script
treeWalker的节点访问结束方法exit和enter方法类似就是"字符串enter变成exit"
每一种类型的节点的定义以及其enterRule方法和exitRule方法都在PlSqlParser.py里面定义
Listener的方法除了在类本身定义之外还体现在各种src/listener下的xxxhandler.py文件中xxxhandler.py文件中被@ext_listener注解的方法都是listener的属性方法可以被调用到。
xxxhandler方法生效的条件
1.被@ext_listener注解修饰
2.确保你的xxxhandler.py被src/listener/init.py导入
整体而言,使用PlSqlParser将pgk文件解析成ParseTree语法树通过src.TreeWalker.TreeWalker#walk翻译语法树翻译的过程通过递归调用的方式先将最底层的子节点翻译后再翻译上层的节点最后翻译整棵树完成语法树翻译工作。
3.4 xxxhandler的enterRule方法分析
几乎所有节点都需要做的事情就是调用GlobalCtx的enter_ctx
方法,该方法会将当前节点封装成SingleLevelChild对象并将其加入到GlobalCtx的名为_child_code_stack的栈中。少数节点需要根据自身情况判断是否设置终止访问标志特殊的节点会做一些自定义的处理
3.5 xxxhandler的exitRule方法分析
根据处理的类型生成对应的CodeBase对象这是一个生成代码的保存类保存了和代码生成的相关信息其中code
属性就是生成代码的字符串。生成CodeBase对象之后调用GlobalCtx.exit_ctx方法该方法会判断_child_code_stack栈是否存在元素没有元素表示这是一个根节点访问结束处理调用相应的代码生成器code_gen的p_output
方法实行生成代码存在元素说明处理还没结束需要将自己生成的CodeBase对象和节点本身封装成ChildCodeInfo对象添加到_child_code_stack栈最后一个节点也即父节点的__children_code_info的属性上