跳转到主要内容

非 Unicode 数据库中的 XML

处理 DB2 for Linux, UNIX, and Windows 中 XML 数据中的代码页转换

Randall P. Spalten (rspalten@us.ibm.com), 顾问软件工程师, IBM
Randall Spalten 是加利福尼亚州圣何塞市硅谷实验室的一名顾问软件工程师。他于 1997 年加入 IBM,从事信息集成方面的工作。从那时起,他从事过 DB2 很多领域的工作,包括联邦、DDL 语义、两阶段提交处理和运行时,之后还转向 pureXML 支持。目前他在 pureXML Runtime 小组工作,主要负责非 Unicode 支持和运行时性能增强。
Preethi Vishwanath (pvishwa@us.ibm.com), 软件工程师, IBM
Preethi Vishwanath 是 DB2 XML Runtime 小组的软件开发人员。从 2006 年作为实习生加入 IBM 开始,她一直从事为非 Unicode 数据库提供 XML 支持方面的工作。她的职业兴趣包括分布式数据库设计、运行时和语言支持以及复制问题。她拥有圣何塞州立大学计算机科学硕士学位。

简介: 本文解释如何在 DB2® Viper 2 中的非 Unicode 数据库中使用 XML 本地数据类型。发现避免替换字符、查询结构指针和字符引用的使用的技巧,以及对产品附带的新的实用用户定义函数(UDF)的解释。

发布日期: 2007 年 9 月 13 日
级别: 中级
建议: 

V9 中的 XML 支持

在 DB2 9 中,将 XML 数据包括在数据库中的惟一方法是让数据库使用 UTF-8 码集。即使 XML 数据中只包含 ASCII 代码点,这个限制也仍然有效。XML 是以二进制格式存储在数据库中的,文本节点被存储为 UTF-8 代码点。在 DB2 9 中,不会对 XML 文档进行转换,因此必须以一系列的 UTF-8 字节的形式将 XML 文档插入到数据库中。 DB2 Viper 2 版解除了这一限制,即所有使用 pureXML 特性的数据库必须以 Unicode 方式创建。本文解释非 Unicode 数据库中的 XML 支持,以及如何在保证数据完整性的情况下安全地插入和查询 XML 文档。

例子场景:在不必迁移至 Unicode 码集的情况下迁移至 pureXML

假设您是一个已有的非 Unicode DB2 数据库的数据库管理员(DBA),该数据库包含一个用于编目图书馆中的书籍的表。目前,这个表具有以下形式:


清单 1. 示例关系模式
                
	CREATE TABLE BOOKCAT
		(TITLE           VARCHAR(400) NOT NULL,
		AUTHOR_LAST      VARCHAR(50) NOT NULL,
		AUTHOR_FIRST     VARCHAR(50),
		PUBLISHER        VARCHAR(100),
		DATE_PUB         DATE NOT NULL,
		ISBN             BIGINT NOT NULL PRIMARY KEY,
		KEYWORD1         CHAR(20),
		KEYWORD2         CHAR(20),
		FIRST_PARA       CLOB(2K));
      

然而,在非 Unicode 关系表中,对于表中的数据有一些限制。如果书的题目或文本包含当前数据库的字符集以外的字符会怎样?为什么要将这个表限制为每本书只有两个关键字?如果对于一个新的条目,关键字太大怎么办?如果一本书有一个未知的出版日期,或者有多个作者,怎么办?

这个模式有太多的限制,所以您决定探索如何以 XML 的形式存储数据,但是又不想将现有的数据库迁移至 UTF-8。 假设这个书籍数据库还要接纳来自世界各地的新书,这些书可能用中文、俄语和其他语言编写。您希望用英语和本地语言存储关于书的元数据(题目、作者、第一段)。每本书可以建模为一个 XML 文档,取决于这本书的语言,这个 XML 文档的编码方式可能不同。那么,如何在非 Unicode pureXML 数据库中插入、存储和查询这些文档呢?

非 Unicode 数据库中对 XML 的新的支持

在 DB2 Viper 2 中,用户现在可以在任何新的或已有的数据库中,在新的或已有的表中创建 XML 列。现在不再要求使用 Unicode 码集。创建 XML 类型的列的格式是:

CREATE TABLE <table_name> (<column_name1> XML);

请注意 CREATE TABLE 的语法仍然和以前一样。

例子:


清单 2. 创建 XML 类型的列
                
CREATE TABLE  xmlbookcat (
	ISBN BIGINT NOT NULL PRIMARY KEY,
	BOOK XML
);

