本节讨论 MySQL 中的 XML 和相关功能。
可以在 mysql 和 mysqldump 客户端中使用 --xml
选项从 MySQL 中获取 XML 格式的输出。请参阅 第 6.5.1 节,“mysql — MySQL 命令行客户端” 和 第 6.5.4 节,“mysqldump — 数据库备份程序”。
提供了两个基本 XPath 1.0(XML 路径语言,版本 1.0)功能的函数。XPath 语法和使用的一些基本信息将在本节后面提供;然而,对这些主题的深入讨论超出了本手册的范围,您应该参考 XML 路径语言(XPath)1.0 标准 获取详细信息。对于 XPath 新手或需要刷新基础知识的人,Zvon.org XPath 教程 是一个有用的资源,提供多种语言。
这些函数仍在开发中。在 MySQL 8.3 及更高版本中,我们继续改进这些和其他 XML 和 XPath 功能方面的内容。您可以在 MySQL XML 用户论坛 中讨论这些内容,提出问题,并从其他用户那里获取帮助。
使用这些函数的 XPath 表达式支持用户变量和存储程序局部变量。用户变量弱检查;存储程序局部变量强检查(也见 Bug #26518):
-
用户变量(弱检查)。 使用语法
$@
(即用户变量)的变量不进行检查。服务器不会发出警告或错误,如果变量类型不正确或之前未被赋值。这也意味着用户完全负责任何打字错误,因为如果(例如)使用variable_name
$@myvairable
而不是$@myvariable
,则不会发出警告。示例:
mysql> SET @xml = '<a><b>X</b><b>Y</b></a>'; Query OK, 0 rows affected (0.00 sec) mysql> SET @i =1, @j = 2; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @i, ExtractValue(@xml, '//b[$@i]'); +------+--------------------------------+ | @i | ExtractValue(@xml, '//b[$@i]') | +------+--------------------------------+ | 1 | X | +------+--------------------------------+ 1 row in set (0.00 sec) mysql> SELECT @j, ExtractValue(@xml, '//b[$@j]'); +------+--------------------------------+ | @j | ExtractValue(@xml, '//b[$@j]') | +------+--------------------------------+ | 2 | Y | +------+--------------------------------+ 1 row in set (0.00 sec) mysql> SELECT @k, ExtractValue(@xml, '//b[$@k]'); +------+--------------------------------+ | @k | ExtractValue(@xml, '//b[$@k]') | +------+--------------------------------+ | NULL | | +------+--------------------------------+ 1 row in set (0.00 sec)
-
存储程序中的变量(强检查)。 使用语法
$
的变量可以在存储程序中声明和使用这些函数。当它们在存储程序中被调用时,这些变量是强检查的,检查类型和值。variable_name
示例:
mysql> DELIMITER | mysql> CREATE PROCEDURE myproc () -> BEGIN -> DECLARE i INT DEFAULT 1; -> DECLARE xml VARCHAR(25) DEFAULT '<a>X</a><a>Y</a><a>Z</a>'; -> -> WHILE i < 4 DO -> SELECT xml, i, ExtractValue(xml, '//a[$i]'); -> SET i = i+1; -> END WHILE; -> END | Query OK, 0 rows affected (0.01 sec) mysql> DELIMITER ; mysql> CALL myproc(); +--------------------------+---+------------------------------+ | xml | i | ExtractValue(xml, '//a[$i]') | +--------------------------+---+------------------------------+ | <a>X</a><a>Y</a><a>Z</a> | 1 | X | +--------------------------+---+------------------------------+ 1 row in set (0.00 sec) +--------------------------+---+------------------------------+ | xml | i | ExtractValue(xml, '//a[$i]') | +--------------------------+---+------------------------------+ | <a>X</a><a>Y</a><a>Z</a> | 2 | Y | +--------------------------+---+------------------------------+ 1 row in set (0.01 sec) +--------------------------+---+------------------------------+ | xml | i | ExtractValue(xml, '//a[$i]') | +--------------------------+---+------------------------------+ | <a>X</a><a>Y</a><a>Z</a> | 3 | Z | +--------------------------+---+------------------------------+ 1 row in set (0.01 sec)
参数. 在存储例程中作为参数传递的变量也受到强检查。
包含用户变量或存储程序局部变量的表达式必须(除了符号外)遵守 XPath 1.0 规范中关于变量的规则。
用于存储 XPath 表达式的用户变量被视为空字符串。因此,无法将 XPath 表达式存储为用户变量。(Bug #32911)
-
ExtractValue(
xml_frag
,xpath_expr
)ExtractValue()
接受两个字符串参数,一个 XML 标记片段xml_frag
和一个 XPath 表达式xpath_expr
(也称为 定位器);它返回元素或元素的第一个文本节点的文本(CDATA
),这些元素或元素由 XPath 表达式匹配。使用这个函数相当于在追加
/text()
后使用匹配xml_expr
。换言之,ExtractValue('<a><b>Sakila</b></a>', '/a/b')
和ExtractValue('<a><b>Sakila</b></a>', '/a/b/text()')
产生相同的结果。如果xml_frag
或xml_expr
是NULL
,函数返回NULL
。如果找到多个匹配,函数将返回每个匹配元素的第一个子文本节点的内容(以匹配顺序)作为一个空格分隔的字符串。
如果没有找到匹配的文本节点(包括隐式的
/text()
)—无论是什么原因,只要xml_expr
有效,xml_frag
由正确嵌套和闭合的元素组成—一个空字符串将被返回。不区分空元素匹配和无匹配。这是设计的如此。如果您需要确定
xml_frag
中是否没有找到匹配元素,您应该测试使用 XPathcount()
函数的表达式结果。例如,两个语句都返回一个空字符串,如下所示:mysql> SELECT ExtractValue('<a><b/></a>', '/a/b'); +-------------------------------------+ | ExtractValue('<a><b/></a>', '/a/b') | +-------------------------------------+ | | +-------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT ExtractValue('<a><c/></a>', '/a/b'); +-------------------------------------+ | ExtractValue('<a><c/></a>', '/a/b') | +-------------------------------------+ | | +-------------------------------------+ 1 row in set (0.00 sec)
然而,您可以使用以下方法确定是否实际找到匹配元素:
mysql> SELECT ExtractValue('<a><b/></a>', 'count(/a/b)'); +-------------------------------------+ | ExtractValue('<a><b/></a>', 'count(/a/b)') | +-------------------------------------+ | 1 | +-------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT ExtractValue('<a><c/></a>', 'count(/a/b)'); +-------------------------------------+ | ExtractValue('<a><c/></a>', 'count(/a/b)') | +-------------------------------------+ | 0 | +-------------------------------------+ 1 row in set (0.01 sec)
ImportantExtractValue()
只返回CDATA
,不返回匹配标签中的任何标签或内容(见下面的示例中返回的val1
)。mysql> SELECT -> ExtractValue('<a>ccc<b>ddd</b></a>', '/a') AS val1, -> ExtractValue('<a>ccc<b>ddd</b></a>', '/a/b') AS val2, -> ExtractValue('<a>ccc<b>ddd</b></a>', '//b') AS val3, -> ExtractValue('<a>ccc<b>ddd</b></a>', '/b') AS val4, -> ExtractValue('<a>ccc<b>ddd</b><b>eee</b></a>', '//b') AS val5; +------+------+------+------+---------+ | val1 | val2 | val3 | val4 | val5 | +------+------+------+------+---------+ | ccc | ddd | ddd | | ddd eee | +------+------+------+------+---------+
该函数使用当前 SQL 排序规则来执行
contains()
比较,执行与其他字符串函数(如CONCAT()
)相同的排序聚合,考虑到参数的排序可强制性;见 第 12.8.4 节,“表达式中的排序可强制性”,了解该行为的规则。(以前,总是使用二进制,即大小写敏感的比较。)
如果
xml_frag
包含未正确嵌套或闭合的元素,将返回NULL
,并生成警告,如下面的示例所示:mysql> SELECT ExtractValue('<a>c</a><b', '//a'); +-----------------------------------+ | ExtractValue('<a>c</a><b', '//a') | +-----------------------------------+ | NULL | +-----------------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Warning Code: 1525 Message: Incorrect XML value: 'parse error at line 1 pos 11: END-OF-INPUT unexpected ('>' wanted)' 1 row in set (0.00 sec) mysql> SELECT ExtractValue('<a>c</a><b/>', '//a'); +-------------------------------------+ | ExtractValue('<a>c</a><b/>', '//a') | +-------------------------------------+ | c | +-------------------------------------+ 1 row in set (0.00 sec)
-
UpdateXML(
xml_target
,xml_expr
,new_xml
)该函数将给定的 XML 标记片段
xml_target
中的一个部分替换为新的 XML 片段new_xml
,然后返回更改后的 XML。被替换的xml_target
部分匹配用户提供的 XPath 表达式xml_expr
。如果没有找到匹配的表达式
xml_expr
,或者找到多个匹配,函数将返回原始的xml_target
XML 片段。所有三个参数都应该是字符串。如果UpdateXML()
的任何参数为NULL
,函数将返回NULL
。mysql> SELECT -> UpdateXML('<a><b>ccc</b><d></d></a>', '/a', '<e>fff</e>') AS val1, -> UpdateXML('<a><b>ccc</b><d></d></a>', '/b', '<e>fff</e>') AS val2, -> UpdateXML('<a><b>ccc</b><d></d></a>', '//b', '<e>fff</e>') AS val3, -> UpdateXML('<a><b>ccc</b><d></d></a>', '/a/d', '<e>fff</e>') AS val4, -> UpdateXML('<a><d></d><b>ccc</b><d></d></a>', '/a/d', '<e>fff</e>') AS val5 -> \G *************************** 1. row *************************** val1: <e>fff</e> val2: <a><b>ccc</b><d></d></a> val3: <a><e>fff</e><d></d></a> val4: <a><b>ccc</b><e>fff</e></a> val5: <a><d></d><b>ccc</b><d></d></a>
XPath 语法和用法的深入讨论超出了本手册的范围。请参阅 XML Path Language (XPath) 1.0 规范 获取权威信息。对于 XPath 新手或需要复习基础知识的人,Zvon.org XPath 教程 是一个有用的资源,提供多种语言。
以下是某些基本 XPath 表达式的描述和示例:
-
/
tag
如果且仅当
<
是根元素时匹配。tag
/>示例:
/a
在<a><b/></a>
中有匹配,因为它匹配最外层(根)标签。它不匹配<b><a/></b>
中的内部a
元素,因为在这种情况下它是另一个元素的子元素。 -
/
tag1
/tag2
匹配
<
如果且仅当它是tag2
/><
的子元素,并且tag1
/><
是根元素。tag1
/>示例:
/a/b
匹配 XML 片段<a><b/></a>
中的b
元素,因为它是根元素a
的子元素。它不匹配<b><a/></b>
,因为在这里,b
是根元素(因此不是其他元素的子元素)。也不匹配<a><c><b/></c></a>
;这里,b
是a
的后代,但不是实际的子元素。这个构造可以扩展到三个或更多元素。例如,XPath 表达式
/a/b/c
匹配 XML 片段<a><b><c/></b></a>
中的c
元素。 -
//
tag
匹配任何实例的
<
。tag
>示例:
//a
匹配以下任何一个中的a
元素:<a><b><c/></b></a>
;<c><a><b/></a></b>
;<c><b><a/></b></c>
。//
可以与/
结合使用。例如,//a/b
匹配以下任何一个中的b
元素:<a><b/></a>
或<c><a><b/></a></c>
。Note//
等同于tag
/descendant-or-self::*/
。一个常见的错误是将其与tag
/descendant-or-self::
混淆,尽管后者的结果可能非常不同,如下所示:tag
mysql> SET @xml = '<a><b><c>w</c><b>x</b><d>y</d>z</b></a>'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @xml; +-----------------------------------------+ | @xml | +-----------------------------------------+ | <a><b><c>w</c><b>x</b><d>y</d>z</b></a> | +-----------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT ExtractValue(@xml, '//b[1]'); +------------------------------+ | ExtractValue(@xml, '//b[1]') | +------------------------------+ | x z | +------------------------------+ 1 row in set (0.00 sec) mysql> SELECT ExtractValue(@xml, '//b[2]'); +------------------------------+ | ExtractValue(@xml, '//b[2]') | +------------------------------+ | | +------------------------------+ 1 row in set (0.01 sec) mysql> SELECT ExtractValue(@xml, '/descendant-or-self::*/b[1]'); +---------------------------------------------------+ | ExtractValue(@xml, '/descendant-or-self::*/b[1]') | +---------------------------------------------------+ | x z | +---------------------------------------------------+ 1 row in set (0.06 sec) mysql> SELECT ExtractValue(@xml, '/descendant-or-self::*/b[2]'); +---------------------------------------------------+ | ExtractValue(@xml, '/descendant-or-self::*/b[2]') | +---------------------------------------------------+ | | +---------------------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT ExtractValue(@xml, '/descendant-or-self::b[1]'); +-------------------------------------------------+ | ExtractValue(@xml, '/descendant-or-self::b[1]') | +-------------------------------------------------+ | z | +-------------------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT ExtractValue(@xml, '/descendant-or-self::b[2]'); +-------------------------------------------------+ | ExtractValue(@xml, '/descendant-or-self::b[2]') | +-------------------------------------------------+ | x | +-------------------------------------------------+ 1 row in set (0.00 sec)
-
*
运算符充当一个“通配符”,匹配任何元素。例如,表达式/*/b
匹配以下任何一个中的b
元素:<a><b/></a>
或<c><b/></c>
。然而,该表达式不匹配<b><a/></b>
片段,因为b
必须是某个其他元素的子元素。通配符可以在任何位置使用:表达式/*/b/*
匹配任何b
元素的子元素,该元素不是根元素。 -
您可以使用
|
(UNION
) 运算符匹配多个定位器。例如,表达式//b|//c
匹配所有b
和c
元素在 XML 目标中。 -
也可以根据元素的某个或多个属性的值来匹配元素。这可以使用语法
。例如,表达式tag
[@attribute
="value
"]//b[@id="idB"]
匹配 XML 片段<a><b id="idA"/><c/><b id="idB"/></a>
中的第二个b
元素。要匹配具有
的任何元素,请使用 XPath 表达式attribute
="value
"//*[
。attribute
="value
"]要过滤多个属性值,只需连续使用多个属性比较子句。例如,表达式
//b[@c="x"][@d="y"]
匹配元素<b c="x" d="y"/>
。要查找具有相同属性的多个值的元素,可以使用
|
运算符连接多个定位器。例如,要匹配所有b
元素,其中c
属性的值为 23 或 17,请使用表达式//b[@c="23"]|//b[@c="17"]
。您也可以使用逻辑or
运算符://b[@c="23" or @c="17"]
。Notedifference between
or
和|
是or
连接条件,而|
连接结果集。
XPath 限制。 这些函数支持的 XPath 语法目前存在以下限制:
-
节点集到节点集的比较(例如
'/a/b[@c=@d]'
)不支持。 -
所有标准 XPath 比较运算符都支持。(Bug #22823)
-
相对定位器表达式在根节点的上下文中解析。例如,考虑以下查询和结果:
mysql> SELECT ExtractValue( -> '<a><b c="1">X</b><b c="2">Y</b></a>', -> 'a/b' -> ) AS result; +--------+ | result | +--------+ | X Y | +--------+ 1 row in set (0.03 sec)
在这种情况下,定位器
a/b
解析为/a/b
。相对定位器也支持谓词中。在以下示例中,
d[../@c="1"]
解析为/a/b[@c="1"]/d
:mysql> SELECT ExtractValue( -> '<a> -> <b c="1"><d>X</d></b> -> <b c="2"><d>X</d></b> -> </a>', -> 'a/b/d[../@c="1"]') -> AS result; +--------+ | result | +--------+ | X | +--------+ 1 row in set (0.00 sec)
-
以表达式开头的定位器,包括变量引用、文字、数字和标量函数调用,不允许使用,并将导致错误。
-
操作符
::
不支持与以下节点类型的组合:-
axis
::comment() -
axis
::text() -
axis
::processing-instructions() -
axis
::node()
然而,名称测试(例如
和axis
::name
) 是支持的,如以下示例所示:axis
::*mysql> SELECT ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::b'); +-------------------------------------------------------+ | ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::b') | +-------------------------------------------------------+ | x | +-------------------------------------------------------+ 1 row in set (0.02 sec) mysql> SELECT ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::*'); +-------------------------------------------------------+ | ExtractValue('<a><b>x</b><c>y</c></a>','/a/child::*') | +-------------------------------------------------------+ | x y | +-------------------------------------------------------+ 1 row in set (0.01 sec)
-
-
“上下” 导航不支持在路径将导致 ““上” 根元素的情况下。这是说,你不能使用表达式来匹配当前元素的祖先元素的后代,其中一个或多个祖先元素也是根元素的祖先元素(参见 Bug #16321)。
-
以下 XPath 函数不支持或存在已知问题:
-
id()
-
lang()
-
local-name()
-
name()
-
namespace-uri()
-
normalize-space()
-
starts-with()
-
string()
-
substring-after()
-
substring-before()
-
translate()
-
-
以下轴不支持:
-
following-sibling
-
following
-
preceding-sibling
-
preceding
-
传递给 ExtractValue()
和 UpdateXML()
的 XPath 表达式可以包含冒号字符(:
)在元素选择器中,以便与使用 XML 命名空间符号的标记一起使用。例如:
mysql> SET @xml = '<a>111<b:c>222<d>333</d><e:f>444</e:f></b:c></a>';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT ExtractValue(@xml, '//e:f');
+-----------------------------+
| ExtractValue(@xml, '//e:f') |
+-----------------------------+
| 444 |
+-----------------------------+
1 row in set (0.00 sec)
mysql> SELECT UpdateXML(@xml, '//b:c', '<g:h>555</g:h>');
+--------------------------------------------+
| UpdateXML(@xml, '//b:c', '<g:h>555</g:h>') |
+--------------------------------------------+
| <a>111<g:h>555</g:h></a> |
+--------------------------------------------+
1 row in set (0.00 sec)
这类似于 Apache Xalan 和其他解析器所允许的内容,并且比要求命名空间声明或使用 namespace-uri()
和 local-name()
函数要简单得多。
错误处理。 对于 ExtractValue()
和 UpdateXML()
,XPath 定位器必须有效,且要搜索的 XML 必须由正确嵌套和关闭的元素组成。如果定位器无效,将生成错误:
mysql> SELECT ExtractValue('<a>c</a><b/>', '/&a');
ERROR 1105 (HY000): XPATH syntax error: '&a'
如果 xml_frag
不是由正确嵌套和关闭的元素组成,将返回 NULL
并生成警告,如以下示例所示:
mysql> SELECT ExtractValue('<a>c</a><b', '//a');
+-----------------------------------+
| ExtractValue('<a>c</a><b', '//a') |
+-----------------------------------+
| NULL |
+-----------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
Level: Warning
Code: 1525
Message: Incorrect XML value: 'parse error at line 1 pos 11:
END-OF-INPUT unexpected ('>' wanted)'
1 row in set (0.00 sec)
mysql> SELECT ExtractValue('<a>c</a><b/>', '//a');
+-------------------------------------+
| ExtractValue('<a>c</a><b/>', '//a') |
+-------------------------------------+
| c |
+-------------------------------------+
1 row in set (0.00 sec)
用作 UpdateXML()
的第三个参数的替换 XML 不 检查是否仅由正确嵌套和关闭的元素组成。
XPath 注入。 代码注入 发生在恶意代码被引入系统以获取未经授权的特权和数据时。这基于开发人员对用户输入数据类型和内容的假设。XPath 也不是例外。
一种常见的情况是应用程序通过匹配 XML 文件中的登录名和密码来处理授权,使用类似于以下 XPath 表达式:
//user[login/text()='neapolitan' and password/text()='1c3cr34m']/attribute::id
这相当于 SQL 语句,如下所示:
SELECT id FROM users WHERE login='neapolitan' AND password='1c3cr34m';
使用 XPath 的 PHP 应用程序可能会像这样处理登录过程:
<?php
$file = "users.xml";
$login = $POST["login"];
$password = $POST["password"];
$xpath = "//user[login/text()=$login and password/text()=$password]/attribute::id";
if( file_exists($file) )
{
$xml = simplexml_load_file($file);
if($result = $xml->xpath($xpath))
echo "You are now logged in as user $result[0].";
else
echo "Invalid login name or password.";
}
else
exit("Failed to open $file.");
?>
没有对输入进行检查。这意味着恶意用户可以通过输入 ' or 1=1
来“短路”测试,结果是 $xpath
被评估为如下所示:
//user[login/text()='' or 1=1 and password/text()='' or 1=1]/attribute::id
由于方括号中的表达式总是评估为 true
,因此它实际上与以下表达式相同,该表达式匹配 XML 文档中的每个 user
元素的 id
属性:
//user/attribute::id
可以通过将变量名引号括起来来避免这种攻击,从而强制将 Web 表单传递的值转换为字符串:
$xpath = "//user[login/text()='$login' and password/text()='$password']/attribute::id";
这与防止 SQL 注入攻击的策略相同。一般来说,防止 XPath 注入攻击的实践与防止 SQL 注入攻击相同:
-
永远不要在应用程序中接受未经测试的用户数据。
-
检查所有用户提交的数据的类型;拒绝或转换错误类型的数据。
-
检查数字数据的范围值;截断、四舍五入或拒绝超出范围的值。检查字符串中的非法字符,并删除或拒绝包含这些字符的输入。
-
不要输出可能为未经授权的用户提供线索的明确错误消息;将其记录到文件或数据库表中。
正如 SQL 注入攻击可以用于获取数据库架构信息一样,XPath 注入也可以用于遍历 XML 文件以揭露其结构,如 Amit Klein 的论文 Blind XPath Injection(PDF 文件,46KB)中所讨论的那样。
还需要检查发送回客户端的输出。考虑使用 MySQL ExtractValue()
函数时会发生什么:
mysql> SELECT ExtractValue(
-> LOAD_FILE('users.xml'),
-> '//user[login/text()="" or 1=1 and password/text()="" or 1=1]/attribute::id'
-> ) AS id;
+-------------------------------+
| id |
+-------------------------------+
| 00327 13579 02403 42354 28570 |
+-------------------------------+
1 row in set (0.01 sec)
因为 ExtractValue()
将多个匹配项作为单个空格分隔的字符串返回,这次注入攻击将 users.xml
中的所有有效 ID 提供给用户作为单行输出。作为额外的安全措施,您还应该在返回给用户之前测试输出。以下是一个简单的示例:
mysql> SELECT @id = ExtractValue(
-> LOAD_FILE('users.xml'),
-> '//user[login/text()="" or 1=1 and password/text()="" or 1=1]/attribute::id'
-> );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT IF(
-> INSTR(@id, ' ') = 0,
-> @id,
-> 'Unable to retrieve user ID')
-> AS singleID;
+----------------------------+
| singleID |
+----------------------------+
| Unable to retrieve user ID |
+----------------------------+
1 row in set (0.00 sec)
总之,安全地将数据返回给用户的指南与接受用户输入相同。这些可以总结为:
-
始终测试输出数据的类型和可接受值。
-
永远不要允许未经授权的用户查看可能提供应用程序信息的错误消息。