ivorysql oracle兼容框架

ivorysql采用双端口的方式实现oracle兼容,默认的5432端口为pg语法,1532用来支持oracle的语法与功能,从官方的流程图上看是支持oracle应用连接的,但是貌似没有支持oracle的协议,以下为支持oracle兼容模式的提交的commit

实现双端口主要需要实现
- postmaster进程新增监听的端口1521(或其他可设置的端口号)
- 1521监听到的连接对应为支持oracle兼容的连接
- 添加参数控制隔离默认的pg行为
- 新增参数
- 新增系统函数
postmaster进程新增监听的端口
c
void
PostmasterMain(int argc, char *argv[]){
//...
//创建oracle tcp/ip socket, database_mode用于隔离pg默认行为
if (DB_ORACLE == database_mode && OraListenAddresses)
{
char *rawstring;
List *elemlist;
ListCell *l;
int success = 0;
/* Need a modifiable copy of ListenAddresses */
rawstring = pstrdup(OraListenAddresses);
/* Parse string into list of hostnames */
if (!SplitGUCList(rawstring, ',', &elemlist))
{
/* syntax error in list */
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid list syntax in parameter \"%s\"",
"listen_addresses")));
}
foreach(l, elemlist)
{
char *curhost = (char *) lfirst(l);
if (strcmp(curhost, "*") == 0)
status = StreamServerPort(AF_UNSPEC, NULL,
(unsigned short) OraPortNumber,
NULL,
ListenSocket, MAXLISTEN);
else
status = StreamServerPort(AF_UNSPEC, curhost,
(unsigned short) OraPortNumber,
NULL,
ListenSocket, MAXLISTEN);
if (status == STATUS_OK)
{
success++;
/* record the first successful host addr in lockfile */
if (!listen_addr_saved)
{
AddToDataDirLockFile(LOCK_FILE_LINE_LISTEN_ADDR, curhost);
listen_addr_saved = true;
}
}
else
ereport(WARNING,
(errmsg("could not create listen socket for \"%s\"",
curhost)));
}
if (!success && elemlist != NIL)
ereport(FATAL,
(errmsg("could not create any Oracle TCP/IP sockets")));
list_free(elemlist);
pfree(rawstring);
}
//...
//创建oracle本地Unix Domain Socket
if (DB_ORACLE == database_mode)
{
success = 0;
foreach(l, elemlist)
{
char *socketdir = (char *) lfirst(l);
status = StreamServerPort(AF_UNIX, NULL,
(unsigned short) OraPortNumber,
socketdir,
ListenSocket, MAXLISTEN);
if (status == STATUS_OK)
{
success++;
/* record the first successful Unix socket in lockfile */
if (success == 1)
AddToDataDirLockFile(LOCK_FILE_LINE_SOCKET_DIR, socketdir);
}
else
ereport(WARNING,
(errmsg("could not create Oracle Unix-domain socket in directory \"%s\"",
socketdir)));
}
if (!success && elemlist != NIL)
ereport(FATAL,
(errmsg("could not create any Oracle Unix-domain sockets")));
}
//...
}
新增端口监听到的连接对应为支持oracle兼容的连接
c
//Port新增字段connmode,标识当前连接是什么兼容模式
typedef struct Port
{
pgsocket sock; /* File descriptor */
//...
/*
* Connection Mode.
*
* Use single-byte characters to identify the mode information of the
* current connection. The valid characters are as follows:
* 'p' = PostgreSQL mode,
* 'o' = Oracle mode,
* 'u' = Unkonw mode, shouldn't happen.
*/
char connmode;
} Port;
//connmode字段赋值
PostmasterMain
ServerLoop
BackendStartup
BackendInitialize //子进程自己赋值connmode字段
BackendInitialize(Port *port){
//...
//对比localport是否为oracle端口号,是就给port->connmode赋值'o'
service[0] = '\0';
if ((ret = pg_getnameinfo_all(&port->laddr.addr, port->laddr.salen,
NULL, 0,
service, sizeof(service),
NI_NUMERICSERV)) != 0)
ereport(WARNING,
(errmsg_internal("pg_getnameinfo_all() failed: %s",
gai_strerror(ret))));
localport = atoi(service);
//atoi,异常情况直接用strstr对比字符串
if (localport == 0)
{
char PostPortNumberStr[32];
char OraPortNumberStr[32];
snprintf(PostPortNumberStr, sizeof(PostPortNumberStr), "%d", PostPortNumber);
snprintf(OraPortNumberStr, sizeof(OraPortNumberStr), "%d", OraPortNumber);
if (strstr(service, PostPortNumberStr) != NULL)
{
port->connmode = 'p';
}
else if (strstr(service, OraPortNumberStr) != NULL)
{
port->connmode = 'o';
}
else
port->connmode = 'u';
}
else
{
if (localport == PostPortNumber)
{
port->connmode = 'p';
}
else if (localport == OraPortNumber)
{
port->connmode = 'o';
}
else
port->connmode = 'u';
}
}
隔离原生pg的行为
数据库初始化时可指定database_mode,database_mode==oracle时监听oracle端口,用于隔离原生pg的行为
c
//initdb 新增参数-m [oracle/pg],指定时 bootstrap转为 -y oracle/pg ,默认为oracle
main
initialize_data_directory
bootstrap_template1
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -C ivorysql.identifier_case_switch=%d -X %d %s %s %s %s %s",
backend_exec,
caseswitchmode,
wal_segment_size_mb * (1024 * 1024),
pg_strcasecmp(dbmode, "pg") ? "-y oracle" : "-y pg",
data_checksums ? "-k" : "",
boot_options, extra_options,
debug ? "-d 5" : "");
//database_mode持久化
BootstrapModeMain
//Bootstrap 新增 "-y" 指定pg或者oracle,为bootstrap_database_mode
//bootstrap_database_mode = DB_ORACLE;
//bootstrap_database_mode = DB_PG;
BootStrapXLOG
ControlFile->dbmode = bootstrap_database_mode; //后续start,restart,或者crash恢复 会从controlfile中读取dbmode字段,实现双端口
WriteControlFile
LocalProcessControlFile
ReadControlFile //读取dbmode字段 并赋值ivorysql.database_mode
新增区别于原生pg的一些参数
| 参数名 | 解释 |
|---|---|
| ivorysql.port | 等同于pg端口的参数port,监听oracle兼容连接的端口号,对应内核参数OraPortNumber |
| ivorysql.listen_addresses | 等同于pg端口的参数listen_addresses,监听oracle兼容连接的ip地址,对应内核参数OraListenAddresses |
| ivorysql.database_mode | 值为oracle(1),pg(0),对应内核参数为database_mode, PGC_INTERNAL级别, 用户不可修改,可以视为整个db级别的参数,创建后就不可修改, 默认为DB_PG, 1. 是否监听oracle端口 2. 当前库是否包含oracle兼容的数据类型等 |
c
//新增宏,默认的port
DEF_ORAPORT
DEF_ORAPORT_STR
//新增环境变量IYHOST,IYPORT,在设置环境变量 不指定port和host时,使用环境变量的host和port连接数据库?只留了定义没有使用 最新的代码已经删掉了
{"ivoryhost", "IYHOST", NULL, NULL,
"IvorySQL-Database-Host", "", 40,
offsetof(struct pg_conn, iyhost)},
{"ivoryport", "IYPORT", DEF_ORAPORT_STR, NULL,
"IvorySQL-Database-Port", "", 6,
offsetof(struct pg_conn, iyport)},
//iyhost,iyport 字段还在
struct pg_conn
{
/* Saved values of connection options */
char *pghost; /* the machine on which the server is running,
* or a path to a UNIX-domain socket, or a
* comma-separated list of machines and/or
* paths; if NULL, use DEFAULT_PGSOCKET_DIR */
char *iyhost; /* Oracle compatible mode ENV VARIABLE */
char *pghostaddr; /* the numeric IP address of the machine on
* which the server is running, or a
* comma-separated list of same. Takes
* precedence over pghost. */
char *iyport; /* Oracle compatible mdoe ENV VARIABLE */
char *pgport; /* the server's communication port number, or
//...
}
新增系统函数
| 函数名 | 解释 |
|---|---|
| pg_get_connection_mode | 返回当前连接的方式是oracle或者pg |
babelfish 协议层的抽象
双端口的实现类似于babelfish或是mysql兼容上对协议层功能的裁剪
c
//连接对应的接口
typedef struct ProtocolExtensionConfig {
int (*fn_accept)(pgsocket server_fd, struct Port *port);
void (*fn_close)(pgsocket server_fd);
void (*fn_init)(void);
int (*fn_start)(struct Port *port);
void (*fn_authenticate)(struct Port *port, const char **username);
void (*fn_mainfunc)(struct Port *port) pg_attribute_noreturn();
void (*fn_send_message)(ErrorData *edata);
void (*fn_send_cancel_key)(int pid, int32 key);
void (*fn_comm_reset)(void);
bool (*fn_is_reading_msg)(void);
void (*fn_send_ready_for_query)(CommandDest dest);
int (*fn_read_command)(StringInfo inBuf);
void (*fn_end_command)(QueryCompletion *qc, CommandDest dest);
bool (*fn_printtup)(TupleTableSlot *slot, DestReceiver *self);
void (*fn_printtup_startup)(DestReceiver *self, int operation,
TupleDesc typeinfo);
void (*fn_printtup_shutdown)(DestReceiver *self);
void (*fn_printtup_destroy)(DestReceiver *self);
int (*fn_process_command)(void);
void (*fn_report_param_status)(const char *name, char *val);
} ProtocolExtensionConfig;
//pg默认的libpq连接
static ProtocolExtensionConfig default_protocol_config = {
libpq_accept,
libpq_close,
libpq_init,
libpq_start,
libpq_authenticate,
libpq_mainfunc,
libpq_send_message,
libpq_send_cancel_key,
libpq_comm_reset,
libpq_is_reading_msg,
libpq_send_ready_for_query,
libpq_read_command,
libpq_end_command,
NULL, NULL, NULL, NULL, /* use libpq defaults for printtup*() */
NULL,
libpq_report_param_status
};
//支持tds协议
static ProtocolExtensionConfig pe_config = {
pe_accept,
pe_close,
pe_tds_init,
pe_start,
pe_authenticate,
pe_mainfunc,
pe_send_message,
NULL, /* not interested in cancel key */
NULL,
NULL,
pe_send_ready_for_query,
pe_read_command,
pe_end_command,
TdsPrintTup,
TdsPrinttupStartup,
TdsShutdown,
TdsDestroy,
pe_process_command,
pe_report_param_status
};
typedef struct Port
{
//...
//port新增字段protocol_config,is_tds_conn
ProtocolExtensionConfig *protocol_config; /* wire protocol functions */
bool is_tds_conn; //当前是否为tsql连接
} Port;
PostmasterMain
ServerLoop
ConnCreate //赋值字段protocol_config
在双端口的基础上实现双解析等
以下大部分为对原先功能的复制,拆分为两份代码文件,提供不同的接口,后续oracle新增语法在oracle文件中补充。因为是对原有功能的复制,所以oracle兼容支持所有pg原有的语法与功能:
1. 双parser,oracle兼容走ora_parser, pg走原生的parser。oracle兼容下语法解析后转为pg的语法树,走pg的语义分析,重写,优化执行等
2. 新增plisql
3. 双测试框架
- 新增参数用来控制
5. oracle的config文件等
双parser
-
拷贝原有parser,新增oracle_parser文件夹,以及对应文件,实现双词法解析以及双parser需要对gram.y以及scan.l做接口名做部分修改
生成不同的词法解析函数:%option prefix="core_yy" 通过加入前缀,可以将原来的yylex等函数 变成core_yylex.这样可以在一个程序中建立多个词法分析器。用来分析不同的输入流。
生成不同语法解析函数:%name-prefix="base_yy" 代表生成的函数和变量名从yy改成base_yy,同flex,为了在一个产品里使用多个语法分析器,分析不同的数据类。

