清单 8. 插入中文XML文档
INSERT INTO xmlbookcat VALUES
(0804834679, XMLPARSE (DOCUMENT '
三國演義
Lou
Guanzhong
羅貫中
Tuttle Publishing
8/1/2002
China
Han Dynasty
Empire
History
周末
Here begins our tale: The empire, long divided, must unite;
long united, must divide
天下大势, 分久必合 ,合久必分 :周末七国分争
,并入于秦 ;及秦灭之后, 汉分争, 又并入于汉
0804834679
'));
当数据库服务器收到这条 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 属性 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 ΔΨ 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érlíng,单词 é 已经被对应的十六进制值 é 替换,而 í 则被对应的十六进制值 í 替换。
示例输出:
<?xml version="1.0" encoding = "utf-8" ?>
<a>
Stérlí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(