ISO/IEC 9075-14(SQL/XML)中与 XML 相关的规范在 SQL:2006 中经历了重大修订。 PostgreSQL 对 XML 数据类型及相关函数的实现大体遵循较早的 2003 版,并从后续版本借鉴了一些内容。具体而言:
当前标准提供了一族 XML 数据类型,用于以无类型或 XML 模式类型化的变体保存
“文档” 或 “内容”,并提供保存任意 XML 内容片段的
XML(SEQUENCE) 类型;而
PostgreSQL 只提供单一的
xml 类型,它可以保存 “文档” 或
“内容”。标准中 “序列” 类型没有对应物。
PostgreSQL 提供了 SQL:2006 引入的两个函数,但使用的是 XPath 1.0 语言的变体,而非标准为其指定的 XML Query。
PostgreSQL 不支持
RETURNING CONTENT 或
RETURNING SEQUENCE 子句;规范中定义带有这些子句的函数都隐式地返回内容。
本节介绍你可能遇到的一些由此产生的差异。
PostgreSQL 专有的函数
xpath() 与
xpath_exists() 使用 XPath 语言查询 XML 文档。
PostgreSQL 还为官方使用 XQuery 语言的标准函数
XMLEXISTS 与
XMLTABLE 提供了仅支持 XPath 的变体。对所有这些函数,
PostgreSQL 依赖
libxml2 库,而该库只提供 XPath 1.0。
XQuery 语言与 XPath 2.0 及更高版本之间有很强的关联:任何在两者中都语法有效且执行成功的表达式会产生相同的结果(一个次要例外是包含数字字符引用或预定义实体引用的表达式,XQuery 会用对应字符替换它们,而 XPath 保持原样)。但这些语言与 XPath 1.0 之间没有这种关联;它是一门更早的语言,在许多方面都有差异。
有两类限制需要记住:SQL 标准规定的函数从 XQuery 到 XPath 的限制,以及标准函数与 PostgreSQL 专有函数共同面临的 XPath 限制到 1.0 版。
XQuery 超出 XPath 的特性包括:
XQuery 表达式可以构造并返回新的 XML 节点,此外还能返回所有可能的 XPath 值。XPath 可以创建并返回原子类型的值(数字、字符串等),但只能返回已经出现在作为表达式输入的文档中的 XML 节点。
XQuery 具有用于迭代、排序与分组的控制结构。
XQuery 允许声明并使用局部函数。
较新的 XPath 版本开始提供与这些能力重叠的功能(例如函数式风格的
for-each 与
sort、匿名函数,以及从字符串创建节点的
parse-xml),但这些特性在 XPath 3.0 之前并不可用。
对于熟悉 XQuery 与 XPath 2.0 及更高版本的开发者来说,XPath 1.0 带来了许多需要应对的差异:
XQuery/XPath 表达式的基本类型 序列(它可以包含 XML 节点、原子值,或两者兼有)在 XPath 1.0 中并不存在。1.0 表达式只能产生节点集(包含零个或多个 XML 节点)或单个原子值。
与可以按任意期望顺序包含任意期望条目的 XQuery/XPath 序列不同,XPath 1.0 的节点集没有任何顺序保证,而且与任何集合一样,不允许同一条目多次出现。
libxml2 库看起来确实总是以成员在输入文档中的相对顺序把节点集返回给 PostgreSQL。它的文档并未承诺这一行为,XPath 1.0 表达式也无法控制它。
XQuery/XPath 提供了 XML Schema 定义的所有类型以及作用于这些类型的众多操作符与函数,而 XPath 1.0 只有节点集以及三个原子类型
boolean、double 与
string。
XPath 1.0 没有条件操作符。诸如
if ( hat ) then hat/@size else "no hat" 这样的
XQuery/XPath 表达式在 XPath 1.0 中没有对应写法。
XPath 1.0 没有针对字符串的排序比较操作符。"cat" < "dog" 与
"cat" > "dog" 都为假,因为每个都是两个
NaN 之间的数值比较。相比之下,
= 与
!= 确实按字符串方式比较字符串。
XPath 1.0 混淆了 XQuery/XPath 所定义的
值比较 与
一般比较 的区别。sale/@hatsize = 7 与
sale/@customer = "alice" 都是存在量词比较,只要存在任何
sale 在该属性上具有给定值即为真;而
sale/@taxable = false() 是与整个节点集的
有效布尔值 进行的值比较,只有当完全没有
sale 拥有
taxable 属性时才为真。
在 XQuery/XPath 数据模型中,文档节点 可以处于文档形式(即恰好一个顶层元素,其外只有注释与处理指令)或内容形式(放宽上述约束)。它在 XPath 1.0 中的对应物
根节点 只能处于文档形式。这也是作为上下文项传递给任何
PostgreSQL 基于 XPath 的函数的
xml 值必须处于文档形式的部分原因。
这里强调的差异并非全部。在 XQuery 与 XPath 2.0 及更高版本中存在一种 XPath 1.0 兼容模式,W3C 列出的该模式下的 函数库变更 与 语言变更 对这些差异给出了更完整(但仍不穷尽)的说明。兼容模式无法使后续语言与 XPath 1.0 完全等效。
在 SQL:2006 及更高版本中,标准 SQL 数据类型与 XML Schema 类型之间双向转换都被精确规定。不过,这些规则使用 XQuery/XPath 的类型与语义表述,对 XPath 1.0 不同的数据模型没有直接适用性。
当 PostgreSQL 把 SQL 数据值映射为 XML(如
xmlelement),或把 XML 映射为 SQL(如
xmltable 的输出列)时,除少数被特殊处理的情况外,
PostgreSQL 只是简单地假定 XML 数据类型的
XPath 1.0 字符串形式作为 SQL 数据类型的文本输入形式有效,反之亦然。这条规则的好处是简单,同时对于许多数据类型能产生与标准规定的映射相似的结果。
在需要与其他系统互操作时,对某些数据类型可能有必要显式使用数据类型格式化函数(例如 Section 9.8 中的那些)来产生标准的映射。
本节关注的限制并非 libxml2 库所固有,而是适用于 PostgreSQL 中的当前实现。
BY VALUE 传参机制 #
SQL 标准定义了两种 传参机制,适用于把 XML 参数从 SQL 传给 XML 函数或接收结果的场合:
BY REF 保持特定 XML 值的节点标识,
BY VALUE 只传递 XML 的内容而保留节点标识。机制可以在参数列表之前指定,作为所有参数的默认机制;也可以放在任何参数之后,以覆盖默认值。
为说明差异,如果
x 是一个 XML 值,在 SQL:2006 环境中以下两条查询会分别产生真与假:
SELECT XMLQUERY('$a is $b' PASSING BY REF x AS a, x AS b NULL ON EMPTY);
SELECT XMLQUERY('$a is $b' PASSING BY VALUE x AS a, x AS b NULL ON EMPTY);
PostgreSQL 会在
XMLEXISTS 或
XMLTABLE 结构中接受
BY VALUE 或
BY REF,但会忽略它们。xml 数据类型保存的是字符序列化的表示,因此没有节点标识可保留,传参实际上总是
BY
VALUE。
基于 XPath 的函数支持传递一个参数作为 XPath 表达式的上下文项,但不支持传递额外的值供表达式作为命名参数使用。
XML(SEQUENCE) 类型 #
PostgreSQL 的
xml 数据类型只能以
DOCUMENT 或
CONTENT 形式保存值。XQuery/XPath 表达式上下文项必须是单个 XML 节点或原子值,但 XPath 1.0 进一步把它限制为只能是 XML 节点,并且没有允许
CONTENT 的节点类型。结论是:格式良好的
DOCUMENT 是
PostgreSQL 能作为 XPath 上下文项提供的唯一 XML 值形式。