ivorysql的oracle parser也是以插件形式编译加载,需要通过参数shared_preload_libraries 加载到db中
shared_preload_libraries = 'liboracle_parser, ivorysql_ora'
c
//加载和 unload时 做函数指针的赋值和置空
void
_PG_init(void)
{
prev_raw_parser = ora_raw_parser;
prev_pg_get_keywords = get_keywords_hook;
prev_fill_in_contant_lengths = fill_in_constant_lengths_hook;
prev_quote_identifier = quote_identifier_hook;
ora_raw_parser = oracle_raw_parser;
get_keywords_hook = oracle_pg_get_keywords;
fill_in_constant_lengths_hook = oracle_fill_in_constant_lengths;
quote_identifier_hook = oracle_quote_identifier;
}
void
_PG_fini(void)
{
ora_raw_parser = prev_raw_parser;
get_keywords_hook = prev_pg_get_keywords;
fill_in_constant_lengths_hook = prev_fill_in_contant_lengths;
quote_identifier_hook = prev_quote_identifier;
}
- 原有parser接口改为钩子函数sql_raw_parser,通过guc参数来ivorysql.compatible_mode设置值
c
//parser.c
raw_parser_hook_type sql_raw_parser= standard_raw_parser;
raw_parser_hook_type ora_raw_parser= NULL;
List *
raw_parser(const char *str, RawParseMode mode)
{
if (sql_raw_parser == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYSTEM_ERROR),
errmsg("liboracle_parser not found!"),
errhint("You must load liboracle_parser to use oracle parser.")));
return (*sql_raw_parser)(str, mode);
}
//原生pg parser接口改为standard_raw_parser
List *
standard_raw_parser(const char *str, RawParseMode mode)
{
//。。。
}
默认的ivorysql.compatible_mode参数和database_mode
c
PostmasterMain //postmaster进程
ServerLoop
BackendStartup
BackendRun //Backend进程
PostgresMain
InitIvorysql
//根据双端口连接设置的参数connmode 设置新会话默认的ivorysql.compatible_mode参数
static void
InitIvorysql(void)
{
if (MyProcPort)
{
if (MyProcPort->connmode == 'p')
{
SetConfigOption("ivorysql.compatible_mode", "pg", PGC_USERSET, PGC_S_OVERRIDE);
}
else if (MyProcPort->connmode == 'o')
{
SetConfigOption("ivorysql.compatible_mode", "oracle", PGC_USERSET, PGC_S_OVERRIDE);
if (NULL == ora_raw_parser)
ereport(ERROR,
(errmsg("Invalid Oracle compatibility mode syntax library.")));
}
}
}
//sql_raw_parser 在对应ivorysql.compatible_mode参数赋值时赋值
//ivorysql.compatible_mode的assign函数
void
assign_compatible_mode(int newval, void *extra)
{
if(DB_ORACLE == database_mode
&& (IsNormalProcessingMode() || (IsUnderPostmaster && MyProcPort)))
{
if (newval == DB_ORACLE)
{
sql_raw_parser = ora_raw_parser;
pg_transform_merge_stmt_hook = ora_transform_merge_stmt_hook;
pg_exec_merge_matched_hook = ora_exec_merge_matched_hook;
assign_search_path(NULL, NULL);
}
else if (newval == DB_PG)
{
sql_raw_parser = standard_raw_parser;
pg_transform_merge_stmt_hook = transformMergeStmt;
pg_exec_merge_matched_hook = ExecMergeMatched;
assign_search_path(NULL, NULL);
}
}
}
//ivorysql.compatible_mode的check函数,判断当前database_mode等
static bool
check_compatible_mode(int *newval, void **extra, GucSource source)
{
int newmode = *newval;
if (DB_PG == database_mode && newmode == DB_ORACLE)
ereport(ERROR,
(errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
errmsg("parameter ivorysql.compatible_mode cannot be changed in native PG mode.")));
if(DB_ORACLE == database_mode
//正常状态(非init 非bootstrap) 或者 postmaster的子进程 并且 MyProcPort!=NULL
&& (IsNormalProcessingMode() || (IsUnderPostmaster && MyProcPort)))
{
if (newmode == DB_ORACLE)
{
if (ora_raw_parser == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYSTEM_ERROR),
errmsg("liboracle_parser not found!"),
errhint("You must load liboracle_parser to use oracle parser.")));
if (!ISLOADIVORYSQL_ORA)
ereport(ERROR,
(errcode(ERRCODE_SYSTEM_ERROR),
errmsg("IVORYSQL_ORA library not found!"),
errhint("You must load IVORYSQL_ORA to use oracle parser..")));
}
}
return true;
}
-
initdb阶段的适配
cstatic const char *ora_options = "-c session_preload_libraries=liboracle_parser "; void initialize_data_directory(void) { //... /* Bootstrap template1 */ bootstrap_template1(); /* * Make the per-database PG_VERSION for template1 only after init'ing it */ write_version_file("base/1"); //single模式下 创建系统对象时 oracledb 要加载liboracle_parser 动态库 fputs(_("performing post-bootstrap initialization ... "), stdout); fflush(stdout); if (strcmp(dbmode, "pg") == 0) snprintf(cmd, sizeof(cmd), "\"%s\" %s %s template1 >%s", backend_exec, backend_options, extra_options, DEVNULL); else snprintf(cmd, sizeof(cmd), "\"%s\" %s %s %s template1 >%s", backend_exec, backend_options, extra_options, ora_options, DEVNULL); PG_CMD_OPEN; setup_auth(cmdfd); setup_run_file(cmdfd, system_constraints_file); // pg原有文件,以pg语法执行, 有oracle兼容语法文件时 切换到oracle if (database_mode == DB_ORACLE) PG_CMD_PUTS("set ivorysql.compatible_mode to pg;\n\n"); //加载system_functions,system_views, dictionary_file, schema, plpgsql插件。。。 //加载ivory_ora(兼容性功能 包含type,function等的插件),需要切换compatible_mode到oracle if (database_mode == DB_ORACLE) { load_plisql(cmdfd); load_ivorysql_ora(cmdfd); } vacuum_db(cmdfd); make_template0(cmdfd); make_postgres(cmdfd); PG_CMD_CLOSE; check_ok(); } -
其他工具适配
c// 查询兼容性的接口, 赋值全局变量db_mode, 在对应工具中做判断 void getDbCompatibleMode(PGconn *conn) { PGresult *res; char *db_style = NULL; res = PQexec(conn, "show ivorysql.compatible_mode;"); if (PQresultStatus(res) != PGRES_TUPLES_OK) db_mode = DB_PG; else { db_style = PQgetvalue(res, 0, 0); if (0 == pg_strcasecmp(db_style, "oracle")) db_mode = DB_ORACLE; else db_mode = DB_PG; } if (res) PQclear(res); }

-
新增参数
参数名 解释 ivorysql.compatible_mode 用户级别的参数,会话级别可修改,对应参数compatible_db,默认为PG_PARSER,用来控制当前会话是否支持oracle语法与功能
参考 Postgresql源码学习之词法和语法分析 - 墨天轮
新增plisql
原文件基本复制的plpgsql,做了部分文件名等修改
c
//initdb阶段,创建插件到template1
initialize_data_directory(void)
{
//...
//加载ivory_ora(兼容性功能 包含type,function等的插件),需要切换compatible_mode到oracle
if (database_mode == DB_ORACLE)
{
load_plisql(cmdfd);
load_ivorysql_ora(cmdfd);
}
}
//匿名块默认用plisql
void
ExecuteDoStmt(ParseState *pstate, DoStmt *stmt, bool atomic)
{
//...
/* if LANGUAGE option wasn't specified, use the default */
if (language_item)
language = strVal(language_item->arg);
else
{
/* anonymous block's language default value is plsql
* in oracle compatibility mode
*/
if (DB_ORACLE == compatible_db)
language = "plisql";
else
language = "plpgsql";
}
}
oracle的config文件等
新增ivorysql.conf.sample,initdb阶段加载
c
//include_if_exists 相当于在原有的postgres.conf后追加内容,理论上ivorysql.conf中的参数优先级比postgres.conf中的高
//pgmode 不包含ivorysql.conf
if (database_mode == DB_PG)
{
conflines = replace_token(conflines,
"include_if_exists = 'ivorysql.conf'",
"#include_if_exists = 'ivorysql.conf'");
}