Introduction▲
Dans cet article, nous discutons des fonctionnalités XML disponibles dans MySQL, avec l'accent sur les nouvelles fonctionnalités des versions 5.1 et 6.0.
Nous partons du principe que vous avez déjà des connaissances sur XML et que vous savez ce que les termes « valide » et « bien formé » veulent dire.
Nous supposons aussi que vous ayez quelques connaissances sur XPath.
Nous allons couvrir ces différents sujets :
- les méthodes pour exporter des données MySQL dans le format XML, incluant l'utilisation de lib_MySQLudf_xql, une librairie tierce qui peut être utilisée pour cette tâche ;
- l'utilisation des fonctions (nouvelles dans MySQL 5.1) ExtractValue() et UpdateXML() pour travailler avec XML et Xpath ;
- le stockage de données depuis XML dans une base MySQL en utilisant la déclaration LOAD XML (implémentée dans MySQL 6.0) ;
- quelques éléments de sécurités à garder à l'esprit quand on utilise ces techniques
I. Utiliser XML vers et depuis MySQL▲
Dans cette section, nous discutons de la manière d'exporter les données en XML depuis MySQL, et de comment stocker les données obtenues dans un fichier XML vers une base MySQL.
I-A. Exporter les données▲
Dans cette section, nous commençons avec quelques données déjà stockées dans une table MySQL, et démontrons différentes façons de les sortir dans le format XML.
En utilisant l'option -xml. Les deux programmes clients MySQL et mysqldump supportent une option au démarrage qui leur demande de produire une sortir XML.
Voici un bref exemple en utilisant le client MySQL :
shell>
mysql -uroot -e "SHOW VARIABLES LIKE '%version%'"
--xml
<?xml version="1.0"?>
<resultset
statement
=
"SHOW VARIABLES LIKE '%version%'"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
>
<row>
<field
name
=
"Variable_name"
>
protocol_version</field>
<field
name
=
"Value"
>
10</field>
</row>
<row>
<field
name
=
"Variable_name"
>
version</field>
<field
name
=
"Value"
>
5.1.22-beta-debug</field>
</row>
<row>
<field
name
=
"Variable_name"
>
version_comment</field>
<field
name
=
"Value"
>
Source distribution</field>
</row>
<row>
<field
name
=
"Variable_name"
>
version_compile_machine</field>
<field
name
=
"Value"
>
x86_64</field>
</row>
<row>
<field
name
=
"Variable_name"
>
version_compile_os</field>
<field
name
=
"Value"
>
suse-linux-gnu</field>
</row>
</resultset>
Le contenu de l'élément <field name=« Value »>, correspond à la valeur trouvée dans la colonne Value affichée quand la même commande est exécutée dans le client MySQL sans l'option --xml, comme montré ici :
shell>
mysql -uroot -e "SHOW VARIABLES LIKE '%version%'"
+-------------------------+---------------------+
|
Variable_name |
Value |
+-------------------------+---------------------+
|
protocol_version |
10
|
|
version |
5
.1
.22
-beta-debug |
|
version_comment |
Source distribution |
|
version_compile_machine |
x86_64 |
|
version_compile_os |
suse-linux-gnu |
+-------------------------+---------------------+
Naturellement, les valeurs elles-mêmes dépendent de la version de MySQL que vous utilisez et de la machine sur laquelle MySQL est installé, donc si vous utilisez la commande, vos résultats peuvent être différents de ceux montrés ici.
Table d'exemple pour l'export Xml.
Le reste de cette section utilise la table créée et peuplée par les commandes sql ci-dessous.
CREATE
SCHEMA
xmltest;
CREATE
TABLE
xmltest.cities (
name
CHAR
(
35
)
NOT
NULL
DEFAULT
''
,
country CHAR
(
52
)
NOT
NULL
DEFAULT
''
,
population int
(
11
)
NOT
NULL
DEFAULT
'0'
)
;
INSERT
INTO
cities VALUES
(
'Mumbai (Bombay)'
,'India'
,10500000
)
;
INSERT
INTO
cities VALUES
(
'Seoul'
,'South Korea'
,9981619
)
;
INSERT
INTO
cities VALUES
(
'São Paulo'
,'Brazil'
,9968485
)
;
INSERT
INTO
cities VALUES
(
'Shanghai'
,'China'
,9696300
)
;
INSERT
INTO
cities VALUES
(
'Jakarta'
,'Indonesia'
,9604900
)
;
INSERT
INTO
cities VALUES
(
'Karachi'
,'Pakistan'
,9269265
)
;
INSERT
INTO
cities VALUES
(
'Istanbul'
,'Turkey'
,8787958
)
;
INSERT
INTO
cities VALUES
(
'Ciudad de México'
,'Mexico'
,8591309
)
;
INSERT
INTO
cities VALUES
(
'Moscow'
,'Russian Federation'
,8389200
)
;
INSERT
INTO
cities VALUES
(
'New York'
,'United States'
,8008278
)
;
Cette table a été créée à l'origine en utilisant la commande SQL suivante sur la vénérable table exemple world.
CREATE
TABLE
xmltest.cities
SELECT
i.Name
AS
name
,
o.Name
AS
country,
i.Population AS
population
FROM
City i JOIN
Country o ON
i.CountryCode=
o.Code
ORDER
BY
i.Population DESC
LIMIT
10
;
Vous pouvez obtenir une copie de la table world ici http://dev.mysql.com/doc/.
Depuis MySQL 5.1.12 les formats <field> et <row> produits par le client MySQL correspondent à ceux produits par mysqldump.
Cependant, l'élément racine de la sortie mysql --xml est <resultset>, dont l'attribut de la commande contient la commande SQL passée à MySQL comme montré ici :
shell>
mysql -uroot --xml -e 'SELECT * FROM xmltest.cities ORDER BY name'
<?xml version="1.0"?>
<resultset
statement
=
"SELECT * FROM xmltest.cities ORDER BY name"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
>
<row>
<field
name
=
"name"
>
Ciudad de Méico</field>
<field
name
=
"country"
>
Mexico</field>
<field
name
=
"population"
>
8591309</field>
</row>
<row>
<field
name
=
"name"
>
Istanbul</field>
<field
name
=
"country"
>
Turkey</field>
<field
name
=
"population"
>
8787958</field>
</row>
<row>
<field
name
=
"name"
>
Jakarta</field>
<field
name
=
"country"
>
Indonesia</field>
<field
name
=
"population"
>
9604900</field>
</row>
<row>
<field
name
=
"name"
>
Karachi</field>
<field
name
=
"country"
>
Pakistan</field>
<field
name
=
"population"
>
9269265</field>
</row>
<row>
<field
name
=
"name"
>
Moscow</field>
<field
name
=
"country"
>
Russian Federation</field>
<field
name
=
"population"
>
8389200</field>
</row>
<row>
<field
name
=
"name"
>
Mumbai (Bombay)</field>
<field
name
=
"country"
>
India</field>
<field
name
=
"population"
>
10500000</field>
</row>
<row>
<field
name
=
"name"
>
New York</field>
<field
name
=
"country"
>
United States</field>
<field
name
=
"population"
>
8008278</field>
</row>
<row>
<field
name
=
"name"
>
São Paulo</field>
<field
name
=
"country"
>
Brazil</field>
<field
name
=
"population"
>
9968485</field>
</row>
<row>
<field
name
=
"name"
>
Seoul</field>
<field
name
=
"country"
>
South Korea</field>
<field
name
=
"population"
>
9981619</field>
</row>
<row>
<field
name
=
"name"
>
Shanghai</field>
<field
name
=
"country"
>
China</field>
<field
name
=
"population"
>
9696300</field>
</row>
</resultset>
La sortie de mysqldump-xml est structurée quelque peu différemment, comme montré ici :
shell>
mysqldump --xml xmltest cities
<?xml version="1.0"?>
<mysqldump
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
>
<database
name
=
"xmltest"
>
<table_structure
name
=
"cities"
>
<field
Field
=
"name"
Type
=
"char(35)"
Null
=
"NO"
Key
=
""
Default
=
""
Extra
=
""
/>
<field
Field
=
"country"
Type
=
"char(52)"
Null
=
"NO"
Key
=
""
Default
=
""
Extra
=
""
/>
<field
Field
=
"population"
Type
=
"int(11)"
Null
=
"NO"
Key
=
""
Default
=
"0"
Extra
=
""
/>
<options
Name
=
"cities"
Engine
=
"MyISAM"
Version
=
"10"
Row_format
=
"Fixed"
Rows
=
"10"
Avg_row_length
=
"92"
Data_length
=
"920"
Max_data_length
=
"25895697857380351"
Index_length
=
"1024"
Data_free
=
"0"
Create_time
=
"2007-08-24 14:19:42"
Update_time
=
"2007-08-24 14:19:42"
Collation
=
"latin1_swedish_ci"
Create_options
=
""
Comment
=
""
/>
</table_structure>
<table_data
name
=
"cities"
>
<row>
<field
name
=
"name"
>
Mumbai (Bombay)</field>
<field
name
=
"country"
>
India</field>
<field
name
=
"population"
>
10500000</field>
</row>
<row>
<field
name
=
"name"
>
Seoul</field>
<field
name
=
"country"
>
South Korea</field>
<field
name
=
"population"
>
9981619</field>
</row>
<row>
<field
name
=
"name"
>
São Paulo</field>
<field
name
=
"country"
>
Brazil</field>
<field
name
=
"population"
>
9968485</field>
</row>
<row>
<field
name
=
"name"
>
Shanghai</field>
<field
name
=
"country"
>
China</field>
<field
name
=
"population"
>
9696300</field>
</row>
<row>
<field
name
=
"name"
>
Jakarta</field>
<field
name
=
"country"
>
Indonesia</field>
<field
name
=
"population"
>
9604900</field>
</row>
<row>
<field
name
=
"name"
>
Karachi</field>
<field
name
=
"country"
>
Pakistan</field>
<field
name
=
"population"
>
9269265</field>
</row>
<row>
<field
name
=
"name"
>
Istanbul</field>
<field
name
=
"country"
>
Turkey</field>
<field
name
=
"population"
>
8787958</field>
</row>
<row>
<field
name
=
"name"
>
Ciudad de México</field>
<field
name
=
"country"
>
Mexico</field>
<field
name
=
"population"
>
8591309</field>
</row>
<row>
<field
name
=
"name"
>
Moscow</field>
<field
name
=
"country"
>
Russian Federation</field>
<field
name
=
"population"
>
8389200</field>
</row>
<row>
<field
name
=
"name"
>
New York</field>
<field
name
=
"country"
>
United States</field>
<field
name
=
"population"
>
8008278</field>
</row>
</table_data>
</database>
</mysqldump>
Le formatage de certaines sorties XML a été légèrement changé pour s'adapter à l'espace disponible d'une page à imprimer.
mysqldump-xml emploie les éléments suivants :
- l'élément racine du document XML représentant le dump est <mysqldump> ;
- les définitions de table et les données appartenant à chaque base représentée dans le dump sont enveloppées ensemble dans l'élément <database> dont la valeur de l'attribut name est le nom de cette base de données ;
- chaque définition de table est enveloppée dans un élément <table_structure> ;
- les données de chaque table sont enveloppées dans un élément <table_data>, et comprennent les éléments <field> et <row>.
Pour sauvegarder les sorties de MySQL et mysqldump dans un fichier, utilisez simplement l'opérateur > avec le nom de fichier désiré, comme montré ici :
shell>
mysql -uroot --xml xmltest -e 'SELECT name FROM cities LIMIT 2'
>
/tmp/2cities.xml
shell>
more /tmp/2cities.xml
<?xml version="1.0"?>
<resultset
statement
=
"SELECT name,country FROM cities LIMIT 2"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
>
<row>
<field
name
=
"name"
>
Mumbai (Bombay)</field>
<field
name
=
"country"
>
India</field>
</row>
<row>
<field
name
=
"name"
>
Seoul</field>
<field
name
=
"country"
>
South Korea</field>
</row>
</resultset>
La déclaration XML d'un espace de nom est incluse dans la sortie XML de MySQL et mysqldump depuis MySQL 5.1.18
Il peut y avoir des fois où votre application requiert un format XML différent de celui obtenu normalement par MySQL et mysqldump.
Supposons, par exemple, que votre application ait besoin d'un format comme celui-ci :
<cities>
<city>
name1<city>
<city>
name2<city>
<-- etc. -->
</cities>
Une manière d'obtenir cette sortie est d'utiliser la concaténation avec les fonctions CONCAT() et GROUP_CONCAT(), comme ceci :
mysql>
SELECT CONCAT
(
'\n<cities>\n'
,
->
GROUP_CONCAT
(
' <city>'
, name, '</city>\n'
SEPARATOR ''
),
->
'</cities>'
) AS xmldoc
->
FROM cities\G
*************************** 1. row ***************************
xmldoc:
<cities>
<city>
Mumbai (Bombay)</city>
<city>
Seoul</city>
<city>
São Paulo</city>
<city>
Shanghai</city>
<city>
Jakarta</city>
<city>
Karachi</city>
<city>
Istanbul</city>
<city>
Ciudad de Méico</city>
<city>
Moscow</city>
<city>
New York</city>
</cities>
1 row in set (0.01 sec)
Un autre exemple dans ce format :
<cities>
<city
name
=
"name1"
population
=
"population1"
/>
<city
name
=
"name2"
population
=
"population2"
/>
<-- etc. -->
</cities>
Ceci peut être produit par ces commandes :
mysql>
SELECT CONCAT
(
->
'\n<cities>'
,
->
GROUP_CONCAT
(
->
'\n\t<city name="'
, name, '" population="'
, population, '"/>'
->
SEPARATOR ''
->
),
->
'\n</cities>'
->
) AS xmldoc
->
FROM cities\G
*************************** 1. row ***************************
xmldoc:
<cities>
<city
name
=
"Mumbai (Bombay)"
population
=
"10500000"
/>
<city
name
=
"Seoul"
population
=
"9981619"
/>
<city
name
=
"São Paulo"
population
=
"9968485"
/>
<city
name
=
"Shanghai"
population
=
"9696300"
/>
<city
name
=
"Jakarta"
population
=
"9604900"
/>
<city
name
=
"Karachi"
population
=
"9269265"
/>
<city
name
=
"Istanbul"
population
=
"8787958"
/>
<city
name
=
"Ciudad de Méico"
population
=
"8591309"
/>
<city
name
=
"Moscow"
population
=
"8389200"
/>
<city
name
=
"New York"
population
=
"8008278"
/>
</cities>
1 row in set (0.01 sec)
Vous pouvez voir que la production de XML simple par de tels moyens peut rapidement devenir compliquée.
Tant que vous pouvez envelopper les commandes SELECT dans des procédures stockées, le fait est que les routines stockées de MySQL ne peuvent pas (du moins jusqu'à présent) prendre un nombre variable d'arguments. Heureusement, il existe une librairie tierce qui peut être utilisée avec MySQL pour rendre cette tache plus facile.
Utilisation de la librairie lib_mysqludf_xql
La librairie lib_mysqludf_xql est un lot d'UDF écrites et placées sous licence GPL par Arnold Daniels. Les sources sont disponibles ici : www.mysqludf.org
mysql>
SELECT xql_element
(
'city'
, name) FROM cities;
I-B. Importer XML et ses données▲
Dans cette section, nous discutons de quelques techniques pour importer des données XML dans une table MySQL.
En utilisant LOAD_FILE()▲
La façon la plus simple de stocker du XML dans une table MySQL est d'utiliser la fonction LOAD_FILE() pour pouvoir importer un document XML en entier, le rendre disponible en tant que chaine de caractères, et d'insérer cette chaine dans une colonne de la table.
En utilisant le fichier 2cities.xml créé plus tôt, il est possible de faire quelque chose comme ceci :
mysql>
USE xmltest;
mysql>
CREATE TABLE xmldocs (
->
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
->
doc_content BLOB NOT NULL,
->
comment VARCHAR
(
100
) NOT NULL DEFAULT ''
->
);
Query OK, 0
rows affected (
0
.04
sec)
mysql>
INSERT INTO xmldocs VALUES
->
(
NULL, LOAD_FILE
(
'/tmp/2cities.xml'
), '2 cities file'
);
Query OK, 1
row affected (
0
.00
sec)
mysql>
SELECT * FROM xmldocs\G
id: 1
doc_content: <?xml version="1.0"?>
<resultset
statement
=
"SELECT name,country FROM cities LIMIT 2
"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
>
<row>
<field
name
=
"name"
>
Mumbai (Bombay)</field>
<field
name
=
"country"
>
India</field>
</row>
<row>
<field
name
=
"name"
>
Seoul</field>
<field
name
=
"country"
>
South Korea</field>
</row>
</resultset>
comment: 2 cities file
1 row in set (0.00 sec)
En important les données XML de mysqldump dans la colonne de la table MySQL avec une procédure stockée▲
Charger un fichier XML entier dans une seule ligne de la table MySQL résout le problème d'insertion du XML dans MySQL, où il peut être parsé en utilisant les fonctions XPATH de MySQL 5.1 (voir la section XPATH de l'article).
Quoi qu'il en soit, avoir à faire ça à chaque fois que vous voulez accéder aux données n'est pas terrible. Heureusement le développeur Alexander Barkov a écrit une procédure stockée nommée xmldump_load qui extrait les données depuis les éléments et les attributs XML trouvés dans un fichier créé via mysqldump -xml et insert ces données dans les colonnes d'une table MySQL. La source de cette procédure est montrée ici :
DELIMITER
|
DROP
PROCEDURE
IF
EXISTS
xmldump_load |
CREATE
PROCEDURE
xmldump_load(
file_name VARCHAR
(
128
)
,
database_name VARCHAR
(
128
)
,
table_name
VARCHAR
(
128
))
BEGIN
DECLARE
xml
TEXT
;
DECLARE
nrows INT
;
DECLARE
rownum
INT
DEFAULT
1
;
DECLARE
ncols INT
;
DECLARE
colnum INT
DEFAULT
1
;
DECLARE
ins_list TEXT
DEFAULT
''
;
DECLARE
val_list TEXT
DEFAULT
''
;
DECLARE
tmp VARCHAR
(
255
)
;
-- load the XML file's contents into a string
SET
xml
=
LOAD_FILE
(
file_name)
;
-- get the number of <row>s in this table
SET
nrows =
ExtractValue
(
xml
,
'count(/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row)'
)
;
-- get the number of <field>s (columns) in this table
SET
ncols =
ExtractValue
(
xml
,
'count(/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row[1]/field)'
)
;
-- for each <row>
WHILE
rownum
<=
nrows DO
-- for each <field> (column)
WHILE
colnum <=
ncols DO
SET
tmp =
ExtractValue
(
xml
,
'/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row[$rownum]/field[$colnum]/@name'
)
;
SET
ins_list =
CONCAT
(
ins_list, tmp, IF
(
colnum<
ncols, ','
, ''
))
;
SET
tmp =
ExtractValue
(
xml
,
'/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row[$rownum]/field[$colnum]'
)
;
SET
val_list =
CONCAT
(
val_list, ''''
, tmp ,''''
, IF
(
colnum<
ncols, ','
, ''
))
;
SET
colnum =
colnum +
1
;
END
WHILE
;
SET
@ins_text =
CONCAT
(
'INSERT INTO t1 ('
, ins_list, ') VALUES ('
, val_list, ')'
)
;
SET
ins_list =
''
;
SET
val_list =
''
;
PREPARE
stmt FROM
@ins_text;
EXECUTE
stmt;
SET
rownum
=
rownum
+
1
;
SET
colnum =
1
;
END
WHILE
;
END
|
DELIMITER
;
Cette procédure stockée emploie les variables utilisateurs XPATH et ne fonctionne donc que sous MySQL 5.1.20 ou supérieur. Vous pouvez trouver une copie de create-xmpdump-load.sql à la fin de cet article.
L'appelant de cette procédure stockée doit avoir les privilèges FILE de MySQL.
Vous pouvez tester xmldump_load en utilisant le script test.sh suivant :
# Demo for xmldump_load()
DB
=
"test"
# Change "root" and "mypass" in the following 2 lines to
# a user and password appropriate to your installation
MYSQL
=
"mysql -uroot -pmypass --socket=/tmp/mysql.sock"
MYSQLDUMP
=
"mysqldump -uroot -pmypass --socket=/tmp/mysql.sock"
# Creates a test table with two columns and fills it with some data
$MYSQL
$DB
<>
END
SELECT VERSION
(
);
DROP TABLE IF
EXISTS t1;
CREATE TABLE t1 (
a INT, b VARCHAR
(
128
));
INSERT INTO t1 VALUES (
1
,'11111'
);
INSERT INTO t1 VALUES (
2
,'22222'
);
INSERT INTO t1 VALUES (
3
,'33333'
);
INSERT INTO t1 VALUES (
4
,'44444'
);
INSERT INTO t1 VALUES (
5
,'55555'
);
INSERT INTO t1 VALUES (
6
,'66666'
);
INSERT INTO t1 VALUES (
7
,'77777'
);
INSERT INTO t1 VALUES (
8
,'88888'
);
INSERT INTO t1 VALUES (
9
,'99999'
);
END
# Dumps data into an XML file
$MYSQLDUMP
--xml $DB
t1 >
/tmp/t1.xml
# Empties the table
$MYSQL
--execute
=
"DELETE FROM t1"
$DB
# Creates the procedure, calls it, and
# makes sure we've restored all records
$MYSQL
$DB
<< END
\. create-xmldump-load.sql
CALL xmldump_load('/tmp/t1.xml', 'test', 't1');
SELECT * FROM t1;
END
# Performs cleanup
# Comment out the remaining lines if you wish to
# preserve the stored procedure, table, and XML
# file following the test run
$MYSQL
$DB
<< END
DROP PROCEDURE xmldump_load;
DROP TABLE t1;
END
rm /tmp/t1.xml
En utilisant Load XML▲
Une contribution d'Erik Wetterberg d'une nouvelle commande SQL a été acceptée pour MySQL 6.0, et sera disponible à partir de la version 6.0.3. LOAD XML simplifie réellement la tache d'importation de données depuis un fichier XML vers une table MySQL, sans avoir à utiliser la procédure stockée montrée précédemment.
La syntaxe de cette commande est montrée ici :
LOAD
XML
[LOW_PRIORITY | CONCURRENT]
[LOCAL]
INFILE
'filename'
[REPLACE | IGNORE]
INTO
TABLE
[db_name.]
tbl_name
[CHARACTER SET charset_name]
[ROWS IDENTIFIED BY '<tagname>']
[IGNORE number [LINES | ROWS]
]
[(column_or_user_var,...)]
[SET col_name = expr,...]
Cette commande lit les données d'un fichier XML dans une table, et agit comme complément de MySQL et mysqldump dans le mode de sortie XML (c.-à-d. en utilisant l'option --xml ).
Le nom de fichier filename doit être donné en tant que chaine de caractères. L'élément tagname de l'option ROWS IDENTIFIED BY doit aussi être donné en tant que chaine de caractères et doit être entouré des signes supérieur et inférieur (< et >).
La clause LOAD DATA utilisée dans LOAD XML▲
Si vous avez utilisé la commande MySQL LOAD DATA décrite précédemment, vous devriez trouver les clauses suivantes familières, puisqu’elles travaillent essentiellement de la même façon que se soit LOAD XML que LOAD DATA :
- LOW_PRIORITY ou CONCURRENT
- LOCAL
- REPLACE ou IGNORE
- CHARACTER SET
- La clause IGNORE number LINES est analogue aux clauses IGNORE … LINES de la commande LOAD DATA. LOAD XML accepte aussi IGNORE number ROWS Dans les deux cas, la clause force les number premières lignes à être ignorées et donc non importées.
- (column_or_user_var,…)
- SET
Reportez vous à la syntaxe de LOAD DATA INFILE dans le manuel de MySQL 5.1 pour plus d'informations sur ces clauses.
ROWS IDENTIFIED BY '<tagname>'▲
LOAD XML supporte trois formats XML différents :
- les attributs sont interprétés en tant que noms de colonnes, et leurs valeurs en tant que valeurs des colonnes
<row
column1
=
"value1"
column2
=
"value2"
.../>
- les noms des balises sont interprétés comme noms de colonnes, et le contenu de ces balises en tant que valeurs des colonnes
<row>
<column1>
value1</column1>
<column2>
value2</column2>
</row>
- les noms de colonnes d'une table dérivent de l'attribut name de la balise <fields>, et les valeurs des colonnes sont prises dans le contenu de ces balises.
<row>
<field
name
=
'column1'
>
value1</field>
<field
name
=
'column2'
>
value2</field>
</row>
Ce dernier est celui utilisé par l'outil MySQL mysqldump.
Les routines d'import utilisées par LOAD XML détectent automatiquement le format utilisé pour chaque ligne et l'interprètent correctement, faisant correspondre les noms basés sur les balises ou les attributs à ceux des colonnes. Vous pouvez facilement vérifier cela par vous-même en créant un fichier XML qui utilise un mix des formats cités précédemment avec la commande LOAD XML pour l'importer dans une table.
Vous devez avoir les privilèges FILE de MySQL pour utiliser LOAD XML.
II. Les fonctionnalités XPath▲
Dans cette section, nous voyons les fonctionnalités XPath ajoutées dans MySQL 5.1.5.
ExtractValue() vous autorise a utiliser une expression XPath sur un fragment de XML dans le but de retourner le contenu d'un ou plusieurs éléments.UpdateXML() rend cela possible en en remplaçant un fragment de XML existant par un nouveau, en utilisant XPath pour préciser le fragment à remplacer.
II-A. La fonction ExtractValue()▲
L'exemple dans 'En utilisant LOAD FILE()' précédent démontre comment récupérer le contenu d'un fichier XML et le réinsérer dans une base, mais le problème d'obtention des données reste réel.
Une manière d'y arriver est d'utiliser la fonction ExtractValue(). La syntaxe de cette fonction est montrée ici :
ExtractValue(xml_fragment, xpath_expression)
ExtractValue() prend deux arguments. Le premier est le fragment de XML à tester, le deuxième est l'expression XPath à trouver.
Exemple : regardons comment nous pouvons obtenir le nom de la ville dans ce document (que nous avons créé précédemment : 2cities.xml)
Nous procédons en deux étapes : d'abord nous prenons le XML depuis la table xmldocs et on la place dans une variable utilisateur.
mysql>
SELECT doc_content FROM xmldocs LIMIT 1
INTO @xml;
Query OK, 1
row affected (
0
.00
sec)
mysql>
SELECT @xml\G
*************************** 1. row ***************************
@xml: <?xml version="1.0"?>
<resultset
statement
=
"SELECT name,country FROM cities LIMIT 2
"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
>
<row>
<field
name
=
"name"
>
Mumbai (Bombay)</field>
<field
name
=
"country"
>
India</field>
</row>
<row>
<field
name
=
"name"
>
Seoul</field>
<field
name
=
"country"
>
South Korea</field>
</row>
</resultset>
1 row in set (0.00 sec)
Maintenant nous utilisons ExtractValue() avec @xml en tant que premier argument. Pour le deuxième argument, nous employons une expression XPath qui veut dire « le contenu du premier élément <field > inclus dans le deuxième élément <row > trouvé n'importe où dans le document ».
Un autre moyen de faire cela serait d'utiliser une expression XPath qui veut dire : « le contenu du second <field > dont l'attribut name est 'name' ».
mysql>
SELECT ExtractValue
(
@xml, '//field[@name="name"][2]'
);
+------------------------------------------------+
| ExtractValue(@xml, '//field[@name="name"][2]') |
+------------------------------------------------+
| Seoul |
+------------------------------------------------+
1 row in set (0.00 sec)
Comme vous pouvez le voir, le résultat (Seoul) est le même que tout à l'heure, ce qui est exactement ce que nous voulions.
II-B. La fonction UpdateXML()▲
Cette fonction vous permet de remplacer une portion d'un fragment XML (identifié par un localisateur XPath) avec différentes balises XML.
Par exemple, prenez ce fragment XML <book><chapter/></book>.
Maintenant supposez que vous voulez le transformer en <book><part><chapter/></part></book> cet exemple vous montre comment vous pouvez le faire avec UpdateXML() en sauvegardant le résultat dans une variable utilisateur @new_xml.
mysql>
SELECT @new_xml:=
UpdateXML
(
'<book><chapter/></book>'
,
->
'//chapter'
,
->
'<part><chapter/></part>'
)
->
AS uxml;
+--------------------------------------------------------------------+
| uxml |
+--------------------------------------------------------------------+
| <book><part><chapter/></part></book>
|
+--------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
SELECT @new_xml;
+--------------------------------------+
| @new_xml |
+--------------------------------------+
| <book><part><chapter/></part></book>
|
+--------------------------------------+
1 row in set (0.00 sec)
La syntaxe pour cette fonction est montrée ici :
UpdateXML(xml, locator, replacement)
Xml est la balise XML à tester pour la correspondance.
locator est l'expression XPath utilisée pour obtenir la correspondance.
replacement est la balise a utiliser pour remplacer le XML qui correspond à locator.
Contrairement à ExtractValue(),UpdateXML() récupère les éléments et les contenus et ensuite renvoie le tout sous forme de chaine de caractères. S'il n'y a aucune correspondance pour locator, le XML original est retourné.
II-C. Le support de XPath dans MySQL 5.1 et 6.0▲
Dans cette section nous discutons des fonctionnalités XPath fournies avec les fonctions ExtractValue() et UpdateXML() dans MySQL 5.1 et 6.0.
Les fonctions et les opérateurs supportés▲
Les expressions XPath basiques (connues comme repères) sont supportées, ceci inclut les repères utilisant les opérateurs suivants :
- l'opérateur / (slash), cet opérateur agit de la même manière que sont homologues dans les chemins de fichiers systèmes UNIX, quand le premier / représente la racine du document et l'identifiant suivant est le nom de l'élément XML.
Par exemple, /book correspond au premier élément book.
De multiples slashes peuvent être utilisées pour retracer une branche.
Par exemple /book/chapter correspond à <book><chapter/></book> où book est le premier élément et chapter est un fils de cet élément.
Un double slashes (//) en entête que le pattern correspond n'importe où dans le document.
Par exemple le repère //section correspond à l'élément section partout où il se trouve, quelque soit sa relation avec les autres éléments du document ; - l'opérateur | union. Cet opérateur peut être utilisé quand vous voulez combiner les repères quand vous voulez faire correspondre n'importe lesquels d'entre eux.
Par exemple //section|//paragraph correspond a n'importe quel élément section ou paragraph, n'importe où dans le document ; - l'opérateur * wildcard : cet opérateur correspond à n'importe quel élément.
Par exemple, //chapter/* correspond à n'importe quel élément enfant de chapter, n'importe où dans le document et /*/section correspond à n'importe quel élément section enfant de l'élément le plus haut ; - correspondance d'attribut : vous pouvez trouver un élément par la valeur de un ou plusieurs de ses attributs en utilisant la syntaxe element [@ attribute = value ].
Par exemple //section[@id='xpath-locators'] correspond à un élément section n'importe où dans le document qui possède un attribut id avec la valeur xpath-locators.
NOTE
Vous pouvez trouver de multiples valeurs d'attributs en les combinant simplement.
Par exemple, le repère //paragraph[@role='intro'][@title='xpath support'] correspond à n'importe quel élément paragraph (n'importe où dans le document XML à tester) qui a un attribut role dont la valeur est intro et un attribut title dont la valeur est xPath-support.
Pour trouver des éléments pour lesquels aux mêmes attributs correspondent une des multiples valeurs, vous pouvez utiliser plusieurs repères joints par l'opérateur union |.
Par exemple, pour trouver tous les éléments paragraph, n'importe où dans le document XML à tester dont l'attribut title a l'une des valeurs Example ou Syntax, vous utiliserez l'expression //paragraph[@title=« Example »]|//paragraph[@title=« Syntax »]. Vous pouvez aussi utiliser l'opérateur logique or pour cette expression : //paragraph[@title=« Example » or @title=« Syntax »].
La différence entre or et | est que or joint les conditions alors que | joint les jeux de résultat. - Les espaces de noms ne sont pas explicitement supportés. Quoi qu'il en soit les noms d'éléments contenant : sont autorisés, donc vous pouvez travailler avec des balises XML qui utilisent la notation d'espaces de noms.
Par exemple //person:biography correspond au tag <person:biography/> n'importe où dans le document. - Tous les opérateurs standards de comparaison XPath (or, and, =, !=, <=, <, >>=, and >) sont supportés, Par exemple considérez ce qui suit :
mysql>
SET @xml =
'<foo bar="2">123</foo><foo bar="6">456</foo>'
;
Query OK, 0 rows affected (0.00 sec)
mysql>
SELECT ExtractValue
(
@xml, '//foo[@bar="2"]'
);
+---------------------------------------+
| extractvalue(@xml, '//foo[@bar="2"]') |
+---------------------------------------+
| 123 |
+---------------------------------------+
1 row in set (0.00 sec)
mysql>
SELECT ExtractValue
(
@xml, '//foo[@bar>"2"]'
);
+---------------------------------------+
| extractvalue(@xml, '//foo[@bar>"2"]') |
+---------------------------------------+
| 456 |
+---------------------------------------+
1 row in set (0.00 sec)
mysql>
SELECT ExtractValue
(
@xml, '//foo[@bar>="2"]'
);
+----------------------------------------+
| extractvalue(@xml, '//foo[@bar>="2"]') |
+----------------------------------------+
| 123 456 |
+----------------------------------------+
1 row in set (0.01 sec)
Notez que quand plusieurs correspondances sont trouvées, ExtractValue() les retourne dans un seul espace délimité.
- Vous pouvez aussi utiliser l'indexation dans des repères pour identifier les éléments particuliers d'intérêt, comme on l'a vu précédemment pour //row[2]/field[1].
ExtractValue(), comme son nom l'indique, obtient une valeur, il ne retourne aucun élément XML. Voici un exemple qui devrait rendre cela plus clair :
SET @xml =
'<a>3<b><c>5<d/></c></b></a>'
;
Query OK, 0 rows affected (0.00 sec)
mysql>
SELECT ExtractValue
(
@xml, '//a'
), ExtractValue
(
@xml, '//b'
), ExtractValue
(
@xml, '//c'
);
+---------------------------+---------------------------+---------------------------+
| ExtractValue(@xml, '//a') | ExtractValue(@xml, '//b') | ExtractValue(@xml, '//c') |
+---------------------------+---------------------------+---------------------------+
| 3 | | 5 |
+---------------------------+---------------------------+---------------------------+
1 row in set (0.01 sec)
ExtractValue(@xml, '//a') retourne seulement le contenu de n'importe quel élément <a>, il ne retourne rien des éléments <b>, <c>, ou <d>, ou rien de leur contenu. ExtractValue(@xml, '//c') retourne seulement le contenu de n'importe quel élément <c>, car le seul élément <b> ne contient pas de texte, mais seulement d'autres éléments XML. ExtractValue(@xml, '//b') retourne une chaine vide.
Les fonctions et les opérateurs non supportés▲
Beaucoup d'opérateurs et de fonctions XPath sont supportés.
Quoi qu'il en soit le support de Xpath dans MySQL est toujours en développement et donc certains aspects ne sont pas encore implémentés, ceci inclut les limitations suivantes.
- Les expressions de repères relatifs sont résolues dans le contexte du nœud de racine, par exemple considérez la requête suivante et son résultat :
mysql>
SELECT ExtractValue
(
->
'<book>
-> <paragraph title="Example">P1</paragraph>
-> <paragraph title="Syntax">P2</paragraph>
-> </book>'
,
->
'book/paragraph'
->
) AS paras;
+-------+
| paras |
+-------+
| P1 P2 |
+-------+
1 row in set (0.03 sec)
Dans ce cas le repère book/paragraph est résolu en /book/paragraph.
Les repères relatifs sont aussi supportés dans les prédicats. Dans l'exemple suivant /@title=« Example » est résolu en /book/paragraph/@title=« Example »:
mysql>
SELECT ExtractValue
(
->
'<book>
-> <paragraph title="Example"><body>P1</body></paragraph>
-> <paragraph title="Syntax"><body>P2</body></paragraph>
-> </book>'
,
->
'book/paragraph/body[../@title="Example"]'
)
->
AS para;
+------+
| para |
+------+
| P1 |
+------+
1 row in set (0.00 sec)
- L'opérateur :: n'est pas supporté en combinaison avec les nœuds du type axis ::comment(), axis ::text(), axis ::processing-instructions(), et axis ::node().
Quoi qu'il en soit les tests nommés (comme axis :: name et axis ::*) sont supportés, comme montré dans l'exemple.
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)
- La navigation « Up-and-down » n'est pas supportée dans les cas ou le chemin mènerait « au-dessus » de l'élément de racine. C'est pourquoi vous ne pouvez pas utiliser les expressions qui correspondent sur les descendants d'un ancêtre d'un élément donné où un ou plus des ancêtres de l'élément actuel sont aussi un ancêtre de l'élément de racine (cfBug #16321).
- Les fonctions XPath suivantes ne sont pas supportées : id(), lang(), local-name(), name() (un patch a récemment été soumis pour implémenter la fonction, il devrait être effectif dans la version 6.0 ) namespace-uri(), normalize-space(), starts-with(), string(), substring-after(), substring-before(), et translate().
- Les axes suivants ne sont pas supportés : following-sibling, following, preceding-sibling, et preceding.
Traitement d'erreur▲
Pour ExtractValue() et UpdateXML(), le repère XPath utilisé doit être valide et le XML doit être bien formé.
Si le repère est invalide, une erreur est générée :
mysql>
SELECT ExtractValue
(
'<foo bar="2">123</foo>'
, '//foo[@bar>="2"]'
);
+------------------------------------------------------------+
| ExtractValue('<foo
bar
=
"2"
>
123</foo>
', '//foo[@bar>="2"]') |
+------------------------------------------------------------+
| 123 |
+------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
SELECT ExtractValue
(
'<foo bar="2">123</foo>'
, '//foo@bar>="2"]'
);');
ERROR 1105 (HY000): XPATH syntax error: '@bar>="2"]'
Si le XML est mal formé, la valeur NULL est retournée, et un avertissement s'affiche.
mysql>
SELECT ExtractValue
(
'<foo bar="2"123</foo>'
, '//foo[@bar>="2"]'
);
+-----------------------------------------------------------+
| ExtractValue('<foo
bar
=
"2"
123</foo>
', '//foo[@bar>="2"]');') |
+-----------------------------------------------------------+
| NULL |
+-----------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql>
SHOW WARNINGS;
+---------+------+--------------------------------------------------------------------------------------------+
| Level | Code | Message|
+---------+------+--------------------------------------------------------------------------------------------+
| Warning | 1522 | Incorrect XML value: 'parse error at line 1 pos 13: unknown token unexpected ('>' wanted)' |
+---------+------+--------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
Quoi qu'il en soit, le XML de remplacement utilisé comme 3e argument de la fonction UpdateXML() n'est pas vérifié pour la bonne formation du document.
NOTE
L'espace ne permet pas une discussion complète de la syntaxe Xpath et de son utilisation. Pour de plus amples informations, rendez-vous sur : XML Path Language (XPath) 1.0 standard. La page Zvon.org XPath Tutorial fournit aussi des ressources utiles pour ceux qui débutent dans XPath ou qui veulent se rafraichir les bases de XPath.
Les variables utilisateur dans les expressions XPath▲
Depuis MySQL 5.1.20, vous pouvez employer les variables utilisateurs dans les repères XPath sous n'importe quelle forme :
- Légèrement vérifiée : une variable qui utilise la syntaxe $@ variable_name ne voit pas son type vérifié ni si elle contient une valeur et dans ce cas aucune erreur n'est renvoyée par le serveur.
En d'autres termes vous êtes responsable des vérifications adéquates et des fautes de frappe lors de l'appel des variables.
Par exemple si vous utilisez $@myvairable au lieu de $@myvariable et que $@myvairable n'a pas de valeur alors MySQL pense que $@myvairable n'a aucune valeur de type approprié.
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)
- Fortement vérifiée : une variable qui utilise la syntaxe $ variable_name peut être déclarée et utilisée dans les fonctions XPath de MySQL quand elles sont appelées dans des procédures stockées.
Comme une variable est locale a une procédure stockée dans laquelle elle est définie, et est vérifié par son type et sa valeur.
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)
1 row in set (0.00 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)
Les expressions contenant les variables définies par l'utilisateur n'importe quelle sorte doivent (à part la notation) se conformer aux règles des expressions XPath contenant des variables comme décrit dans les spécifications XPath.
III. Considérations de sécurité▲
Avec n'importe quelle fonction pour les bases, vous devez être conscient des implications de sécurité, de travailler sur les Xml et les expressions Xpath n'est pas différent à cet égard. Il y a de nombreux points à considérer, incluant la possibilité de lire depuis un fichier en utilisant LOAD XML INFILE.
Le fait que le système de privilège MySQL ne s'applique pas au contenu du fichier XML, et les possibilités pour les saisies des utilisateurs subversifs d'avoir des conséquences fortuites.
III-A. Charger des données depuis un fichier▲
Comme avec la commande LOAD DATA le transfert du fichier XML depuis le poste client jusqu'au serveur est initié par le serveur MySQL. En théorie un serveur patché devrait être chargé de dire au programme client de charger un fichier choisi par le serveur plutôt que par l'utilisateur dans la commande LOAD XML.
Un tel serveur pourrait avoir accès à n'importe quel fichier sur le client à qui le client donne l'accès en lecture.
Dans un environnement web, les clients se connectent habituellement depuis le serveur web. Un utilisateur qui peut exécuter n'importe quelle commande sur le serveur MySQL peut utiliser LOAD XML LOCAL, peut lire n'importe quel document pour lequel le serveur web a les accès en lecture.
Dans cet environnement, le client en ce qui concerne le serveur MySQL est en réalité le serveur Web, pas le programme distant étant exécuté par l'utilisateur qui se connecte au serveur du Web.
Vous pouvez désactiver LOAD XML sur le serveur en le démarrant avec --local-infile=0 ou --local-infile=OFF. Le résultat est montré dans cet exemple.
shell>
mysqld_safe --local
-infile
=
OFF &
shell>
mysql -uroot xtest
Reading table information for
completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 6
.0
.4
-alpha-debug Source distribution
Type 'help;'
or '\h'
for
help. Type '\c'
to clear the buffer.
mysql>
LOAD XML LOCAL
INFILE '/home/jon/person.xml'
->
INTO TABLE person
->
ROWS IDENTIFIED BY '<person>'
;
ERROR 1148
(
42000
): The used command is not allowed with this MySQL version
Cette option peut aussi être utilisée quand vous démarrez le client MySQL pour désactiver LOAD XML le temps de la session utilisateur.
Pour prévenir un client contre le chargement d'un fichier XML sur le serveur, ne donnez pas les droits FILE a l'utilisateur MySQL correspondant ou révoquez les droits si l'utilisateur les possède déjà.
shell>
mysql -uroot -p
Password: ********
Reading table information for
completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 6
.0
.4
-alpha-debug Source distribution
Type 'help;'
or '\h'
for
help. Type '\c'
to clear the buffer.
mysql>
REVOKE FILE ON *.* FROM jon@localhost;
Query OK, 0
rows affected (
0
.00
sec)
mysql>
exit
Bye
shell>
mysql -ujon -p
Enter password: ********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 6
.0
.4
-alpha-debug Source distribution
Type 'help;'
or '\h'
for
help. Type '\c'
to clear the buffer.
mysql>
USE xmltest;
Reading table information for
completion of table and column names
You can turn off this feature to get a quicker startup with -A
mysql>
LOAD XML INFILE '/home/jon/person.xml'
->
INTO TABLE person
->
ROWS IDENTIFIED BY '>person>'
;
ERROR 1045
(
28000
): Access denied for
user 'jon'
@'localhost'
(
using password: YES)
Révoquer les droits FILE (ou ne pas les accorder dans un premier temps) protège seulement l'utilisateur de l'exécution de la commande LOAD XML INFILE ou de la fonction LOAD_FILE().
Cela ne protège pas l'utilisateur d'exécuter la commande LOAD XML LOCAL INFILE. Pour désactiver cet état, vous devez démarrer le serveur ou le client avec l'option --local-infile=OFF comme vu précédemment.
En d'autres termes, le droit FILE affecte seulement si le client peut lire des fichiers sur le serveur, il n'a aucun comportement sur le client s’il peut lire des fichiers sur le système en local.
III-B. Les privilèges XML et MySQL▲
La granularité trouvée dans le système de privilèges MySQL en ce qui concerne la plupart des objets de base de données ne s'étend pas au document XML. Un client MySQL qui a les accès a un document XML peut accéder a tout le document ou a n'importe quelle partie de celui-ci et il n'y a aucun moyen de restreindre l'accès a certaines parties du XML ou certains éléments.
Gardez a l'esprit que MySQl n'a pas de privilèges préventifs, ce qui veut dire, que vous ne pouvez restreindre l'accès de type d'objets particulier dans une base de données.
A la place, si vous voulez empêcher un client d'accéder a un XML trouvé dans une table, vous devez changer les droits pour que cet utilisateur n'ait plus accès du tout a cette table, ou au moins les colonnes de cette table qui contient les données XMl. XPath ne fait pas d'exceptions a cet égard.
III-C. Injection XPath▲
Une des menaces de sécurité les plus grandes aux applications est l'injection de code, le code malveillant introduit dans le système pour gagner l'accès non autorisé aux privilèges et aux données.
Elles sont connues pour exister dans plusieurs langages de programmation et de scripting.
Ce qu'ils ont tous en commun est l'exploitation des suppositions faites par des développeurs sur le type et le contenu des saisies des utilisateurs.
XPath ne fait pas d'exceptions a cet égard.
Supposons que votre application donne des autorisations en trouvant la combinaison login et password dans le fichier XML users.xml dont le contenu est montré ici :
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user
id
=
"00327"
>
<login>
douglas42</login>
<password>
24ph0d</password>
</user>
<user
id
=
"13579"
>
<login>
cherrygarcia</login>
<password>
1c3cr34m</password>
</user>
<user
id
=
"02403"
>
<login>
jimbob</login>
<password>
p4nc4k35</password>
</user>
<user
id
=
"42354"
>
<login>
kitten</login>
<password>
m3330w</password>
</user>
<user
id
=
"28570"
>
<login>
lucyvanpelt</login>
<password>
f0076411</password>
</user>
</users>
Supposons que chaque combinaison login / password est unique, votre application peut utiliser une expression XPath comme celle-ci pour valider l'utilisateur, commencer une session et associer la session a l'ID unique de l'utilisateur
//user[login/text()='cherrygarcia' and password/text()='1c3cr34m']/attribute::id
Ceci est l'équivalent d'une commande SQL comme celle-ci :
SELECT
id FROM
users WHERE
login=
'cherrygarcia'
AND
password
=
'1c3cr34m'
;
Une application PHP employant XPath et le fichier users.xml devrait utiliser le process de login via un formulaire comme ceci :
<?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
."
);
?>
L'entrée n'est pas du tous vérifiée, ce qui veut dire qu'un utilisateur malveillant peut court-circuiter le test en entrant ' or 1=1 pour le login et le password pour que l'expression XPath soit évaluée comme ceci :
//user[login/text()='' or 1=1 and password/text()='' or 1=1]/attribute::id
L'expression entre crochets est évaluée à true et est donc effectivement la même que celle-ci qui cherche l'attribut id de tous les éléments user du Xml.
//user/attribute::id
Une manière de contrer simplement cette attaque est de « quoter » les noms des variables pour les interpoler dans la définition de $xpath
$xpath
=
"
//user[login/text()='
$login
' and password/text()='
$password
']/attribute::id
"
;
Si cette technique semble familière, c'est parce que c'est celle généralement conseille pour se prémunir contre les injections SQL.
En général les pratiques pour éviter les injections XPath sont les mêmes que pour les injections SQL :
- ne jamais accepter les données non testées d'un utilisateur dans votre application ;
- vérifier le type de données utilisateurs, rejeter ou convertir les données de type erroné ;
- tester les valeurs numériques pour les valeurs hors fourchette, tronquer, arrondir ou rejeter les valeurs. Tester les chaines pour les caractères illégaux et supprimez-les ou rejetez les chaines les contenant ;
- ne renvoyez pas de messages d'erreur spécifiques, qui pourraient indiquer à un utilisateur des indices sur la façon de compromettre votre système. Loggez-les dans un fichier ou une table à la place.
Juste comme les injections SQL peuvent être utilisées pour obtenir des informations sur le schéma des tables, les injections Xpath peuvent être utilisées pour traverser les fichiers XML et découvrir leurs structures. L'espace ne me permet pas d'aller plus loin, mais vous pouvez approfondir vos connaissances sur le sujet avec le papier d'Amit Klein Blind XPath Injection.
C'est aussi important de vérifier les sorties envoyées au client. Par exemple regardons notre exemple précédent, cette fois, au lieu d'utiliser les fonctionnalités XPath de PHP, ont utilise la fonction 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)
Parce que ExtractValue() retourne des correspondances multiples dans un seul espace délimité, cette injection fournit à l'utilisateur dans une seule ligne tous les ID valides contenus dans users.xml.
Vous devriez donc tester la sortie avant de l'envoyer à l'utilisateur, par exemple :
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)
En général la ligne de conduite pour retourner des données à l'utilisateur de façon sécurisée sont les mêmes que lors de l acceptation de données de la part de celui-ci.
On peut les résumer :
- toujours tester les données en entrée pour le type et la valeur ;
- ne jamais autoriser les utilisateurs à voir des messages qui pourraient compromettre la sécurité de votre système.
Suivre ces principes pour construire une application XML peut aider pour assurer la sécurité de celle-ci.