XQuery et le typage statique

XQuery dans SQL Server est un langage typé statiquement. Autrement dit, il génère des erreurs de type lors de la compilation des requêtes dès qu'une expression renvoie une valeur dotée d'un type ou d'une cardinalité qu'une fonction ou un opérateur spécifique n'accepte pas. De plus, la vérification des types statiques peut également déceler si une expression de chemin d'accès faisant référence à un document XML typé a été mal typée. Le compilateur XQuery commence par une phase de normalisation au cours de laquelle s'ajoutent les opérations implicites (atomisation, par exemple), puis effectue l'inférence de type statique et la vérification des types statiques.

Inférence de type statique

L'inférence de type statique consiste à déterminer le type de retour d'une expression. Pour cela, il faut prendre en compte les types statiques des paramètres d'entrée et la sémantique statique de l'opération, puis inférer le type statique du résultat. Par exemple, le type statique de l'expression 1 + 2,3 est déterminé de la manière suivante :

  • Le type statique de 1 est xs:integer et le type statique de 2,3 est xs:decimal. En fonction de la sémantique dynamique, la sémantique statique de l'opération + convertit l'entier en valeur décimale, puis renvoie une valeur décimale. Le type statique inféré serait alors xs:decimal.

En cas d'instances XML non typées, il existe des types particuliers pour indiquer que les données ne sont pas typées. Ces informations servent à vérifier le type statique et à effectuer certaines conversions implicites.

En cas de données typées, le type d'entrée est inféré à partir de la collection de schémas XML qui contraint l'instance du type de données XML. Par exemple, si le schéma n'autorise que des éléments de type xs:integer, les résultats d'une expression de chemin d'accès utilisant cet élément seront zéro ou plusieurs éléments de type xs:integer. Cela s'exprime actuellement à l'aide d'une expression telle que element(age,xs:integer)* où l'astérisque (*) indique la cardinalité du type résultant. Dans cet exemple, l'expression peut donner comme résultat zéro ou plusieurs éléments de nom « age » et de type xs:integer. D'autres cardinalités s'expriment de façon différente : un et un seul s'exprime à l'aide du nom du type seulement, zéro ou un s'exprime à l'aide d'un point d'interrogation (?), et 1 ou plus à l'aide d'un signe plus (+).

Il arrive parfois que l'inférence de type statique induise le renvoi d'une séquence vide par une expression. Par exemple, si une expression de chemin d'accès portant sur un type de données XML typé recherche un élément <name> dans un élément <customer> (/customer/name), mais que le schéma n'autorise pas la présence de <name> à l'intérieur de <customer>, l'inférence de type statique générera un résultat vide. Il est ainsi possible de détecter des requêtes incorrectes et de signaler une erreur statique, à moins que l'expression ne soit () ou data( () ).

Des règles détaillées d'inférence sont fournies dans la sémantique formelle de la spécification XQuery. Microsoft les a légèrement modifiées pour les rendre compatibles avec les instances du type de données XML typé. Suite à la modification la plus importante apportée à cette norme, le nœud de document implicite connaît le type de l'instance du type de données XML. Par conséquent, une expression de chemin d'accès de la forme /age sera typée précisément en fonction de cette information.

En utilisant le Utilisation du Générateur de profils SQL Server, vous pouvez voir les types statiques retournés au cours des compilations de requêtes. Pour les consulter, votre trace doit inclure l'événement XQuery Static Type dans la catégorie d'événement TSQL.

Vérification des types statiques

La vérification des types statiques permet de s'assurer que, lors de l'exécution, seules des valeurs de type compatible avec l'opération seront reçues. Dans la mesure où les types n'ont pas à être vérifiés lors de l'exécution, des erreurs potentielles peuvent être détectées à l'avance au moment de la compilation. Les performances s'en trouvent donc améliorées. En revanche, le typage statique demande à l'auteur de la requête un soin supplémentaire dans la formulation de la requête.

Voici les types appropriés qu'il est possible d'utiliser :

  • Types explicitement autorisés par une fonction ou une opération.

  • Sous-type d'un type explicitement autorisé.

Les sous-types sont définis en fonction des règles de sous-typage permettant de les dériver par restriction ou extension du schéma XML. Par exemple, un type S est un sous-type du type T si toutes les valeurs dotées du type S sont aussi des instances du type T.

De plus, toutes les valeurs entières sont aussi des valeurs décimales, conformément à la hiérarchie du type de schéma XML. En revanche, toutes les valeurs décimales ne sont pas des entiers. Si un entier est un sous-type de décimal, la réciproque est fausse. Par exemple, l'opération + n'autorise des valeurs que de certains types comme les types numériques xs:integer, xs:decimal, xs:float et xs:double. Si des valeurs d'autres types, comme xs:string, sont transmises, l'opération génère une erreur de type. Cela s'appelle le typage fort. Des valeurs d'autres types, comme le type atomique utilisé pour indiquer la présence de XML non typé, peuvent être converties implicitement en une valeur d'un type accepté par l'opération. Cela s'appelle le typage faible.