为了修改一个表以添加一个 XML 列,必须能够合法地添加一个列到表中(即表中不能有不带 DEFAULT 子句的 NOT NULL 列,并且表中行的大小必须能够容纳新的 XML 列,这个列长度为 80 字节)。 DB2 允许用户在 CREATE TABLE 或 ALTER TABLE 的列声明中使用 CCSID UNICODE 或 CCSID ASCII 子句,以创建以不同于数据库其他部分的字符码集标识符(CCSID)存储的表。在非 Unicode 数据库中,将 XML 列添加到具有 CCSID UNICODE 属性的表中是非法的。那样的表不存储在数据库代码页中,在这些表中 XML 不受支持。如果尝试这么做,会产生 a -873(代码页不匹配)错误。在非 Unicode 数据库中创建一个新的用于存储 XML 数据的表。

例子:

CREATE TABLE untable(a VARCHAR(20)) CCSID UNICODE;
ALTER TABLE untable ADD xmlcol XML;

这将产生错误 SQL0873N Objects encoded with different encoding schemes cannot be referenced in the same SQL statement

XML 数据的存储

这些 XML 文档必须具有类似于下面的结构:


清单 3. XML 文档的结构
                
	<book>
		<title> . . . </title>
		<native_title> . . . </native_title>
		<author>
			<firstname> . . . </firstname>
			<lastname> . . . </lastname>
			<native_name> . . . </native_name>
		</author>
		<publisher> . . . </publisher>
		<date> . . . </date>
		<keyword> . . . </keyword>
		<first_para> . . . </first_para>
		<native_first_para> . . . </native_first_para>
		<ISBN> . . . </ISBN>
	</book>

由于不同的书籍可能用不同的语言编写,因此需要在同一个 DB2 列中存储包含不同字符集的 XML 文档。下面的例子显示,为了提高可读性,元素的名称采用 ASCII,但是元素中的值可以由任何有效的 Unicode 代码点组成。下面是这些 XML 文档的示例数据:


清单 4. XML 文档的示例数据
                
	<book>
		<title> A Tale of Two Cities </title>
		<author>
			<firstname> Charles </firstname>
			<lastname> Dickens </lastname>
		</author>
		<publisher> Signet Classics </publisher>
		<date> 7/1/1997 </date>
		<keyword> London </keyword>
		<keyword> Sydney Carton </keyword>
		<keyword> Paris </keyword>
		<first_para> It was the best of times, it was the worst of times </first_para>
		<ISBN> 0451526562 </ISBN>
	</book>


清单 5. XML 文档的示例数据
                
	<book>
		<title> Romance of the Three Kingdoms </title>
		<native_title> 三國演義 </native_title>
		<author>
			<firstname> Lou </firstname>
			<lastname> Guanzhong </lastname>
			<native_name> 羅貫中 </native_name>
		</author>
		<publisher> Tuttle Publishing </publisher>
		<date> 8/1/2002 </date>
		<keyword> China </keyword>
		<keyword> Han Dynasty </keyword>
		<keyword> Empire </keyword>
		<keyword> History </keyword>
		<keyword> 周末 </keyword>
		<first_para> Here begins our tale: The empire, long divided, must unite;
                           long united, must divide </first_para>
		<native_first_para> 天下大势, 分久必合 ,合久必分 :周末七国分争
                    ,并入于秦 ;及秦灭之后, 汉分争, 又并入于汉 </native_first_para>
		<ISBN> 0804834679 </ISBN>
	</book>


清单 6. XML 文档的示例数据
                
	<book>
		<title> War and Peace </title>
		<native_title> Война и мир </native_title>
		<author>
			<firstname> Leo </firstname>
			<lastname> Tolstoy </lastname>
		</author>
		<publisher> Modern Library </publisher>
		<date> 7/9/2002 </date>
		<keyword> Russia </keyword>
		<keyword> History </keyword>
		<keyword> Paris </keyword>
		<keyword> Napoleon </keyword>
		<keyword> Война </keyword>
		<first_para> Well, Prince, Genoa and Lucca are now no more than private estates 
                of the Bonaparte family </first_para>
		<native_first_para> Eh bien, mon prince. Gênes et Lucques ne sont plus que 
               des apanages, des поместья, de la famille Buonaparte </native_first_para>
		<ISBN> 0375760644 </ISBN>
	</book>

在了解如何在非 Unicode 数据库中安全地存储这些数据之前,需要研究解析和查询执行期间 XML 数据在 DB2 中的流程,包括从客户机到服务器,以及从服务器到 pureXML 特性。

XML 数据流

