级别: 初级
Howard Katz, 所有者, Fatdog Software
2001 年 6 月 01 日
2006 年 2 月 06 日 更新
Howard
Katz 介绍了 W3C XQuery 规范,该规范目前正在努力成为推荐标准。这个复杂的规范目前包含 15
个不同的工作草案,而且在最终完成之前可能还会增加。本文提供了该规范的一些历史背景、形成文档的路线图和规范当前状态的简单介绍。侧栏简要描述了
XQuery 表层语法的一些主要特性。通过示例代码示范了 XQuery 和 XQueryX 之间的区别,并给出了关于表层语法的例子。
注意:本文在
2005 年 12 月经过修正,结合了 XQuery 规范的最新变动情况:其中八个工作草案已进入 “W3C 候选推荐标准”
状态,整个规范距离最终成为 “推荐标准” 更近了一步。主要的全文文档最初发表于 2004 年,最近经过了修改。关于更新设施的
“需求工作草案(Requirements Working Draft)” 和建立 XPath/XQuery
词法分析器(tokenizer)的草案都是 2005 年第一次发布的。XQuery 特性的数量继续增长,XQuery 实现者列表和 Web
上的开发人员资源也在不断增加。
沿着成为 W3C 推荐标准的道路走了漫长的六年之后,XQuery
规范颇具好莱坞大片的神秘和不朽色彩,就像 “星球大战” 和 “指环王” 系列一样。XQuery 起源于 1998 年由 W3C
发起的查询语言研讨会,来自工业界、学术界和研究团体的代表聚集到波士顿,提出了 XML 查询语言中他们认为重要的特性和需求。
两个不同的阵营
那些对历史感兴趣的人可以在线获取这 66 篇演讲稿(参阅 参考资料),它们主要来自两大不同的阵营:那些将 XML 主要作为文档 使用的人(在很大程度上反映了 XML 起源于 SGML)和将 XML 作为数据 使用的人 —— 后者很大程度上反映了 XML 在中间件领域、前端传统关系数据库领域中不断增长的现状。
尤
其是俄勒冈州研究生院的 David Maier的演讲稿 “Database Desiderata for an XML Query
Language”,它非常简明扼要,非常有助于了解 Query Language Working
Group(查询语言工作组)的思想,后者是在波士顿讨论会之后不久特许成立的。
虽然成员时增时减,但按照 W3C
的标准,这个工作组很庞大(据说只有 Protocol Working Group(协议工作组)的成员比它多)。它由 30
多个成员公司构成,反映了数据和文档这两大阵营各自的观点。现在合并而成的最终形式(经过了漫长的时间)是一种 XML 查询语言标准,
能够代表这两个团体的需求和观点。
对于 XML 用户来说,最熟悉的 XQuery 关键组件是
XPath,它本身也是一个 W3C 规范。单独的 XPath 位置路径本身(“//book/editor” 意味着
“在当前数据集中查找所有图书编辑”)就是完全有效的 XQuery。在数据方面,XQuery 具有类似于 SQL
的外观和能力,这是来自关系数据库世界的用户所欢迎和熟悉的。
卑微的出身
XQuery
最初称为 Quilt。Quilt 最初作为用户级语法的测试工具,是工作组中三个勤奋而又眼光远大的成员创立的:Jonathan
Robie、Don Chamberlin 和 Daniela Florescu。在定义需求、用例以及底层数据模型和代数方面,Quilt
都建立在整个工作组的协同努力的基础上。
Robie、Chamberlin 和 Florescu 指出了一些语言对
Quilt 设计的影响,包括 XQL、XML-QL 和 SQL。如果对计算机语言的演化感兴趣,请阅读 “XML Query Language:
Experiences and Exemplars”(参阅 参考资料),这篇意义重大的论文对前两种语言以及另外两种语言 YaTL 和 Lorel 进行了很好的比较。作者 Mary Fernandez、Jerome Simeon 和 Phil Wadler 本身都是该工作组的成员。
既然数据和文档团体持有如此不同的的观点,以及工作组所建立的基础的可靠性,这个庞大的规范需要如此长的时间才向大众公布也就不足为奇了。W3C 工作组的内部进展是严格保密的,并且在 2001 年 2 月中旬以前,查询语言工作组的大多数工作都是秘密进行的。
很
早就发布了 Requirements 文档和 Data Model 工作草案,但是直到 2001 年 2
月,工作组的发布工作才得以进入高潮,那时大量的文档开始出现。此后在 2001 年进行了两次重要更新,以后每年都有三四次更新,只有 2004
年例外,工作组只发布了一次更新。
今年新增加了更新机制的需求文档,再加上为 XQuery
语言实现者提供的关于建立词法分析器的简短说明,文档总数达到了 16 个(包括由于某种我不清楚的原因也放在 XML Query 网站上的
XSLT 规范),不用很长时间就会组成一套完整的文档集。肯定会在某个时候出现一种更新语言文档。
新生的发布王国
完全用于描述和定义 XQuery 的文档目前包括:
-
XML Query Requirements
- 工作组的主要规划文档。XQuery 需求列表。
-
XML Query Use Cases
- 一些实际场景和解决特定问题的 XQuery 片段。
-
XQuery 1.0: An XML Query Language
- 核心文档,介绍语言本身,以及对大多数其他内容的概述。
-
XQuery 1.0 and XPath 2.0 Data Model
- XML 信息集的扩展。描述查询实现必须理解的数据项和形式语义的基础。
-
XQuery 1.0 and XPath 2.0 Formal Semantics
- 从形式上定义语言的底层代数。
-
XML Syntax for XQuery 1.0 (XQueryX)
- 为喜欢使用 XML 的人(主要是计算机)提供的另一种语法。
-
XQuery 1.0 and XPath 2.0 Functions and Operators Version 1.0
- XML Schema 数据类型、 XML 节点及其序列的大约 225 个函数和操作符。
-
XML Path Language (XPath) 2.0
- 单独分离出来的 XPath 文档。
-
XPath Requirements Version 2.0
- XPath 的需求文档。
-
XSLT 2.0 and XQuery 1.0 Serialization
- 对从 XQuery 1.0 和 XPath 2.0 Data Model 输出序列化尖括号 有关的 XML 问题的考察。序列化本质上 不是主语言规范的一部分。
-
XML Query and XPath Full-Text Requirements
- 描述 Full-Text Recommendation 需要达到的功能需求。
-
XML Query and XPath Full-Text Use Cases
- Full-Text 规范应该能够处理的实际场景。
-
XQuery 1.0 and XPath 2.0 Full-Text
- 主 full-text 文档,详细描述严格意义上的 XQuery 的全文扩展语言。
-
XQuery Update Facility Requirements
- XQuery 要求的能够对已有文档写入新数据以及对文档进行查询的功能。
-
Building a Tokenizer for XPath or XQuery
- 工作草案注释文件,选择了主 XQuery 1.0 文档中的一些语法资料进行解释。只对语言的实现者有意义。
这些文档(参阅 参考资料)
代表了大量工作。“XQuery 1.0: An XML Query Language” 是其中的关键,但其他文档也为 XQuery
成为良好的规范和全面支持的语言作出了贡献。据我所知,这是出自 W3C 的最复杂的一套规范(虽然 XML Schema
或许可与之相比,但那又是另一回事了)。
如果您对如此之多的文档感到惊讶和不知道从何处着手,我可以推荐两种方法。可以从核心的 XQuery 1.0
文档开始。它有一个很好的介绍性概述,详细介绍了该语言的很多特性。另一种方法是从选择一个 Use Cases 工作草案开始。该文档列举了
XQuery 能够发挥作用的一些实际场景。每个用例都针对特定的应用领域,并列出了用于该领域示例数据的一些 XQuery
代码。如果希望看到实际语法的具体例子,这些代码片段是非常有价值的。第三种方法是研究 Functions and Operators
工作草案中列出的很多内置函数,但采用这种方法最好对语言要有基本的理解。
最近几年关于这个领域出现了两本很好的图书,阐释
了该规范的各种细节,都是 Addison-Wesley 出版的:“XQuery from the Experts”
收集了来自该工作组成员的一些关于 XQuery 的技术文章,而 “XQuery: The XML Query Language” 则是
Microsoft 的 Michael Brundage 撰写的必读参考书(参阅 参考资料)。
BabelFish,您在哪儿?
XQuery 实际上是三种语言合为一体:
-
表层语法是其中最容易看到的语言,也是用户最可能接触到的。从很多方面来说,这个语言版本就是 XQuery。(参阅侧栏 语法:一个简单的例子 中关于表层语法的例子。)
-
一种基于 XML 的替代语法用另一种更便于机器处理的语言代替了表层语言。(参阅本文后面的 XQueryX。)
-
形式代数语言详细描述了 XQuery 处理程序的内部工作机制。
底层的形式主义
Data Model and Formal Semantics(数据模型和形式语义)工作草案共同为 XQuery 提供了精确的理论基础。这两个文档详细介绍了查询代数,这是用形式术语定义的一组精确定义,它定义了 XQuery 查询期望操作的核心实体以及各种语言操作符怎样使用那些操作数的公式。除非是查询引擎的实现者、有充足的经费保障或者单纯喜欢复杂的形式系统,否则不会对它感兴趣。
提供了一个让实现者能够将表面语法特性直接重新造型为底层代数的映射。正如一些厂商在 XML 商业展示会上所展示的那样,可以实现实际上与代数直接沟通的查询处理程序(虽然我认为这更多的是概念证明性的)。参考资料 中有一个链接指向其中一个基于代数的引擎的在线演示版本。
代数还提供了详细描述如何将复杂表达式优化及转变成更简单的等价形式的规则。我只能说(我不是语言学家,并且形式语义文档读起来并不轻松),它们都是好东西。特别是大型数据库供应商会赞赏一种从头开始设计并且经过优化而高效的查询语言结构。
代数还提供了附加类型信息的位置。XQuery 是强类型的:如果数据有相关的 XML Schema,处理程序就可以按照模式进行验证、并为查询引擎提供文档中节点数据类型的后模式验证信息集(PSVI)信
息,同时利用 “XML Schema Part 2: Datatypes”
中声明的类型和自定义的用户定义类型。代数同时具有静态和动态类型检查能力。比方说,引擎可以使用 PSVI
派生的类型信息在编译时静态地检查查询表达式的数据类型(分析查询的语法正确性时)。在这个周期中尽早地确定类型无效的查询,这样能够大大减少对大型数据
集进行很可能是昂贵的(和无结果的)搜索的需要。XQuery 规范中涉及到语言的语法和语义的大部分工作都与类型有关。
迁移到 XPath 2.0
XQuery
和 XPath 2.0 具有同样的公共数据模型,有点奇怪的数据模型文档标题也反映了这一点:“XQuery 1.0 and XPath 2.0
Data Model”(这也是工作组开始使用缩写词 XDM 表示数据模型的原因)。XPath 2.0 现在已经完全成熟。该数据模型描述了
XPath 处理程序所关心的 XML 文档中的核心信息,XPath step 操作的最终语法和语义基本上已经完成。全部规范为 Query
Language 工作组和 XSL 工作组共同所有,这两个工作组需要对 XPath 2.0
的未来达成一致。不论在政治上还是在技术上,这常常会带来挑战。不过,即便取得一致意见的道路崎岖不平,这两个工作组看来并没有表现出有多么不协调(至少
在外人眼里如此)。
为了说明从 XPath 1.0 转移到 2.0 的意义,仅仅举一个例子:XPath 1.0
是基于集合的表达式语言。XPath 1.0 的 4
种数据类型之一,节点集,仅仅是集合而已。按照定义,集合是无序的并且不含重复成员。另一方面,XPath 2.0
却是以序列为基础的。相比而言,XPath 2.0 中节点的序列(毫不奇怪,相应地被称为节点序列)是有序的,并且允许重复。用行话来说,这些差异的分歧存在于许多问题中,工作组需要独立和协作找出这些问题来,以便使他们都能够与 XPath 2.0 协调一致。
我们现在在何处?
所有实质性的遗留问题都已经解决。组成 XQuery 的七个关键文档按 W3C 的说法成为了 “候选推荐标准”,这意味着,用正式的术语来说,XQuery 现在被认为 “是稳定的且合适于实现”。
按
照正式的 W3C 推荐标准工作流程,以上 Last Call 阶段中提出的所有问题都已得到响应,工作组现在正寻找工业厂商去实际验证
XQuery
的主要特性是可实现的。为此,实现者必须对工作组提供的测试套件运行其实现。那些在候选推荐标准阶段没有被两个或更多厂商实现的特性,就有可能被从规范中
删除。当前有风险的特性包括:
- 静态类型化
- 模块
- 集合
- 静态类型化
- 普通的 XML 嵌入
- 复制名称空间(Copy-namespace)声明
XQueryX
XQueryX 是一种替代表层语言的基于 XML 语法的规范,也是较早加入 XQuery 文档家族的文件之一。XQuery 需求中有一条指出多种语法是允许的,看来工作组似乎是在两面下注:一种格式必须方便人类读写,另一种则要求能够用 XML 表达。XQueryX 是工作组对后一种需求的答案。
基于 XML 的查询表是具备 XML 所有显而易见、众所周知的优点:很容易使用标准工具解析、生成和询问查询的内容。这样做可能很有用,比方说,如果正在进行源代码级的优化或转换,就可能需要方便地检查查询中是否具有特定语法结构的能力。XML 非常适合这类工作。
XQueryX 基本上是该语言的形式语法到 XML 的一对一映射。由于语法的复杂性,使得 XQueryX 非常 冗长,基本上无法阅读。所幸的是,该语言的目标受众 —— 机器 —— 不会对此抱怨。清单 1 和 2 提供了分别用标准 XQuery 语法和 XQueryX 语法表示的简单查询表达式。要注意像类固醇一样的膨胀因子。
清单 1. 标准语法表示的简单查询
<bib> { for $b in doc("http://bstore1.example.com/bib.xml")/bib/book where $b/publisher = "Addison-Wesley" and $b/@year > 1991 return <book year="{ $b/@year }"> { $b/title } </book> } </bib>
|
清单 2 显示了等价的 XQueryX 查询。因为太长,省略了大约四分之三的篇幅。XQueryX 工作草案中的完整清单有 132 行之多:
清单 2. XQueryX 格式的查询(片段)
<?xml version="1.0"?> <xqx:module xmlns:xqx="http://www.w3.org/2005/XQueryX" ... > <xqx:mainModule> <xqx:queryBody> <xqx:elementConstructor> <xqx:tagName>bib</xqx:tagName> <xqx:elementContent> <xqx:flworExpr> <xqx:forClause> <xqx:forClauseItem> <xqx:typedVariableBinding> <xqx:varName>b</xqx:varName> </xqx:typedVariableBinding> <xqx:forExpr> <xqx:pathExpr> <xqx:argExpr> <xqx:functionCallExpr> <xqx:functionName>doc</xqx:functionName> <xqx:arguments> <xqx:stringConstantExpr> <xqx:value>http://bstore1.example.com/bib.xml</xqx:value> </xqx:stringConstantExpr> </xqx:arguments> </xqx:functionCallExpr> </xqx:argExpr> <xqx:stepExpr> <xqx:xpathAxis>child</xqx:xpathAxis> <xqx:nameTest>bib</xqx:nameTest> </xqx:stepExpr> <xqx:stepExpr> <xqx:xpathAxis>child</xqx:xpathAxis> <xqx:nameTest>book</xqx:nameTest> </xqx:stepExpr> </xqx:pathExpr> </xqx:forExpr> </xqx:forClauseItem> </xqx:forClause> ...
|
准备就绪,然后该何去何从?
2001
年 6 月,就在第一次重要的发布迭代之后,在我第一次撰写关于已有 XQuery 实现的总结文章时,当时还只有两种实现:我自己很早的一种实现和
Microsoft
的实现。于是我给自己找了点乐子,开玩笑说比尔盖茨和我成了市场竞争中的赛马师。那个时代已经过去了,经过四年和发布了更多工作草案之后,那个玩笑已经过
时了。现在差不多有四五十种实现了,还有大量的相关产品和工具。
如果要寻找一个实现,最好的地方就是 XML Query 主页(参阅 参考资料)。这个列表更新很快,随着兴趣和动机不断增强,随着规范越来越接近推荐标准状态,我预料将会不断出现新的实现。
语法:一个简单的例子
让我们通过实际的例子快速看一下 XQuery 的一些特性。下面是一个非常简单的查询,操作 Use Cases 文档中的规范示例文件。该查询展示了 XQuery 投影(在数据集中选择与所定标准匹配的节点子集)和转换(生成与正被查询的文档不同的输出文档)的能力。XQuery 允许在一个查询中同时指定要搜寻的内容和输出格式。
清单 3 显示了查询操作的文档的片段:
清单 3. 查询操作的文档片段
<bib> <book year="1994"> <title>TCP/IP Illustrated</title> <author><last>Stevens</last><first>W.</first></author> <publisher>Addison-Wesley</publisher> <price>65.95</price> </book> <book year="1992"> <title>Advanced Programming in the Unix environment</title> <author><last>Stevens</last><first>W.</first></author> <publisher>Addison-Wesley</publisher> <price>65.95</price> </book> <book year="2000"> <title>Data on the Web</title> <author><last>Abiteboul</last><first>Serge</first></author> <author><last>Buneman</last><first>Peter</first></author> <author><last>Suciu</last><first>Dan</first></author> </book> ... </bib>
|
现在希望得到 清单 4 所示的输出文档(经过一点美化):
清单 4. 输出文档
<results> <book authorCount="1"> <author>Stevens</author> </book> <book authorCount="1"> <author>Stevens</author> </book> <book authorCount="3"> <author>Abiteboul</author> <author>Buneman</author> <author>Suciu</author> </book> ... </results>
|
下面就是查询本身。它的任务是扫描查询文档中的所有图书,生成上面所显示的结果文档:在输出的每个新建 <book> 标记中包含计算出的 authorCount 属性;并丢弃原始文档中其他大部分信息,只保留每位作者的姓。
(注意,我在这里使用了术语 “查询文档”(单数)。这是一种简化,XQuery 数据模型也能处理文档集合以及文档片段。)
清单 5. 扫描查询文档中所有图书的查询
<results> { for $book in doc( "http://uri-for-book-dataset" )//book let $authors := $book/author return <book authorCount="{ count($authors) }"> { for $author in $authors return <author>{ $author/last/text() }</author> } </book> } </results>
|
清单 5 中,使用 doc() 函数使查询指向要查找的 XML 文档。它返回 XQuery 和 XPath 数据模型(XDM)词汇表中的 文档节点 。
剖析查询
下面是该查询的一些有趣特性:
for/let 表达式
该示例包含两个嵌套的 for 循环和一个 let 。外层的 for 循环遍历扩展路径表达式 doc(...)//book 得到的每个节点,并将每个 <book> 节点分别放到名为 $book 的变量中。let 表达式选择每本书的所有 <author> 子节点,并放到名为 $authors 的变量中。$authors 变量保存节点序列,$book 和 $author 变量都包含一个节点。
请务必留意,这些变量不是赋值的 而是绑定的。差别细微但很重要:变量一旦绑定,它的值就不可更改。这可以防止在运行中对变量重新赋值而造成糟糕的负面影响。另一个潜在的好处是,(在某种程度上)可以在处理期间重排包含变量的行,从而允许智能引擎优化它们的查询。
for 和 let 表达式是 FLWOR (读作 flower)表达式的子组件。这个缩写词代表其五个主要的成分子句:for -let -while -order by -return 。清单 6 中,FLWOR 表达式的形式语法为:
清单 6. 带有 for 和 let 子成分的 FLWOR 表达式
FLWORExpr ::= (ForClause | LetClause)+ WhereClause? "return" Expr
|
表明这是一个变化非常多的表达式类型,能够生成大量可能的查询实例。正如该生成所显示的那样,"return" 关键字后面的 Expr 项本身可以被另一个 FLWOR 表达式替代,因此,可以像不断加长的 LEGO 积木那样,将 FLWOR 表达式首尾相接,无限 地排成一行。能够用任何其他表达式类型替换 Expr 项,使 XQuery 具有可组合性 并给予它丰富的表达能力。XQuery 中有大量表达式类型,每一种可以插入需要更一般的 Expr 的地方。
一般来说,最终会有一个 return 语句结束 FLWOR 序列。在上面的例子中,增加的内部 return 是为了方便插入要输出的每个 <book> 的元素构造器。
元素构造器
这个查询包含三个元素构造器。通过将文字尖括号 XML 直接写入查询本身的正文中,在查询过程中动态生成元素 <results> 、<book> 和 <author> 。
在需要区分文字文本内容和元素构造器中需要计算的子表达式时,使用花括号({ 和 } )。比方说,如果我们编写 清单 7 所示的文字表达式则不需要用花括号来分隔内部标记和外部标记。
清单 7. 用括号区分内部和外部标记的代码
顺便提一句,花括号是在 2001 年 7 月修订表面语言语法时引入的。更早版本的语法不需要花括号。花括号是语言在成为推荐标准过程中不断修改和演化的一个好例子。
属性构造器
清单 8 中的代码显示了行内属性构造器的用法。count() 函数返回每本书中包含的 <author> 元素的个数。也要注意这里的花括号,用于将需要计算的表达式和周围的文字 XML 分开。在属性构造器中使用引号分隔属性值是规范演变过程中不断修改的另一个例子。
清单 8. 使用行内属性构造器
<book authorCount="{ count($authors) }" >
|
内置函数和运算符
count() 是内置函数的一个例子。“Functions and Operators” 草案列出了大约 12 类 225 种函数和运算符,用于构造和操作各种不同的数据类型,包括数字、字符串、布尔值、日期时间、qname、节点和序列。
清单 9 中的表达式通过 text() 运算符,使用从包围它的 <last> 元素中提取的姓氏来填充每个 <author> 元素的内容。如果直接使用 $author/last ,将同时得到包围的标记,这不是本例所希望的情况。
清单 9. 使用内置运算符 text()
<author>{ $author/last/text() }</author>
|
|
|
|
参考资料
学习
获得产品和技术
关于作者
|
|
|
Howard
Katz 生活在加拿大BC省的 Roberts Creek,他是 Fatdog Software 公司的所有者,该公司专门开发 XML
文档搜索软件。他是 XQEngine 的作者,这是一种基于 Java 的开放源码 XQuery 引擎。近 35
年来他一直是一位活跃的程序员(一直业绩良好),并长期为计算机行业刊物撰写技术文章。他曾为 Microsoft 撰写一个在线 Java 专栏,为
Apple 每月撰写一期关于面向对象编程的专栏文章。他曾任 “温哥华 XML 开发者协会”
的主持人。他和妻子夏天去海上划船,冬天去边远地区滑雪。您可以通过 howardk@fatdog.com 与 Howard 联系。
|