Si elle est obligatoire après une conversion implicite, la vérification des types statiques permet de s'assurer que seules les valeurs dotées de types autorisés et d'une cardinalité correcte sont transmises à une opération. Dans le cas de "string" + 1, la vérification reconnaît que le type statique de "string" est xs:string, mais puisque ce type n'est pas autorisé avec l'opération +, une erreur de type est générée.

En cas d'ajout du résultat d'une expression arbitraire E1 à une expression arbitraire E2 (E1 + E2), l'inférence de type statique détermine tout d'abord les types statiques de E1 et de E2, puis vérifie que ces types statiques sont autorisés avec l'opération. Par exemple, si le type statique de E1 peut être xs:string ou xs:integer, la vérification des types statiques génère une erreur de type bien que certaines valeurs puissent être des entiers lors de l'exécution. Il en irait de même si le type statique de E1 était xs:integer*. Dans la mesure où l'opération + n'accepte qu'une et une seule valeur entière et que E1 peut en renvoyer zéro ou plus d'une, la vérification des types statiques génère une erreur.

Comme nous l'avons déjà mentionné, il arrive fréquemment que l'inférence de type déduise un type plus large que ce que sait l'utilisateur à l'égard des données transmises. Dans ces cas-là, l'utilisateur doit réécrire la requête. Voici quelques exemples classiques de cette situation :

  • Le type infère un type plus général tel qu'un supertype ou une union de types. Si le type est un type atomique, vous devez utiliser l'expression cast ou la fonction constructor pour indiquer le type statique réel. Par exemple, si le type inféré de l'expression E1 est soit xs:string, soit xs:integer, et que l'addition requiert xs:integer, vous devez écrire xs:integer(E1) + E2 au lieu de E1+E2. Cette expression risque d'échouer au moment de l'exécution en présence d'une valeur de chaîne, impossible à convertir en xs:integer. En revanche, l'expression passera désormais la vérification des types statiques. Avec l'arrivée de SQL Server 2005, cette expression est mappée avec la séquence vide.

  • Le type infère une cardinalité supérieure à celle que les données contiennent réellement. Cela se produit fréquemment puisque le type de données xml peut contenir plusieurs éléments de premier niveau et qu'une collection de schémas XML ne peut imposer aucune contrainte à cet égard. Afin de réduire le type statique et de garantir qu'il n'y a en fait qu'une seule et unique valeur transmise, vous devez utiliser le prédicat positionnel [1]. Par exemple, pour ajouter 1 à la valeur de l'attribut c de l'élément b situé sous l'élément a de niveau supérieur, vous devez write (/a/b/@c)[1]+1. En outre, le mot clé DOCUMENT peut être utilisé avec une collection de schémas XML.

  • Certaines opérations perdent les informations de type lors de l'inférence. Par exemple, s'il est impossible de déterminer le type d'un nœud, ce dernier devient anyType. Il n'est pas implicitement converti en un autre type. Ces conversions se produisent le plus souvent pendant la navigation à l'aide de l'axe parent. Vous devez éviter d'utiliser ce type d'opérations et réécrire la requête si l'expression crée une erreur de type statique.

Contrôle des types union

La manipulation des types union demande un soin particulier du fait du contrôle du type. Deux des problèmes rencontrés sont expliqués dans les exemples suivants.

Exemple : fonction sur un type union

Considérez une définition d'élément pour <r> d'un type union :

<xs:element name="r">
<xs:simpleType>
   <xs:union memberTypes="xs:int xs:float xs:double"/>
</xs:simpleType>
</xs:element>

Dans le contexte XQuery, la fonction « moyenne » fn:avg (//r) renvoie une erreur statique puisque le compilateur XQuery ne peut pas ajouter des valeurs de types différents (xs:int, xs:float ou xs:double) pour les éléments <r> dans l'argument de fn:avg() Pour résoudre ce problème, réécrivez l'appel de fonction sous la forme fn:avg(for $r in //r return $r cast as xs:double ?).

Exemple : opérateur sur un type union

L'opération addition (« + ») requiert les types exacts des opérandes. Par conséquent, l'expression (//r)[1] + 1 retourne une erreur statique qui a la définition de type décrite précédemment pour l'élément <r>. Une solution consiste à la réécrire sous la forme (//r)[1] cast as xs:int? +1, où « ?  » indique 0 ou 1 occurrence. Avec l'arrivée de SQL Server 2005, SQL Server requiert « cast as » avec « ? », parce qu'une conversion peut générer la séquence vide comme résultat des erreurs d'exécution.