Cet article comporte trois parties. Les deux premières sont destinées aux développeurs désireux d’écrire des extensions de SPIP ; elles présentent l’architecture générale, puis les fonctions disponibles. La troisième détaille l’implémentation et est destinée aux contributeurs de SPIP, désireux de le porter sur d’autres implémentations de SQL ou d’améliorer les portages existants. Dans tout l’article, on parlera de serveur SQL pour désigner une implémentation de SQL utilisée par SPIP, bien qu’à proprement parler certaines implémentations ne sont pas des serveurs.
Architecture générale
Dans un premier temps, on peut considérer que l’interface de SPIP aux implémentations de SQL se réduit à l’unique fonction suivante, définie dans le fichier ecrire/base/abstract_sql.php
:
sql_serveur($ins_sql, $serveur='', $continue=false)
Cette fonction commence, si elle ne l’a pas déjà fait auparavant, par se connecter au serveur spécifié en deuxième argument.
Cet argument est souvent omis, ce qui désigne alors l’implémentation SQL choisie à l’installation, et mémorisé par SPIP dans un fichier de connexion (voir Les bases de données en SPIP).
Sinon, l’argument doit indiquer explicitement le nom du fichier de connexion à utiliser, l’extension .php
étant omise. Le résultat retourné est une autre fonction, réalisant le type d’action demandée par la chaîne passée en premier argument (par exemple select
ou fetch
) sur la base SQL indiquée par le fichier de connexion.
Le troisième argument indique ce qu’il faut faire lorsqu’une telle fonction ne peut être retournée.
S’il est absent ou égal à false
, une erreur fatale sera déclenchée.
Autrement, deux autres situations sont distinguées.
Si la connexion indiquée est inconnue ou inopérante, la valeur false
sera retournée.
Autrement, sera retournée une structure de données décrivant la connexion (elle sera décrite dans la dernière partie), ce qui permet d’une part de vérifier qu’une fonction existe sans risquer l’erreur fatale, et d’autre part d’obtenir plusieurs informations avant utilisation.
Cette vision minimaliste de l’interface permet de tout faire, mais avec une syntaxe assez opaque. Si par exemple $db
est le nom d’une base dans le serveur principal, on ne le sélectionnera pas
$f = sql_serveur('selectdb');
$f($db);
Pour clarifier l’écriture, il existe un jeu de fonctions enchaînant les deux instructions ci-dessus pour les cas les plus fréquents. Par exemple, il existe
sql_selectdb($nom, $serveur='')
Ce qui permet de réécrire plus simplement l’opération précédente :
sql_selectdb($db)
De manière générale, l’interface de SPIP aux serveurs SQL est un jeu de fonctions dont le nom est sql_
f et dont le dernier argument, optionnel, est le nom du serveur. Elles appellent sql_serveur
pour obtenir une fonction f qu’elles appliquent sur leurs arguments, y compris le nom du serveur.
Toutes les fonctions dont le nom est ainsi construit sont réservées à l’interface de SPIP aux serveurs SQL.
Dans une vision orientée objet, ce jeu de fonctions représente les méthodes de l’objet sql
, mais on écrit sql_
f à la place de sql->
f, et il n’est pas nécessaire d’instancier une classe. La présence du nom du serveur dans tous les appels permet de simuler les opérations impliquant l’objet moi-même.
Ce jeu de fonctions dispense donc la plupart du temps d’utiliser sql_serveur
. Signalons également la fonction (présente dans SPIP de longue date) :
spip_connect($serveur='')
qui simplement ouvre la connexion au serveur, et est donc équivalente à
sql_serveur('', $serveur, true)
et qui retourne false si le serveur est indisponible, sinon la structure de données décrivant les possibilités du serveur.
Fonctions disponibles
Les fonctions de l’interface SQL peuvent se classer en plusieurs groupes, pour lesquels on donnera à chaque fois un tableau présentant leurs arguments. Pour les exemples on se reportera au code de SPIP.
Un premier groupe de fonctions concernent la lecture des tables SQL. Les fonctions incontournables sont :
- sql_select
dont les arguments sont les clauses SQL habituelles de cette instruction, et dont le résultat est une ressource Select ;
- sql_fetch
, utilisée presque toujours dans une boucle, qui permet de récupérer les lignes successives d’une ressource Select ; son résultat est un tableau indexé par le nom des champs ;
- sql_free
qui signale au serveur de libérer une ressource.
D’autres fonctions offrent des compositions fréquentes de ces opérations :
- sql_fetsel
de mêmes arguments que sql_select
, qui applique celle-ci sur ses arguments puis sql_fetch
sur la ressource retournée ; pratique pour les requêtes dont le résultat ne comporte qu’une ligne ;
- sql_getfetsel
de mêmes arguments que sql_select
, qui applique celle-ci sur ses arguments puis sql_fetch
sur la ressource retournée, et enfin extrait du tableau retourné le champ dont le nom est donné comme premier argument (la liste des champs n’en comporte donc qu’un) ; pratique pour les requêtes dont le résultat ne comporte qu’une ligne d’un seul champ ;
- sql_allfetsel
de mêmes arguments que sql_select
, qui applique celle-ci sur ses arguments puis sql_fetch
sur la ressource retournée tant que celui-ci retourne un tableau non vide ; sql_allfetsel
retourne pour finir le tableau de tous les tableaux retournés par sql_fetch
(attention à ne pas saturer la mémoire avec cette fonction) ;
- sql_countsel
de mêmes arguments que sql_select
moins le premier, qui applique celle-ci sur COUNT(*)
et ses autres arguments et retourne le nombre calculé ; pratique pour connaître le nombre de lignes que retournerait la requête Select ainsi décrite.
- sql_count
qui retourne le nombre de lignes d’une ressource Select, comme si on avait ajouté COUNT(*)
dans la requête ;
- sql_get_select
utilise les mêmes arguments que sql_select
, mais retourne le code SQL de la requête, sans l’exécuter. Cette fonction peut-être utile pour les besoins de certains plugins ou pour créer facilement des vues SQL.
Les quatre premières appellent pour finir sql_free
, et sont donc à préférer autant que possible au trio select-fetch-free
dont on oublie facilement le dernier membre.
Le tableau ci-dessous précise le contenu et l’ordre des arguments attendus par ces fonctions.
Fonction | Arguments |
sql_select |
|
sql_fetch |
|
sql_free |
|
sql_count |
|
sql_countsel |
|
sql_fetsel |
|
sql_allfetsel |
|
sql_getfetsel |
|
sql_get_select |
|
Dans les fonctions ci-dessus, si la clause Select est fournie sous forme de tableau, ses éléments seront concaténés, séparés par des virgules. En cas de tableau pour les clauses Where et Having, les éléments doivent être des chaînes (des sous-tableaux en notation préfixée sont également pris en charge, mais réservés au compilateur). Ces chaînes seront réunies en une grande conjonction (i.e, elles seront concaténées avec AND comme séparateur).
La clause From est une chaîne (le cas du tableau est réservé au compilateur de SPIP). Attention : s’il est nécessaire de référencer les tables dans les autres clauses, il faut en définir des alias dans ce paramètre et les utiliser systématiquement. Ainsi, on écrira :
sql_countsel('spip_articles AS a, spip_rubriques AS r', "a.id_secteur=r.id_rubrique AND r.titre='monsecteur')
ou
sql_countsel('spip_articles AS a JOIN spip_rubriques AS r ON a.id_secteur=r.id_rubrique", "r.titre='monsecteur'")
alors que l’écriture suivante ne sera pas comprise :
sql_countsel('spip_articles, spip_rubriques', "spip_articles.id_rubrique=spip_rubriques.id_secteur AND spip_rubriques.titre='monsecteur'")
Un deuxième groupe de fonctions est constitué par celles modifiant le contenu des tables. Ces fonctions sont délicates à définir car la syntaxe des valeurs à introduire dans les tables change d’un serveur SQL à un autre (notamment les dates). Pour cette raison, ces fonctions doivent disposer de la description de la table à modifier, afin de connaître le type des valeurs attendues par le serveur SQL. SPIP retrouve automatiquement ces informations (données au moment de la création de la table) mais il est possible de fournir une description arbitraire (avant-dernier argument de ces fonctions, optionnel et d’ailleurs rarement utile).
SPIP fournit donc une fonction d’insertion, sql_insertq
, et une fonction de mise à jour, sql_updateq
, qui prennent un tableau champ=>valeur et s’occupent de citer les valeurs en fonction du type (avec la fonction sql_quote
spécifiée ci-dessous).
Est également disponible sql_insertq_multi
permettant de faire des insertions de plusieurs entrées en prenant un tableau de tableau champ=>valeur.
Pour les mises à jour où les nouvelles valeurs dépendent des anciennes (comme dans cpt=cpt+1
), utiliser sql_update
où les valeurs seront prises littérallement, mais il faudra interdire soigneusement les possibilités d’attaque par injection de code.
Il existe également sql_replace
, fonction effectuant une mise à jour sur une ligne correspondant à une clé primaire, ou insérant les valeurs si cette ligne n’existe pas
ainsi qu’une fonction sql_replace_multi
pour des mises à jour ou insertions multiples.
Enfin, sql_delete
efface d’une table les lignes répondant à une clause Where.
Fonction | Arguments |
sql_updateq |
|
sql_update |
|
sql_insertq |
|
sql_insertq_multi |
|
sql_replace |
|
sql_replace_multi |
|
sql_delete |
|
Un groupe un peu à part est formé de fonctions traitant spécifiquement des opérandes. : elles ne se connectent pas au serveur, mais retournent des chaînes dépendant de celui-ci :
- sql_quote
prend une chaîne ou un nombre, retourne un nombre si l’argument était un nombre ou une chaîne représentant un entier, sinon retourne la chaîne initiale entourée d’apostrophes et avec les apostrophes protégées selon la syntaxe propre au serveur (un \ devant pour certains, une deuxième apostrophe pour d’autres) ;
- sql_hex
prend une chaîne de chiffres hexadécimaux et retourne sa représentation dans le serveur SQL visé ;
- sql_in
construit un appel à l’opérande IN, en traitant les éventuelles valeurs hexadécimales y figurant ;
- sql_test_int
prédicat retournant Vrai si le type SQL fourni désigne un entier ;
- sql_test_date
prédicat retournant Vrai si le type SQL fourni désigne une date ;
- sql_multi
applique une expression SQL sur champ contenant un bloc multi (voir Réaliser un site multilingue) pour y prendre la partie correspondant à la langue indiquée ; l’intérêt d’effectuer cette opération au niveau SQL est essentiellement de demander simultanément un tri sur cette colonne.
Fonction | Arguments |
sql_quote |
|
sql_hex |
|
sql_in |
|
sql_multi |
|
sql_test_date |
|
sql_test_int |
|
Un groupe important est constitué par les fonctions manipulant les déclarations de bases et de tables. Pour des raisons historiques, cette première version de l’interface reprend quasi littéralement la syntaxe de MySQL3 et devra certainement être revue, en particulier pour y faire apparaître la déclaration des jointures. Les fonctions sql_create
, sql_alter
, sql_showtable
et sql_drop_table
permettent de créer, modifier, voir et supprimer une table.
Les fonctions sql_create_view
, sql_drop_view
permettent de créer ou supprimer une vue.
Les fonctions sql_listdbs
, sql_showbase
et sql_selectdb
permettent de voir les bases accessibles, de voir leur contenu et d’en sélectionner une. À noter que tous les hébergeurs n’autorisent pas forcément de telles actions ; SPIP essaiera de le deviner, en notant ses essais dans le fichier spip.log
.
Fonction | Arguments |
sql_create |
|
sql_alter |
|
sql_showtable |
|
sql_drop_table |
|
sql_create_view |
|
sql_drop_view |
|
sql_listdbs | |
sql_selectdb |
|
sql_showbase |
|
Deux fonctions permettent de régler le codage des caractères lors des communications avec le serveur :
- sql_set_charset
, demande d’utiliser le codage indiqué ;
- sql_get_charset
, demande si un codage de nom donné est disponible sur le serveur.
Fonction | Arguments |
sql_get_charset |
|
sql_set_charset |
|
Un dernier groupe de fonctions offre quelques outils de gestion des requêtes et des tables ; on se reportera à leurs homonymes dans la documentation des serveurs SQL. Signalons toutefois que sql_explain
est utilisée implicitement par le débusqueur de SPIP, accessible par les boutons d’administration de l’espace public, lorsqu’on lui demande le plan de calcul d’une boucle (ou de tout un squelette).
Fonction | Arguments |
sql_optimize |
|
sql_repair |
|
sql_explain |
|
sql_error |
|
sql_errno | |
sql_version |
Hors groupe, la fonction générale sql_query
, nom qu’aurait dû porter l’historique
spip_query
; leur utilisation est de toute façon à éviter.
Réalisation des portages
Cette section est destinée à ceux souhaitant porter SPIP sur d’autres serveurs SQL, ou ayant besoin d’informations plus techniques, notamment sur la gestion des versions de l’interface. Les fonctions du fichier ecrire/base/abstract_sql.php
étudiées ci-dessus se contentent d’offrir une interface homogène aux différents serveurs, mais ne procèdent elles-mêmes à aucun calcul. C’est dans le fichier ecrire/base/connect_sql.php
que se situe le travail effectif.
La fonction essentielle est spip_connect
qui ouvre la connexion au serveur SQL indiqué par son argument (ou, s’il est omis, le serveur principal) en repérant les connexions déjà faites. Cette ouverture consiste à inclure un fichier de connexion créé lors de l’installation de SPIP par les scripts présents dans le répertoire install
. Un fichier de connexion se réduit pour l’essentiel à appliquer la fonction spip_connect_db
aux valeurs fournies lors de l’installation.
La fonction spip_connect_db
reçoit en particulier comme argument le type du serveur SQL. Ce type doit être le nom d’un fichier présent dans le répertoire req
.
Ce fichier est chargé et doit définir toutes les fonctions d’interfaces définies à la section précédente, plus la fonction req_
type_dist
qui sera immédiatement appliquée sur les mêmes arguments que spip_connect_db
, type excepté. C’est cette fonction qui doit établir effectivement la connexion.
Porter SPIP sur d’autres serveurs SQL consiste donc à définir ce jeu de fonctions et à le placer dans le répertoire req
.
Le gestionnaire de versions d’interface repose sur le deuxième argument de spip_connect
qui indique la version, la version courante étant prise par défaut.
Toutes les fonctions de l’interface sont définies dans le fichier abstract_sql
, se nomment sql_X et sont les seules à se nommer ainsi. Elles se connectent toutes en appelant une variante de spip_connect
dont le premier argument est le numéro de version de l’interface. Au cas où le fichier abstract_sql
nécessiterait une révision, il sera renommé abstract_sql_N, et le Sed suivant lui sera appliqué (N désigne le numéro de version) :
s/\(sql_[A-Za-z_0-9 ]*\)/\1_N/
En appliquant également ce script aux extensions de SPIP fondées sur cette version, on leur permettra d’en appeler ses fonctions, qui seront chargées sans collision de noms, le Sed ayant préfixé le nom des anciennes avec leur numéro de version. Il faudra juste rajouter une instruction include portant sur le fichier abstract_sql_N.
Du côté du portage, il faudra renommer pareillement les fichiers du répertoire
req
, et écrire les nouvelles versions.
La coexistence de plusieurs versions de l’interface pendant l’exécution de SPIP repose sur la structure décrivant le serveur. Celle-ci est en fait un tableau, contenant :
- link
, ressource indiquant la connexion ;
- db
, nom de la base de données ;
- prefixe
, nom du préfixe de table ;
- ldap
, nom de l’éventuel fichier décrivant le serveur LDAP.
Les autres entrées sont les numéros de versions disponibles, et leur valeur est le tableau des fonctions implémentant cette version de l’interface.
Les autres fonctions du fichier connect_sql
concernent essentiellement la gestion des versions et le traitement de quelques cas particuliers de déclarations des tables standards de SPIP.