本節討論 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.4 及更高版本中 XML 和 XPath 功能的這些和其他方面。您可以在MySQL XML 使用者論壇中討論這些內容、提出相關問題並從其他使用者取得協助。
與這些函數一起使用的 XPath 表達式支援使用者變數和本機儲存程式變數。使用者變數會進行弱式檢查;儲存程式的本機變數會進行強式檢查 (另請參閱錯誤 #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 表達式中使用的變數也必須經過強式檢查。
包含使用者變數或儲存程式本機變數的表達式,除了符號表示法以外,其餘部分必須符合 XPath 1.0 規格中給定的包含變數的 XPath 表達式規則。
用於儲存 XPath 表達式的使用者變數會被視為空字串。因此,無法將 XPath 表達式儲存為使用者變數。(錯誤 #32911)
ExtractValue(xml_frag,xpath_expr)ExtractValue()接受兩個字串引數,一個是 XML 標記片段xml_frag,另一個是 XPath 表達式xpath_expr(也稱為 定位器);它會傳回 XPath 表達式所比對的元素或元素的第一個文字節點的文字 (CDATA)。使用此函數相當於在使用
xpath_expr後附加/text()來執行比對。換句話說,ExtractValue('<a><b>Sakila</b></a>', '/a/b')和ExtractValue('<a><b>Sakila</b></a>', '/a/b/text()')會產生相同的結果。如果xml_frag或xpath_expr為NULL,則函數會傳回NULL。如果找到多個相符項,則每個相符元素的第一個子文字節點的內容會以單一、空格分隔的字串形式傳回 (依相符順序)。
如果找不到該表達式的相符文字節點 (包括隱含的
/text()) — 無論任何原因,只要xpath_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)重要事項ExtractValue()僅返回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,xpath_expr,new_xml)此函數會以新的 XML 片段
new_xml取代指定 XML 標記片段xml_target的單一部分,然後傳回變更後的 XML。xml_target中要取代的部分會與使用者提供的 XPath 表達式xpath_expr相符。如果找不到符合
xpath_expr的表達式,或找到多個相符項,則函數會傳回原始的xml_targetXML 片段。所有三個引數都應為字串。如果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/><。tag/>範例:
/a在<a><b/></a>中有相符項,因為它符合最外層(根)標籤。它與<b><a/></b>中的內部a元素不符,因為在此情況下,它是另一個元素的子元素。/tag1/tag2當且僅當
<是tag2/><的子元素,且tag1/><是根元素時,才符合tag1/><。tag2/>範例:
/a/b符合 XML 片段<a><b/></a>中的b元素,因為它是根元素a的子元素。它在<b><a/></b>中沒有相符項,因為在此情況下,b是根元素(因此不是任何其他元素的子元素)。此 XPath 表達式在<a><c><b/></c></a>中也沒有相符項;這裡的b是a的子系,但實際上不是a的子元素。此結構可延伸到三個或更多元素。例如,XPath 表達式
/a/b/c符合片段<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符合片段<a><b/></a>或<c><a><b/></a></c>中的b元素。注意//等於tag/descendant-or-self::*/。常見的錯誤是將其與tag/descendant-or-self::混淆,但後者的表達式實際上可能會導致非常不同的結果,如下所示tagmysql> 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符合 XML 片段<a><b/></a>或<c><b/></c>中的b元素。但是,此表達式在片段<b><a/></b>中不會產生相符項,因為b必須是其他元素的子元素。萬用字元可用於任何位置:表達式/*/b/*符合b元素的任何子元素,而該元素本身不是根元素。您可以使用
|(UNION) 運算子來比對多個定位符。例如,表達式//b|//c符合 XML 目標中的所有b和c元素。也可以根據元素的一個或多個屬性的值來比對元素。這可以使用語法
來完成。例如,表達式tag[@attribute="value"]//b[@id="idB"]符合片段<a><b id="idA"/><c/><b id="idB"/></a>中的第二個b元素。若要比對具有的任何元素,請使用 XPath 表達式attribute="value"//*[。attribute="value"]若要篩選多個屬性值,只需依序使用多個屬性比較子句即可。例如,表達式
//b[@c="x"][@d="y"]符合給定 XML 片段中任何位置出現的元素<b c="x" d="y"/>。若要尋找具有相同屬性且符合多個值的元素,您可以使用由
|運算子聯結的多個定位符。例如,若要比對所有b元素,且其c屬性具有 23 或 17 的值,請使用表達式//b[@c="23"]|//b[@c="17"]。您也可以使用邏輯or運算子達到此目的://b[@c="23" or @c="17"]。注意or和|的差異在於,or會聯結條件,而|會聯結結果集。
XPath 限制事項。這些函數支援的 XPath 語法目前受限於以下限制
不支援節點集到節點集的比較(例如
'/a/b[@c=@d]')。支援所有標準 XPath 比較運算子。(錯誤 #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"]/dmysql> 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)在路徑會導致 「高於」根元素的情況下,不支援「向上和向下」導覽。也就是說,您無法使用符合指定元素的祖先的子系的表達式,其中目前元素的一個或多個祖先也是根元素的祖先 (請參閱錯誤 #16321)。
不支援下列 XPath 函數,或有已知的問題,如下所示
id()lang()local-name()name()namespace-uri()normalize-space()starts-with()string()substring-after()substring-before()translate()
不支援下列軸
following-siblingfollowingpreceding-siblingpreceding
作為引數傳遞給 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 語句的 XPath 等效語句如下
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一種可以規避這種特定攻擊的方法,是簡單地在 $xpath 的定義中引用要內插的變數名稱,強制從 Web 表單傳遞的值轉換為字串
$xpath = "//user[login/text()='$login' and password/text()='$password']/attribute::id";這與通常建議用於防止 SQL 注入攻擊的策略相同。一般來說,您應該遵循的防止 XPath 注入攻擊的做法與防止 SQL 注入的做法相同
永遠不要接受應用程式中來自使用者未經測試的資料。
檢查所有使用者提交的資料類型;拒絕或轉換類型錯誤的資料
測試數值資料是否超出範圍值;截斷、四捨五入或拒絕超出範圍的值。測試字串中是否有非法字元,並將其剔除或拒絕包含這些字元的輸入。
不要輸出可能向未經授權的使用者提供線索的明確錯誤訊息,這些線索可能被用來危害系統;而是將這些訊息記錄到檔案或資料庫表格中。
正如 SQL 注入攻擊可用於獲取有關資料庫架構的資訊一樣,XPath 注入也可用於遍歷 XML 檔案以揭示其結構,正如 Amit Klein 的論文 盲 XPath 注入(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)一般來說,安全地將資料傳回給使用者的準則與接受使用者輸入的準則相同。這些可以歸納為
始終測試傳出的資料類型和允許的值。
永遠不允許未經授權的使用者檢視可能提供有關應用程式的資訊的錯誤訊息,這些資訊可能被用於利用它。