在数字化浪潮席卷全球的当下,各行各业的数据量呈现爆发式增长,数据库管理系统已成为企业数据处理不可或缺的基石。在此背景下,许多使用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