DB2 的 XML 组件在 Unicode 代码页范围内操作。因此,所有 XML 解析、串行化、查询执行和压缩都必须在 Unicode 代码页范围内进行。当数据库是 UTF-8 数据库时,可以在数据库代码页中操纵数据。如果数据库采用非 Unicode 代码页(例如 ISO-8859-1),那么所有 XML 文档、XQUERY 字符串和片段都必须经过代码页转换,从数据库代码页转换为 Unicode(特别地,转换为 UTF-8,代码页 1208)。

DB2 的 SQL 组件在数据库代码页中操作。所有 SQL 比较、SQL 查询执行以及 SQL 类型转换都必须在数据库代码页中进行。如果查询由一些 XML 活动和一些 SQL 活动组成,那么要在 “XML” 和 “SQL” 这两个 “世界” 之间进行转换,因为每个世界的数据必须在正确的代码页中。

将数据从数据库代码页转换为 UTF-8 代码页称为转换到 “XML 世界”,而将数据从 UTF-8 转换到数据库代码页则称为转换到 “SQL 世界”。这是服务器上 SQL/XML 或 XQUERY 执行期间惟一的两种代码页转换形式。

为了使非 Unicode 数据库支持 XML,建议使用一个 Unicode 客户机,因为客户机必须能够呈现输入 XML 和结果 XML 文档中的所有字符。如果哦客户机代码页和数据库代码页相同,那么客户机与服务器之间就不存在转换,于是客户机就可以呈现输入 XML 和结果 XML 文档中的所有字符。

客户机可以临时使用 DB2CODEPAGE 环境变量覆盖代码页。如果将这个变量设置为 1208 (UTF-8),那么 DB2 就可以像在代码页 1208 中那样与客户机通信。用这种方法覆盖客户机代码页可能导致不正确的输出或者不能打印的字符(例如将 Kanji 数据从一个 943 数据库发送到一个已经覆盖了 DB2CODEPAGE 的 ASCII 客户机)。在 Windows 中,可以在 Windows 控制面板中修改语言和环境设置,以覆盖客户机环境的代码页。

什么东西要经过转换?


图 1. 转换过程的例子
转换过程的例子

每当从 DB2 客户机发生任何请求到 DB2 服务器,所有字符数据(例如查询或 CHAR 主机变量)都要从客户机代码页转换为数据库代码页。不管请求中是否牵涉到 XML,这个过程都会发生。

查询的 XML 部分被转换到 XML 世界,以便 XML 解析器进行解析以及 XML 值的输出(串行化)。结果则被转换到 SQL 世界。这两个转换都可能引入替换字符,即当目标代码页不能呈现代码点时插入到字符流中的替换代码点。替换字符会导致服务器上损失数据完整性。本文将寻找避免替换字符的方法。

SQL 查询的结果和串行化 XML 在传回客户机之前被转换到 SQL 世界。在这个时候,如果串行化 XML 包含数据库代码页中不能表示的字符,或者在客户机代码页中不能表示的字符,就可能引入替换字符。下面讨论避免这两种替换字符陷阱的方法。

插入 XML 文档

至此,您已经知道了数据如何转换到正确的代码页,那么,这对于插入 XML 文档有何影响呢?为了将一个 XML 文档插入到表中,可以使用一个包含 XML 数据的字符串:


清单 7. 包含 XML 数据的字符串
                
