IvorySQL 3.2 贡献独门秘籍,可以平滑迁移Oracle???

在数字化浪潮席卷全球的当下,各行各业的数据量呈现爆发式增长,数据库管理系统已成为企业数据处理不可或缺的基石。此背景下,许多使用Oracle的企业可能难以面对一些挑战,数据库复杂性、高昂维护成本、数据迁移和集成等问题逐渐凸显。IvorySQL 3.2基于PostgreSQL 16.2,引入了多种Oracle XML函数的全面兼容性功能,同时修复了多个问题,更多信息请参考文档网站。

IvorySQL完美兼容了Oracle 12c中的部分常用XML函数,这意味着依赖这些XML函数的SQL代码可以平滑地从Oracle迁移到IvorySQL,而无需进行任何修改

起猛了,以为在用Oracle?

图片

图片

那么,IvorySQL 3.2是如何实现与Oracle 12c XML函数的兼容性的呢?

这些函数包括:

APPENDCHILDXML 、DELETEXML、EXISTSNODE、EXTRACT (XML)、EXTRACTVALUE、INSERTCHILDXMLAFTER、INSERTCHILDXMLBEFORE、INSERTCHILDXML、INSERTXMLAFTER、INSERTXMLBEFORE、UPDATEXML。

>>>新版本体验链接:

https://docs.ivorysql.org/cn/ivorysql-doc/v3.2/v3.2/1.html

01

技术实现原理

IvorySQL在实现与Oracle 12c中11个常用XML SQL函数的兼容性时,与PostgreSQL保持了一致,底层处理函数同样采用了libxml2库提供的接口

这些XML函数作为ivorysql_ora插件的一个子插件提供,确保了与PostgreSQL数据库在XML处理方面的兼容性和一致性。

由于Oracle的xml函数要求某些参数类型是 XMLType,比如下面这个existsnode()函数:

原型:

EXISTSNODE(XMLType_instance, XPath_string [, namespace_string ])

示例:

SELECT existsnode(XMLType('<a><b>d</b></a>'), '/a') from dual;

>>>添加XMLType数据类型

为了确保与Oracle的兼容性,IvorySQL添加了一个XMLType数据类型。这个数据类型的作用是将用户提供的字符串转换成内部统一的XMLType类型从而无需用户修改SQL语句即可实现从Oracle到IvorySQL的平滑迁移。这一举措极大地简化了迁移过程,降低了用户的操作难度。

CREATE TYPE sys.XMLTYPE (
  INPUT     = sys.xmltype_in,
 OUTPUT     = sys.xmltype_out,
  LIKE         = xml
);

其中输入输出转换由以下的函数完成。

CREATE FUNCTION sys.xmltype_in(cstring)
RETURNS sys.xmltype
AS 'MODULE_PATHNAME','ivy_xmltype_in'
LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION sys.xmltype_out(sys.xmltype)
RETURNS cstring
AS 'MODULE_PATHNAME','ivy_xmltype_out'
LANGUAGE C IMMUTABLE STRICT;

另外,为了避免与PostgreSQL中已有的同名关键字“extract”产生混淆,IvorySQL将原有的同名关键字重命名为“PGEXTRACT”,以确保函数调用的清晰性和准确性。

>>>两种不同的实现方式

在实现这11个Oracle兼容的XML函数时,IvorySQL采用了两种不同的实现方式。

其中,除了UPDATEXML函数外,其他10个函数均通过SQL函数的方式进行实现。由于UPDATEXML函数的参数数量是不确定的,因此采用了表达式方式来实现,这要求为其编写专门的语法分析代码和执行器代码,以确保其功能的正确性和灵活性。

>>>以下是updatexml函数实现过程的简要叙述:

首先,在src/include/oracle_parser/ora_kwlist.h文件中,我们新增了关键字updatexml

PG_KEYWORD("updatexml", UPDATEXML, COL_NAME_KEYWORD, BARE_LABEL)

随后,在src/backend/oracle_parser/ora_gram.y文件中,我们将UPDATEXML定义为关键字,并引入了xmlf_expr_list作为一个新的list类型。为了支持UPDATEXML函数的复杂语法结构,我们增加了如下相关的语法规则:

func_expr_common_subexpr:
| UPDATEXML '(' xmlf_expr_list ')'
{
$$ = makeXmlExpr(IS_UPDATEXML, NULL, NIL, $3, @1);
}

xmlf_expr_list: a_expr
      {
    $$ = list_make1($1);
   }
   | xmlf_expr_list ',' a_expr
   {
    $$ = lappend($1, $3);
   }

上述规则在语法分析阶段确保了当遇到UPDATEXML(..., ..., ...)字段时,能够正确地将小括号内以逗号分隔的内容作为参数,来构造一个XmlExpr类型的node结构体。这个结构体随后被返回给语法分析器以进行后续处理。

值得注意的是,这个node中包含了新增的操作码IS_UPDATEXML。在后续的执行阶段,内核函数ExecEvalXmlExpr()会特别针对IS_UPDATEXML操作码进行处理,从而实现了对UPDATEXML函数调用的正确执行。

case IS_UPDATEXML:
  {
   Datum   *argvalue = op->d.xmlexpr.argvalue;
   bool     *argnull = op->d.xmlexpr.argnull;
   List     *values = NIL;
// 确保参数不少于3个
   if (list_length(xexpr->args) < 3)
    ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
      errmsg("invalid number of arguments")));
// 构建参数列表
   for (int i = 0; i < list_length(xexpr->args); i++)
   {
    values = lappend(values, DatumGetPointer(argvalue[i]));
   }
// 调用钩子函数
   if (values != NIL)
   {
    if (ora_updatexml_hook && compatible_db == DB_ORACLE)
     *op->resvalue = PointerGetDatum((*ora_updatexml_hook)(values));
    else
     *op->resvalue = (Datum) 0;
    *op->resnull = false;
   }
  }
  break;

从上述代码中可以看到,在特定情况下,预先设定的钩子函数被调用这个钩子函数在插件初始化时,被特别指定为与updatexml函数关联的c函数

具体来说,这个函数位于contrib/ivorysql_ora/src/ivorysql_ora.c文件中。这样的设计使得当IvorySQL遇到updatexml函数调用时,能够调用相应的c函数来处理,从而实现了对Oracle中updatexml函数的兼容。

pre_ora_updatexml_hook = ora_updatexml_hook;
 ora_updatexml_hook = updatexml;

02

一些函数的使用实例

>>>首先准备基础数据:

create table inaf(a int, b xmltype);
insert into inaf values(1,xmltype('<a><b>100</b></a>'));
insert into inaf values(2, '');

select * from inaf;

create table xmltest(id int, data XMLType);
insert into xmltest values(1, '<value>one</value>');
insert into xmltest values(2, '<value>two</value>');

select * from xmltest;

create table xmlnstest(id int, data xmltype);
INSERT INTO xmlnstest VALUES(1, xmltype('<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="http://www.def.com" xmlns:web="http://www.abc.com"><soapenv:Body><web:BBB><typ:EEE>41</typ:EEE><typ:FFF>42</typ:FFF></web:BBB></soapenv:Body></soapenv:Envelope>'));

>>>测试各个函数功能:

(1)函数extract的示例用法:

extrct(XML) 该函数用于返回XML节点路径下的相应内容。其中参数XMLType_instance用于指定XMLType实例,Xpath_string用于指定XML节点路径。

SELECT extract(XMLType('<AA><ID>1</ID></AA>'), '/AA/ID') from dual;
  extract   
------------
 <ID>1</ID>
(1 row)

(2)函数extractvalue的示例用法:

extractvalue 该函数提供对XML内容的检索功能,extractvalue只能返回一个节点的一个值。

SELECT extractvalue(XMLType('<a><b>100</b></a>'),'/a/b') from dual;
 extractvalue 
--------------
 100
(1 row)

(3)函数existsnode的示例用法:

existsnode 该函数用于检查XML内容是否符合指定的路径表达式。

SELECT existsnode(XMLType('<a><b>d</b></a>'), '/a/b') from dual;
 existsnode 
------------
          1
(1 row)

(4)函数deletexml的示例用法:

deletexml 该函数用于删除指定路径的XML节点。

