Oracle兼容框架之双parser
双parser框架主要分为两部分,包括SQL端和服务器端编程语言。
1. SQL端词法语法分离
基本做法是新增一套兼容Oracle风格的语法和词法,在开启Oracle兼容的情况下,走Oracle风格的语法分析,生成相应的语法树。
具体方法: 在src/backend/下面,新建一个oracle_parser目录,将src/backend/parser/下的scan.l和gram.y复制到该目录下,改名成ora_gram.y和ora_scan.l,添加 Oracle风格的语法和此法分析代码,同时复制keywords.c到该目录下,用来存放自己的关键字。该oracle_parser目录编译成一个动态库 libparser_oracle.so。当开启Oracle兼容的时候,配置文件ivorysql.conf被嵌入到postgresql.conf文件的末尾。配置文件ivorysql.conf中的shared_preload_libraries参数中添加“liboracle_parser”,这样当数据库启动时能够自动载入liboracle_parser动态库。
新增ora_raw_parser 函数指针,当libparser_oracle.so动态库被加载时,该动态库中的 _PG_init() 函数将 oracle_raw_parser() 函数的地址赋值给 ora_raw_parser,_PG_fini()则在兼容模式切换时负责重置 ora_raw_parser 为空。
每一个后端进程调用BackendInitialize()函数,通过后端进程所连接的端口号来设置 port→connmode。如果端口是Oracle兼容端口,则设置connmode为’o’,否则设置为’p’。
这之后PostgresMain()调用InitIvorysql(),如果port→connmode是’o’,则调用函数 SetConfigOption("ivorysql.compatible_mode", "oracle", PGC_USERSET, PGC_S_OVERRIDE); 由于这个参数设置了 assign_hook,因此当 SetConfigOption() 函数里执行assign_hook()时,相当于调用 assign_compatible_mode(),从而设置 sql_raw_parser = ora_raw_parser;
在对SQL语句进行分析时,函数pg_parse_query()→raw_parser() 通过函数指针 sql_raw_parser 调用standard parser() 或者 ora_raw_parser()。
下面的图演示了SQL语句分析时发生的事情。
2. 服务器端编程语言词法语法分离
在系统表pg_language中新增pliSQL语言。
具体实现方法: 将PG源码中的plpgsql目录,复制一份,改名为plisql,里面的文件名称,改成plisql开头,因为plpgsql是一个language,改造的plisql也是一个language,因此,plpgsql language的注册函数如plpgsql_validator, plpgsql_call_handler, plpgsql_inline_handler都需要改成plisql开头,里面其他函数的改名都改成以plisql开头。
plisql目录构建为一个插件,initdb时如果数据库模式是Oracle,则创建这个插件。这个插件会将pliSQL语言注册到数据库的系统表中。
pliSQL端没有自己的词法规约,它依赖SQL端的词法规约,因此,针对pliSQL,强制其走兼容Oracle的词法规约,主要改造点是在 plisql_scanner_init 函数中,里面应该调用ora_scanner_init()函数,plisql目录中的 internal_yylex() 函数调用 ora_core_yylex。
pliSQL语法规则在plisql/src/pl_gram.y中,兼容Oracle plSQL块的语法规则都在这个模块中实现。
SQL端创建函数,没有指定language的时候,如果当前是兼容Oracle模式,则默认language是plisql,如果是兼容pg模式,则默认language是plpgsql。oracle_parser中的ora_gram.y默认是plisql,pg parser中的gram.y默认是plpgsql。
匿名块没有指定language的时候,如果是兼容Oracle模式,默认plisql,如果是兼容pg模式,则默认plpgsql。
处理过程的ExecuteDoStmt函数同样根据兼容模式来决定默认language。