INSERT INTO xmlbookcat VALUES 
(0451526562, XMLPARSE (DOCUMENT '<?xml version="1.0" encoding="ISO-8859-1" ?>
	<book>
		<title> A Tale of Two Cities </title>
		<author>
			<firstname> Charles </firstname>
			<lastname> Dickens </lastname>
		</author>
		<publisher> Signet Classics </publisher>
		<date> 7/1/1997 </date>
		<keyword> London </keyword>
		<keyword> Sydney Carton </keyword>
		<keyword> Paris </keyword>
		<first_para> It was the best of times, it was the worst of times </first_para>
		<ISBN> 0451526562 </ISBN>
	</book>'));
      

整个 INSERT 语句被从客户机代码页转换到数据库代码页,然后 XMLPARSE 文档字符串被转换到 UTF-8,以便解析为 XML。这些转换并没有引入替换字符,并且是可逆转的,所以在这个场景中可以安全地插入 XML 数据。插入用数据库代码页编码,并且只包括在数据库代码页中可以表示的字符的 XML 文档总是安全的。即使对于非 Unicode 数据库,这一点仍然适用。但是,如果想在同一个环境中插入中文 XML 文档,情况会怎样呢?


清单 8. 插入中文 XML 文档
                
INSERT INTO xmlbookcat VALUES 
(0804834679, XMLPARSE (DOCUMENT '<?xml version="1.0" encoding="ISO-8859-1" ?>
	<book>
		<title> Romance of the Three Kingdoms </title>
		<native_title> 三國演義 </native_title>
		<author>
			<firstname> Lou </firstname>
			<lastname> Guanzhong </lastname>
			<native_name> 羅貫中 </native_name>
		</author>
		<publisher> Tuttle Publishing </publisher>
		<date> 8/1/2002 </date>
		<keyword> China </keyword>
		<keyword> Han Dynasty </keyword>
		<keyword> Empire </keyword>
		<keyword> History </keyword>
		<keyword> 周末 </keyword>
		<first_para> Here begins our tale: The empire, long divided, must unite;
                           long united, must divide </first_para>
		<native_first_para> 天下大势, 分久必合 ,合久必分 :周末七国分争
                    ,并入于秦 ;及秦灭之后, 汉分争, 又并入于汉 </native_first_para>
		<ISBN> 0804834679 </ISBN>
	</book>'));
      

当数据库服务器收到这条 SQL 语句并将其转换到数据库代码页时会遇到一个问题,中文字符都不能在数据库代码页(只由 ISO-8859-1 中的 ASCII 字符组成)中表示。这时,DB2 用一个替换字符来替代这些缺失的字符。取决于目标代码页,这个替换字符的代码点可能有所不同,但是对于代码页 819 (ISO-8859-1),它是十六进制字符 0x1A。在客户机上,这通常显示为一个问号('?')。

替换字符的存在意味着数据完整性已经丢失。原始文档的中文字符被丢失,通过将数据流发回客户机不能获得这些字符。在任何其他代码页中,替换字符总是被转换为另一个替换字符。

在以上 XML 文档中,XML 解析器将书的本地题目解析为 "????"(每个 ? 字符为 0x1A 代码点)。这时一个合法的 XML 字符流,解析器并不会抛出错误,但是 XML 列中的数据现在却被毁坏。选择这个文档只能导致替换字符串被传回客户机。

注意,如果数据库是以代码页 943 创建的,由于该代码页支持 Shift-JIS 编码代码点,上述 INSERT 语句是安全的,并且文档会被插入到数据库中。对于俄文的例子,只有将 XML 文档插入支持 ISO-8859-5 (Cyrillic) 编码的数据库才是安全的。

如果用户经常使用包含大量数据库代码页中不能表示的代码点的 XML 文档,那么最完美的解决方案是使用 Unicode 数据库。在服务器上,解析或串行化不会引入替换字符。注意,将数据返回到非 Unicode 数据库仍然可能引入替换字符。但是,数据库中的 XML 文档不受影响。

使用 ENABLE_XMLCHAR 参数

在 Viper 2 版中,有一个新的 DBA 配置参数,该参数可以防止在 CHAR 类型的主机变量或字符串上执行 XML 解析。这个参数作用范围为整个数据库,它的名称为 ENABLE_XMLCHAR。默认情况下,所有数据库创建时 ENABLE_XMLCHAR 被设置为 TRUE,但是也可以将它设置为 FALSE,以便当用户在 CHAR、VARCHAR 或 CLOB 类型且非 BIT DATA 类型的主机变量上执行进行隐式或显式 XMLPARSE 操作的查询时,抛出一个错误(-20429)。在这个场景中,CHAR FOR BIT DATA、VARCHAR FOR BIT DATA 和 BLOB 都被接受。 更改这个数据库参数值的命令是:

db2 update db cfg for database name using ENABLE_XMLCHAR FALSE
      

之后,DBA 需要再循环实例,以使更改生效。 例如,如何用户在一个 ISO-8859-1 代码页的数据库上执行:

INSERT INTO XTAB VALUES (XMLPARSE(DOCUMENT 'I just 
    joined the ΔΨΠ fraternity!' PRESERVE WHITESPACE));
      

那么产生的插入到 XTAB 中的 XML 文档包含 3 个替换字符,用于替代希腊字母。如果 ENABLE_XMLCHAR 被设为 FALSE,那么这条 INSERT 语句将产生一个错误:

SQL20429N  The XML operation "XMLPARSE" is not allowed on strings 
      that are not FOR BIT DATA on this database.
      

这个错误告诉用户,对于 CHAR 类型 XMLPARSE 是不允许的。为了适当地将这个 XML 文档插入数据库,用户需要使用一个外部 API 将字符串 "I just joined the ΔΨΠ fraternity!" 绑定到一个 BLOB、CHAR FOR BIT DATA 或 VARCHAR FOR BIT DATA 主机变量。这样可以用与客户机代码页相同的代码点将文本插入 XML 列。

不安全 XML 文档的插入

插入包含不能在数据库代码页中表示的代码点的文档的惟一安全的方法是使用一个 BLOB 或 BIT DATA 主机变量。这不能从 CLP 中完成。

将 XML 绑定到一个 BLOB 主机变量有助于避免替换字符,因为 DB2 不会在 BLOB 数据上执行代码页转换。为了确定 BLOB 中的数据的代码页,有一定的规则。

如果 BLOB XML 流包含一个 byte-order mark (BOM),那么可以用它来确定 BLOB 中的数据所设的代码页。这被称作 “内部编码”,因为主机变量上没有 “外部编码”(即没有与主机变量相关联的代码页)。

如果 BLOB XML 流不包含 BOM,那么解析这个流,以便在 XML 数据头部中的 "<?xml version="1.0" encoding = "...encoding string">" 格式中发现 XML 属性 encoding。encoding 属性(如果存在)被映射为一个 CCSID,然后这个 CCSID 被用作内部编码。要查看有效的编码属性列表,请参阅 参考资料 小节。

如果没有 BOM,也没有给定的编码属性,则假定 BLOB 数据由 UTF-8 代码点组成。注意,在 XML 解析之前不会进行检查以确保 BLOB 只包含合法的 UTF-8 代码点。如果传入一个包含单独一个代码点 0xDB 的 BLOB 以将其解析为 XML,那么会导致一个错误,因为这不是合法的 UTF-8 代码点。对于 XML 可以包含的合法 UTF-8 代码点还有更多的限制。要了解关于这一方面的更多信息,请参阅 参考资料小节。

用于 “清理” XML 文档的 UDF

最后一种办法是将有问题的字符转换为十六进制字符引用(格式为 "&#xhhhh;",其中 hhhh 是字符的十六进制 Unicode UTF16 代码点)。十进制字符引用可以在任何 XML 片段中使用,并且在 XML 解析期间被实际的代码点替换。字符串 "I just joined the &#x394;&#x3A8;&#x380; fraternity!" 在 UTF-8 中相当于 "I just joined the ΔΨΠ fraternity!" 。

为了帮助用户转换 XML 文档,DB2 提供了两个 UDF,这两个 UDF 在 XML 文档被插入数据库之前测试和清理 XML 文档。

第一个 UDF 名为 TEST_XML,它以一个包含 XML 文档的 BLOB 为参数(较可取的方法是使用一个 BLOB_FILE,该 BLOB_FILE 引用一个用 UTF-8 码集编码的 XML 文本文件),并输出一个 Boolean 值。当 TEST_XML 被调用时,DB2 尝试将这个 BLOB 从 UTF-8 转换为数据库代码页,如果在转换期间没有遇到替换字符则返回 TRUE,如果遇到替换字符则返回 FALSE。这个函数不会插入文档或者修改 BLOB。它只是一个测试,看是否可以安全地将这个 XML 文档作为 CHAR、VARCHAR 或 CLOB 插入到数据库中,而不会损失数据完整性。

如果该 BLOB 是以 UTF-8 之外的任何代码页编码的,那么用户可以为 TEST_XML 指定输入 BLOB 的代码页,作为该函数的第二个可选参数。

第二个 UDF 执行转换操作,进行换码符替换。这个 UDF 名为 CLEAN_XML,它以一个包含 XML 文档(假设使用 UTF-8 代码页)的 BLOB 或 BLOB 文件为输入,并输出一个 CLOB,该 CLOB 包含使用数据库代码页的 XML 文档,其中每个不能安全地转换到数据库代码页的代码点被替换为换码符形式 "&#xhhhh",其中 hhhh 是字符的十六进制 UTF16 代码点。这个函数不会插入文档或者修改 BLOB,它可以与 INSERT/XMLPARSE 结合使用,以便将任何给定的 BLOB XML 文档安全地插入到任何数据库中,例如:

INSERT INTO XTAB VALUES (XMLPARSE(CLEAN_XML(:BLOB_HV)));

如果该 BLOB 是以 UTF-8 之外的任何代码页编码的,那么用户可以指定输入 BLOB 的代码页作为 CLEAN_XML 可选的第二个参数。

例子:

假设以下 XML 文档是作为输入 BLOB 提供给使用代码页 ISO-8859-7 的数据库的一个样本。(注意,下面的 Sterling 中使用的符号 "é" 和 "í" 不属于 ISO-8859-7)。

示例输入:

<?xml version="1.0" encoding="utf-8" ?>
      <a> Stérlíng
      </a>

在上述输入上运行 TEST_XML 所产生的输出值为 1,因为 Sterling 中使用的 "é" 和 "í" 符号不属于 ISO-8859-7,因此需要被替换。

类似地,运行 CLEAN_XML 将导致 Stérlíng 被转换为 St&#xE9;rl&#xED;ng,单词 é 已经被对应的十六进制值 &#xE9; 替换,而 í 则被对应的十六进制值 &#xED; 替换。

示例输出:

<?xml version="1.0" encoding = "utf-8" ?>
        <a>
 St&#xE9;rl&#xED;ng 
        </a>

现在,不管数据库使用何种代码页,都可以将这个 XML 插入到包含 XML 列的表中。 对于这两个 UDF,需要提醒的是,它们并不会将 BOLB 解析为 XML 文档。这两个函数中不会检查格式良好性,因此可能存在这样的情况,即元素标记名称或 XML 文档中其他非文本部分被更改为十六进制字符引用。如果 XML 文档中的元素标记名称或 XML 头部信息中包含不能在数据库代码页中呈现的代码点,那么从 CLEAN_XML 得到的文档不再是一个格式良好的 XML 文档。

非 Unicode 数据库中 XML 数据的查询

XML 串行化是将 Unicode XML 值绑定到主机变量,以便最终传回客户机。如果值被串行化为不是 FOR BIT DATA 的 CHAR、VARCHAR 或 CLOB 类型,那么会有一个到 SQL 世界的代码页转换。BLOB 串行化不会经过任何代码页转换,但是在串行化后得到的头部中会被赋予一个 "encoding" 属性,该属性默认为 UTF-8 编码。

如果代码点不能在数据库代码页中呈现,则可能引入替换字符。如果引入了这些字符,那么用户会收到一个错误 -20412,这与 XML 白皮书中的一条规则相符,即 XML 值的串行化不能引入替换字符。

现在看看这个场景中 SELECT 如何工作。

语法:

	SELECT XMLSERIALIZE(<XML column name> AS <datatype>) FROM <table_name>;

举一个简单的例子,获取 INSERT 的结果: SELECT XMLSERIALIZE(book AS CLOB(1M)) FROM xmlbookcat; 或者 XQUERY db2-fn:xmlcolumn('XMLBOOKCAT.BOOK')/book;

注意,默认情况下,XQUERY 会将它的结果串行化为一个 CLOB。从一个表中选择一个 XML 列也会导致结果被串行化为一个 CLOB。

CLOB 数据类型的代码页自然地等于数据库代码页,所以在串行化期间对 SQL 的回复有一个隐式的代码页转换。然后,结果被从数据库发送回 Unicode 客户机,相应地该回复被进行转换。最后得到使用客户机代码页的完全相同的数据,没有丢失数据。

但是,对于中文和俄文字符,XML Serializer 在输出中将它们替换为替换字符。如果发现被串行化的 XML 数据包含替换字符,则客户机抛出一个 -20412 错误。即使以 BLOB 形式插入中文或俄文文档,这些文档仍然不能串行化为 CLOB 或 CHAR 类型而不产生错误。

选择这种数据的惟一安全的方法是将它绑定到一个 BLOB 主机变量或一个 BLOB FILE 主机变量,以便将它写到一个 XML 文件中。如果可以在不串行化的情况下将数据返回到客户机,那么,通过直接绑定到一个 XML 输出主机变量,这是避免任何替换字符的最安全的方法。

使用字符引用

客户机可以手动地将字符引用嵌入到 XML 流中,以便将惟一的代码点传递到数据库中,而不必将 XML 数据绑定到一个 BLOB。例如,对于希腊 Delta 字符("Δ",Unicode 代码点为 U+0394,UTF-8 代码点为 0xCE94):

	INSERT INTO XMLBOOKCAT VALUES (1, xmlparse(document 
    '<?xml version="1.0"?><book>H&#x0394;llo</book>'));

该语句可以将 delta 字符插入任何数据库上的 XML 文档中。

	SELECT XMLSERIALIZE(XMLCOL AS BLOB(250)) FROM xmlbookcat;

给出以下输出:

	x'3C626F6F6B3E48CE946C6C6F3C2F626F6F6B3E'

对于 "<book>HΔllo</book>" 有 UTF-8 代码点。 注意,Delta 字符是通过十六进制字符引用(十六进制 394)传入的,它被正确地存储到 ISO8859-1 数据库中。串行化 Delta 字符可以得到字符的 UTF-8 表示(十六进制 CE94)。

替换字符

将 Kanji 字符插入到码集 8859-1 中

INSERT INTO xmlbookcat VALUES( 11, 
    XMLPARSE(document'<?xml version="1.0" encoding="utf-8" ?> 
       <book><pr番duct> </pr番duct></book>'));

product 中出现的 Kanji 字符 “番” 不属于数据库代码页 8859-1。在解析之前,该字符被一个替换字符替代,导致元素标记名称抛出一个 SQL 错误代码 "16110 XML Syntax error. Expected to find Attribute name"。

在 Kanji 字符上执行 XQUERY

XQUERY (番);

以上语句导致一个错误 -16002N "An XQuery expression has an unexpected token 0x1A following XQUERY"。

选择 Kanji XML 元素

SELECT XMLELEMENT(NAME "pr番duct") FROM xmlbookcat;

以上语句导致一个错误代码 "SQL 20275N The XML name "pr duct" is not valid",其中 Kanji 符号番已经被替换为一个空格。

混合代码页情况下的 XQUERY 例子

	XQUERY for $x in db2-fn:xmlcolumn('XMLBOOKCAT.BOOK')/book[keyword = 'Paris']	
		return $x/author;

这个例子扫描 book 文档,并返回具有关键字 "Paris" 的书的作者。它将返回对应于 "Leo Tolstoy" 和 "Charles Dickens" 的元素。注意,即使俄文书包含 Cyrillic Unicode 字符,在 ISO-8859-1 数据库上仍可以合法地执行这个 XQUERY,因为它只串行化那些与结果谓词匹配的元素。

	XQUERY for $x in db2-fn:xmlcolumn('XMLBOOKCAT.BOOK')/book[native_title = '三國演義']
		return $x/date;

如果数据库支持题目谓词中使用的 Shift_JIS 代码点,那么这个例子是合法的,但是在其他数据库上不能正确运行。即使只返回日期作为结果集,查询本身仍然被转换为数据库代码页,所以题目谓词必须被安全地译码。对于不支持这些代码点的数据库,通过用每个符号相应的字符引用替代代码点,这个查询可以在这种形式的 XML 文档上运行:

	XQUERY for $x in db2-fn:xmlcolumn('XMLBOOKCAT.BOOK')/book
          [native_title='&#x4E09;&#x570B;&#x6F14;&#x7FA9;']
		return $x/date;

虽然有些笨拙,但是这的确能正确地匹配服务器上的谓词,并返回预期的值。可以在任何传递给 DB2 的字符串中使用那样的字符引用来替代不能在数据库代码页中表示的代码点。

	XQUERY for $x in db2-fn:xmlcolumn('XMLBOOKCAT.BOOK')/book
		return $x/native_first_para;

如果在任何预期一个 UTF-8 码集的数据库上运行,则该查询将在客户机上给出一个 -20412 替换字符错误,因为它在同一个结果集中返回俄文、中文和英文代码点。实际上不存在包含所有这些值的非 Unicode 代码页。客户机还可以将结果集绑定到一个 BLOB,从而可以将字节安全地传递回客户机,而不必进行译码。

	XQUERY for $x in db2-fn:xmlcolumn('XMLBOOKCAT.BOOK')/book
		return $x/first_para;

这个查询返回每本书第一段的英文版本,即使那本书在“本地”的第一段中含有非 ASCII 字符也是如此。它可以安全地在任何数据库上运行。

现在研究代码页转换过程的更多分枝。SQL 解析器和 XML 解析器在 DB2 编译器中交互,但是它们预期使用不同代码页的输入。那么,当同一个请求中同时出现 XQUERY 和 SQL 时会怎样呢?

	XQUERY db2-fn:sqlquery("SELECT BOOK FROM XMLBOOKCAT")//book[date < '01/01/1999']; 

这里有一个包含嵌入式 SQL 请求的 XQUERY。首先,整个 XQUERY(包含 SQL 请求)被转换为 UTF-8。这个查询的 SQL 部分("SELECT BOOK FROM XMLBOOKCAT")被转换为 SQL。最终得到的查询是一个混合的、部分使用一种代码页、部分使用另一种代码页的查询。

清单 9 尝试比较 ISO-8859-1 数据库中的中文 Lou 字符(“羅”)。


清单 9. 比较中文 Lou 字符
                
SELECT XMLCAST(
XMLQUERY('$doc/book/author[native_name = "羅"]' 
PASSING xmlbookcat.book AS "doc") AS CLOB)
 	FROM xmlproductnuc;

这会产生一个涉及替换字符 0x1A 的错误 16002N 和一个 SQL 代码为 16002N 的错误,如清单 10 所示。


清单 10. SQL 代码 16002N
                
SQL16002N  An XQuery expression has an unexpected token "0x1A" following " = ". 
Expected tokens may include: "#x9|#xA|#xD|[#x20-#xD7FF]|[#xE000-#xFFFD]|
[#x10000-#x10FFFF]". Error QName=err:XPST0003.  SQLSTATE=10505

为了正确地将这个值与 XML 数据相比较,可以使用字符引用(例如 &#x7F85)。

对于像 XMLTABLE 这样的更复杂的场景:


清单 11. XMLTABLE 比较
                
SELECT x.* from XMLBOOKCAT xmlb, 
      XMLTABLE('$p/book' PASSING xmlb.book AS "p" COLUMNS 
"Title" 		VARCHAR(50)    PATH '//title',
"Publisher"		VARCHAR(32)    PATH '//publisher',
"Author_First"	VARCHAR(32)    PATH '//author/firstname',
"Author_Last"	        VARCHAR(32)    PATH '//author/lastname') AS X;

XML 文档被分解,每个绑定列(题目、出版社、名、姓)被转换为数据库代码页。然而,由于所有这些元素都包含 ASCII 字符,所以这是安全的。下面的 XMLTABLE 就不安全了:


清单 12. 不安全的 XMLTABLE 调用
                
SELECT x.* from XMLBOOKCAT xmlb, 
      XMLTABLE('$p/book' PASSING xmlb.book AS "p" COLUMNS 
"Title" 		VARCHAR(50)    PATH '//native_title',
"Publisher"		VARCHAR(32)    PATH '//publisher',
"Author"	      VARCHAR(32)    PATH '//author/native_name') AS X;

这将在非 Unicode 数据库中产生替换字符,因为它尝试将 "native_name" 和 "native_title" 元素中的 Unicode 字符转换为数据库代码页,以便存储在关系表的一个 VARCHAR 列中。对于非 Unicode 数据库中使用混合代码页的 XML 文档,不建议这么做。

结束语

如果想将 XML 添加到非 Unicode 数据库中,并且 XML 文档中有很多数据库代码页中不能表示的字符,那么建议将数据库迁移至 Unicode,并以此格式插入 XML 数据库。

如果 XML 文档只包含可以在数据库代码页中呈现的字符,那么用户可以直接向数据库插入和查询 XML,而不会受到限制或者引入替换字符,但是小心别插入新的包含数据库代码页之外字符的 XML 文档。

如果文档中有一些数据库代码页之外的字符,那么可以使用 UDF TEST_XML 进行检查,如果文档可以安全地插入到数据库中,则该函数返回 TRUE,反之,如果在将文档插入 Unicode 数据库之前,需要将它传递给 CLEAN_XML UDF 进行清理,则该函数返回 FALSE。

DBA 可以使用数据库配置参数 ENABLE_XMLCHAR 来限制用户通过字符类型插入 XML 文档。

从数据库中获取 XML 的最安全的方法是将值取出到一个 XML 本地类型主机变量中。BLOB 也是一种在不进行代码页转换的情况下取 XML 数据的安全的方法。

当对包含数据库代码页之外字符的 XML 数据发出查询时,尝试使用十进制或十六进制字符引用。


参考资料

学习

获得产品和技术

  • 下载 Viper 2 代码 并参与开放的 beta 测试。

  • 下载 IBM 产品评估版,获得来自 DB2、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。

讨论

作者简介

Randall Spalten 是加利福尼亚州圣何塞市硅谷实验室的一名顾问软件工程师。他于 1997 年加入 IBM,从事信息集成方面的工作。从那时起,他从事过 DB2 很多领域的工作,包括联邦、DDL 语义、两阶段提交处理和运行时,之后还转向 pureXML 支持。目前他在 pureXML Runtime 小组工作,主要负责非 Unicode 支持和运行时性能增强。

Preethi Vishwanath 是 DB2 XML Runtime 小组的软件开发人员。从 2006 年作为实习生加入 IBM 开始,她一直从事为非 Unicode 数据库提供 XML 支持方面的工作。她的职业兴趣包括分布式数据库设计、运行时和语言支持以及复制问题。她拥有圣何塞州立大学计算机科学硕士学位。

建议



商标  |  My developerWorks 使用条款与条件

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management, XML
ArticleID=255463
ArticleTitle=非 Unicode 数据库中的 XML
publish-date=09132007
author1-email=rspalten@us.ibm.com
author1-email-cc=
author2-email=pvishwa@us.ibm.com
author2-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签< /strong> 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。