SELECT deletexml(XMLType('<test><value>oldnode</value><value>oldnode</value></test>'),  '/test/value') from dual;
 deletexml 
-----------
 <test/>
(1 row)

(5)函数appendchildxml的示例用法:

appendchildxml 该函数用于在XML对象中插入子节点。它接受一个XML对象和一个XML片段作为参数,并将XML片段作为子节点插入到XML对象中。

SELECT appendchildxml(XMLType('<test><value></value><value></value></test>'),  '/test/value', XMLTYPE('<name>newnode</name>')) from dual;
      appendchildxml      
--------------------------
 <test>                  +
   <value>               +
     <name>newnode</name>+
   </value>              +
   <value>               +
     <name>newnode</name>+
   </value>              +
 </test>
(1 row)

(6)函数updatexml的示例用法:

updatexml 该函数用于更新特定XML相应的节点路径的内容。

SELECT updatexml(xmltype('<value>one</value>'), '/value', xmltype('<newvalue>1111</newvalue>')) FROM dual;
         updatexml         
---------------------------
 <newvalue>1111</newvalue>
(1 row)

(7)函数insertxmlbefore的示例用法:

insertxmlbefore 该函数用于在XML中的指定路径前插入子节点。

SELECT insertxmlbefore(XMLType('<a>222<b>100</b><b>200</b></a>'), '/a/b', XMLTYPE('<c>88</c>')) from dual;
                 insertxmlbefore
--------------------------------------------------
 <a>222<c>88</c><b>100</b><c>88</c><b>200</b></a>
(1 row)

(8)函数insertxmlafter的示例用法:

insertxmlafter 该函数用于在XML中的指定路径后插入子节点。

SELECT insertxmlafter(XMLType('<a><b>100</b></a>'),'/a/b',XMLType('<c>88</c>')) from dual;

 insertxmlafter
----------------
 <a>           +
   <b>100</b>  +
   <c>88</c>   +
 </a>
(1 row)

(9)函数insertchildxml的示例用法:

insertchildxml 该函数用于向指定的XML路径中插入子节点。

SELECT insertchildxml(XMLType('<a>one<b></b>three<b></b></a>'), '//b', 'name', XMLTYPE('<name>newnode</name>')) from dual;
                            insertchildxml
-----------------------------------------------------------------------
 <a>one<b><name>newnode</name></b>three<b><name>newnode</name></b></a>
(1 row)

(10)函数INSERTCHILDXMLBEFORE的示例用法:

INSERTCHILDXMLBEFORE 该函数用于向指定的XML路径之前插入子节点。

SELECT insertchildxmlbefore(XMLType('<a><b>100</b></a>'), '/a', 'b', XMLType('<c>88</c>')) from dual;
 insertchildxmlbefore
----------------------
 <a>                 +
   <c>88</c>         +
   <b>100</b>        +
 </a>
(1 row)

(11)函数INSERTCHILDXMLAFTER的示例用法:

INSERTCHILDXMLAFTER 该函数用于向指定的XML路径之后插入子节点。

SELECT insertchildxmlafter(XMLType('<a><b>100</b></a>'), '/a', 'b', XMLType('<c>88</c>')) from dual;
 insertchildxmlafter
---------------------
 <a>                +
   <b>100</b>       +
   <c>88</c>        +
 </a>
(1 row)

03

结论

XML(eXtended Markup Language可扩展标记语言)是一种基于文本的,用于结构化任何可标记文档的格式语言。它是一种轻便的,可扩展的,标准的且简学易懂的保存数据的语言。

IvorySQL在PostgreSQL的基础上,实现与Oracle XML函数的高度兼容,确保了从Oracle迁移到IvorySQL后的数据格式和结构的一致性。这种兼容性意味着用户无需对现有的XML处理逻辑进行大规模修改,从而保证了数据的完整性和准确性。此外,IvorySQL的这种跨平台的兼容特性,也降低了因格式差异带来的额外用户维护和升级成本,使得数据处理和管理更加高效、可靠和灵活。

>>>新版本体验链接:

https://docs.ivorysql.org/cn/ivorysql-doc/v3.2/v3.2/1.html

上一篇:Ajax的请求响应


下一篇:62、回溯-N皇后