La interfície d’SPIP amb SQL

Les funcions a cridar i les coses que cal saber per ampliar SPIP de manera portable en quan al seu ús de SQL.

Recordem primer de tot que el Structured Query Langage s’ha anat estandaritzant progressivament i que cada nova implementació esmenava les llacunes de l’especificació amb les seves pròpies solucions. Les primeres versions d’SPIP no coneixien més que una sola implementació de SQL cosa que li permetia centralitzar-ne l’ús per mitjà d’una única funció (spip_query) de la que l’únic argument era la consulta SQL. Al portar SPIP a diferents implementacions de SQL ha imposat renunciar a aquest model: aquesta funció resta disponible per motius de compatibilitat, però el seu ús s’ha d’evitar a partir d’ara. És preferible usar el conjunt de funcions següent, que té a més l’avantatge de neutralitzar la major part de tècniques d’atac per injecció de codi.

El primer cop que SPIP es va portar a un altre servidor diferent de MySQL3 va ser pel servidor PostGreSQL versió 8.2. Immediatament després es va portar per SQLite 2 y 3.

El procés de portar SPIP a diverses bases de dades es fonamenta en substituir la funció spip_query per tantes funcions com instruccions SQL (select, update, etc), cada una d’elles amb tants arguments com clàusules admeti la instrucció (where, limit, etc), més un argument opcional que especifica la base SQL. Els operands SQL (en particular les dates i els números en hexadecimal) resten escrits en sintaxi MySQL, i les funcions d’interfície s’encarreguen de tornar-los a escriure si és necessari.

Es proposen algunes funcions suplementàries: algunes indispensables (accés a les línies successives d’un resultat de select i algunes com abreviacions (descompte, accés a una línia que se sap que és única, etc). Es proporcionen també funcions d’informació general: alfabets utilitzats, presència d’un LDAP, etc.

El procés de portar SPIP a SQLite no va necessitar revisar el joc de funcions definit en un principi per portar a PostGres, la qual cosa demostra la perdurabilitat de la interfície definida d’aquesta manera. No obstant, com que aquestes utilitats estan destinades a evolucionar abans o després, també aquesta nova interfície, proposada amb SPIP 2.0, integra des d’aquest moment un gestor de les seves pròpies versions, cosa que li permet utilitzar una connexió SQL amb diverses versions de la interfície al mateix temps. D’aquesta manera, les extensions d’SPIP que respectin aquesta nova interfície s’asseguren el seu funcionament amb les versions posteriors d’SPIP.

Destaquem per acabar que l’elecció de desenvolupar aquesta arquitectura va ser imposada per l’ineexistència d’una biblioteca de nivell d’abstracció equivalent que estigués disponible tan breument com SPIP; les extensions de PHP ofertes actualment s’acontenten amb uniformitzar les crides a les funcions de base, la qual cosa és només la punta de l’iceberg al que s’enfronta el desenvolupador d’aplicacions sobre bases de dades heterogènies

Aquest article consta de tres parts. Les dues primeres estan destinades als desenvolupadors desitjosos d’escriure extensions d’SPIP; presenten l’arquuitectura general i, després, les funcions disponibles. La tercera part detalla la implementació i està destinada als contribuïdors d’PIP, desitjosos de portar-lo cap a altres implementacions de SQL o de millorar les que existeixen. Al llarg de tot l’article, parlarem de servidor SQL per designar una implementació de SQL utilitzada per SPIP, encara que parlant en propietat algunes implementacions no siguin de servidors.

Arquitectura general

En un primer temps, es pot considerar que la interfície d’SPIP amb les implementacions de SQL es redueix únicament a la funció següent, definida en el fitxer ecrire/base/abstract_sql.php:

sql_serveur($ins_sql, $serveur='', $continue=false)

Aquesta funció comença, si no s’ha fet abans, per connectar-se al servidor especificat en el segon argument. Aquest argument s’omet sovint, cosa que designa llavors la implementació SQL escollida a la instal·lació, i memoritzada per SPIP en un fitxer fitxer de connexió (vegeu Les bases de dades a SPIP). Sinó, l’argument ha d’indicar de manera explícita el nom del fitxer de connexió que s’ha d’utilitzar, ometent l’extensió .php. El resultat que retorna és una altra funció, que realitza el tipus d’acció demanada per la cadena passada en el primer argument (per exemple select o fetch) a la base SQL indicada pel fitxer de connexió. El tercer argument indica què cal fer quan aquesta funció no es pot retornar. Si no hi és o és igual a false, es produirà un error fatal. De no ser així, es poden distingir dues situacions més. Si la connexió indicada no és coneguda o inoperant, es retornarà el valor false. Si no és així, es retornarà una estructura de dades que descruen la connexió (es descriurà a la última part), cosa que permet, d’una banda, verificar que una funció existeix sense caure en el risc d’un error fatal i, de l’altra, obtenir diverses informacions abans de ser utilitzades.

Aquesta visió minimalista de la interfície permet fer-ho tot, però amb una sintaxi força opaca. Si, per exemple, $db és el nom d’una base al servidor principal, no la seleccionarà

$f = sql_serveur('selectdb');
$f($db);

Per tal de clarificar l’escriptura, existeix més avall un joc de funcions que encadenen les dues instruccions pels casos més freqüents. Per exemple, existeix

sql_selectdb($nom, $serveur='') 

El que permet reescriure de manera més simple l’operació anterior:

sql_selectdb($db)

De manera general, la interfície d’SPIP als servidors SQL és un joc de funcions el nom de les quals és sql_f i del que el darrer argument, opcional, és el nom del servidor. Criden sql_serveur per obtenir una funció f que apliquen als seus arguments, inclòs el nom del servidor. Totes les funcions amb el nom construït d’aquesta manera estan reservades per la interfície d’SPIP als servidors SQL.

En un punt de vista orientat a objectes, aquest joc de funcions representa els mètodes de l’objecte sql, però s’escriu sql_f en lloc de sql->f, i no cal instanciar una classe. La presència del nom del servidor a totes les crides permet simular les operacions que impliquen l’objecte per si mateix.

Per tant, aquest joc de funcions dispensa utilitzar la major part de les vegades sql_serveur. Senyalem, també, la funció (present a SPIP des de fa molt de temps) :

spip_connect($serveur='')

que simplement obre la connexió al servidor, i, per tant, és equivalent a

sql_serveur('', $serveur, true)

i que retorna false si el servidor no es troba disponible, i sinó l’estructura de dades que descriuen les possibilitats del servidor.

Funcions disponibles

Les funcions de la interfície SQL es poden classificar en diversos grups, pels que es mostrarà cada cop una taula presentant els seus arguments. Pels exemples ens podem dirigir al codi d’SPIP.

Un primer grup de funcions que es refereixen a la lectura de les taules SQL. Les funcions imprescindibles són:

-  sql_select els arguments del qual són les clàusules SQL habituals d’aquesta instrucció, i el resultat de la qual és un recurs Select;

-  sql_fetch, utilitzat gairebé sempre en un bucle, permet recuperar les línies successives d’un recurs Select; i el seu resultat és una taula indexada pel nom dels camps;

-  sql_free que assenyala al servidor que pot alliberar un recurs.

Altres funcions ofereixen composicions freqüents d’aquestes operacions:

-  sql_fetsel amb els mateixos arguments que sql_select, que s’aplica sobre els seus arguments i després sql_fetch sobre el recurs retornat; pràctic per les consultes amb un resultat que no suposi més d’una línea;

-  sql_getfetsel amb els mateixos arguments que sql_select, que s’aplica sobre els seus arguments i després sql_fetch sobre el recurs retornat, i finalment extreu de la taula retornada el camp el nom del qual es dóna com a primer argument (per tant, la llista dels camps només en comporta un); pràctic per les consultes el resultat de les quals estigui format per una única línia d’un sol camp.

-  sql_allfetsel amb els mateixos arguments que sql_select, que s’aplica sobre els seus arguments i després sql_fetch sobre el recurs retornat sempre i quan aquest retorni una taula que no estigui buida; per acabar sql_allfetsel retorna la taula de totes les taules retornades per sql_fetch (alerta a no saturar la memòria amb aquesta funció);

-  sql_countsel amb els mateixos arguments que sql_select excepte el primer, que aplica aquest sobre COUNT(*) i els seus altres arguments i retorna el númerp calculat; pràctic per conèixer el número de línies que retornaria la consulta Select descrita d’aquesta manera.

-  sql_count que retorna el número de línies d’un recurs Select, com si s’hi hagués afegit COUNT(*) a la petició;

-  sql_get_select fa servir els mateixos arguments que sql_select, però retorna el codi SQL de la petició, sense executar-la. Aquesta funció pot ser útil per les necessitats d’alguns connectors o per crear fàcilment vistes SQL.

Les quatre primeres criden per acabar sql_free, i són, per tant, preferibles sempre que sigui possible al trio select-fetch-free del que s’oblida fàcilment el darrer membre.

La següent taula precisa el contingut i l’ordre dels arguments esperats per aquestes funcions.

Funció Arguments
sql_select
  1. llista de camps: cadena o taula
  2. llista de taules: cadena o taula
  3. clàusula Where : cadena o taula
  4. clàusula Groupby : cadena amb coma de separació o taula
  5. clàusula Orderby: cadena amb coma de separació o taula
  6. clàusula Limit: un o dos enters separats per comes
  7. clàusula Having: cadena o taula
  8. servidor
sql_fetch
  1. recurs
  2. servidor
sql_free
  1. recurs
  2. servidor
sql_count
  1. recurs
  2. servidor
sql_countsel
  1. llista de taules: cadena o taula
  2. clàusula Where: cadena o taula
  3. clàusula Groupby: cadena o taula
  4. clàusula Orderby: cadena amb coma de separació o taula
  5. clàusula Limit: un o dos enters separats per comes
  6. clàusula Having: cadena o taula
  7. servidor
sql_fetsel
  1. llista de camps: cadena o taula
  2. l llista de taules: cadena o taula
  3. clàusula Where: cadena o taula
  4. clàusula Groupby: cadena amb coma de separació o taula
  5. clàusula Orderby: cadena amb coma de separació o taula
  6. clàusula Limit: un o dos enters separats per comes
  7. clàusula Having: cadena o taula
  8. servidor
sql_allfetsel
  1. llista de camps: cadena o taula
  2. llista de taules: cadena o taula
  3. clàusula Where: cadena o taula
  4. clàusula Groupby: cadena o taula
  5. clàusula Orderby: cadena amb coma de separació o taula
  6. clàusula Limit: un o dos enters separats per comes
  7. clàusula Having: cadena o taula
  8. servidor
sql_getfetsel
  1. nom d’un camp: cadena
  2. llista de taules: cadena o taula
  3. clàusula Where: cadena o taula
  4. clàusula Groupby: cadena o taula
  5. clàusula Orderby: cadena amb coma de separació o taula
  6. clàusula Limit: un o dos enters separats per comes
  7. clàusula Having: cadena o taula
  8. servidor
sql_get_select
  1. llista de camps: cadena o taula
  2. llista de taules: cadena o taula
  3. clàusula Where: cadena o taula
  4. clàusula Groupby: cadena o taula
  5. clàusula Orderby: cadena amb coma de separació o taula
  6. clàusula Limit: un o dos enters separats per comes
  7. clàusula Having: cadena o taula
  8. servidor

A les funcions anteriors, si la clàusula Select és facilitada en forma de taula, els seus elements s’encadenaran, separats per comes. En cas de taula per les clàusules Where i Having, els elements han de ser cadenes (també es tenen en compte les subtaules en notació prefixada però són reservades al compilador). Aquestes cadenes es reuniran en una gran conjunció (és a dir, seran encadenades amb AND com a separador).

La clàusula From és una cadena (el cas de la taula es reserva al compilador d’SPIP). Alerta: si cal referenciar les taules a les altres clàusules, cal definir-ne àlies en aquest paràmetre i utilitzar-los sistemàticament. Per tant, escriurem:

sql_countsel('spip_articles AS a, spip_rubriques AS r', "a.id_secteur=r.id_rubrique AND r.titre='monsecteur')

o

sql_countsel('spip_articles AS a JOIN spip_rubriques AS r ON a.id_secteur=r.id_rubrique", "r.titre='monsecteur'")

mentre que la següent escriptura no es comprendrà:

sql_countsel('spip_articles, spip_rubriques', "spip_articles.id_rubrique=spip_rubriques.id_secteur AND spip_rubriques.titre='monsecteur'")

Un segon grup de funcions està constituït per aquelles que modifiquen el contingut de les taules. Aquestes funcions són delicades de definir ja que la sintaxi dels valors que s’hi ha d’introduir varia d’un servidor SQL a un altre (sobretot les dates). Per aquesta raó, aquestes funcions han de disposar de la descripció de la taula a modificar, per tal de conèixer el tipus de valors esperats pels servidor SQL. SPIP retroba automàticament aquestes informacions (donades al moment de crear la taula) però és possible subministrar una descripció arbitrària (penúltim argument d’aquestes funcions, opcional i per altra banda escassament útil).

SPIP proporciona, per tant, una funció d’inserció, sql_insertq, i una funció d’actualització, sql_updateq, que agafen una taula camp=>vaor i s’encarreguen de citar els valors en funció del tipus (amb la funció sql_quote especificada més avall). També hi ha disponible sql_insertq_multi que permet fer insercions de diverses entrades agafant una taula de taula camp=>valor. Per les actualitzacions on els nous valors depenguin dels antics (com a cpt=cpt+1), utilitzar sql_update on els valor es prendran literalment però caldrà prohibir amb cura les possibilitats d’atac per injecció de codi. Existeix igualment sql_replace, funció que fa una actualització sobre una línia corresponent a una clau primària, o inserint els valors si aquesta línia no existeix com a tal funció sql_replace_multi per actualitzacions o insercions múltiples. Finalment, sql_delete esborra d’una taula les línies que responen a una clàusula Where.

Funció Arguments
sql_updateq
  1. taula
  2. taula camp=>valor a citar
  3. clàusula Where
  4. descripció
  5. servidor
sql_update
  1. taula
  2. taula camp=>valor
  3. clàusula Where
  4. descripció
  5. servidor
sql_insertq
  1. taula
  2. taula camp=>valor a citar
  3. descripció
  4. servidor
sql_insertq_multi
  1. taula
  2. taula de taula camp=>valor a citar
  3. descripció
  4. servidor
sql_replace
  1. taula
  2. taula camp=>valor a citar
  3. descripció
  4. servidor
sql_replace_multi
  1. taula
  2. taula de taula camp=>valor a citar
  3. descripció
  4. servidor
sql_delete
  1. $table
  2. $where
  3. servidor

Un grup una mica a banda està format per aquelles funcions que tracten específicament dels operands: no es connecten a un servidor, però retornen cadenes que depenen d’aquest:

-  sql_quote agafa una cadena o un nombre, retorna un nombre si l’argument era un nombre o una cadena representant un enter, sinó retorna la cadena inicial envoltada d’apòstrofs i amb els apòstrofs protegits segons la sintaxi pròpia del servidor (un \ al davant en alguns, un segon apòstrof en uns altres);

-  sql_hex agafa una cadena de xifres hexadecimals i retorna la seva representació al servidor donat;

-  sql_in construeix una crida a l’operand IN, tractant els eventuals valors hexadecimals que hi figuren;

-  sql_test_int prédicat retournant Vrai si le type SQL fourni désigne un entier ;

-  sql_test_date predicat que retorna Veritable si el tipus SQL proporcionat designa una data;

-  sql_multi aplica una expressió SQL a un camp que contingui un bloc multi (vegeu Realitzar un lloc multilingüe) per aprendre la part corresponent a la llengua indicada; l’interès en efectuar aquesta operació a nivell SQL es basa essencialment en demanar simultàniament una selecció en aquesta columna.

Funció Arguments
sql_quote
  1. valor
  2. servidor
sql_hex
  1. valor
  2. servidor
sql_in
  1. columna
  2. valors
  3. vertader si es desitja una negació
  4. servidor
sql_multi
  1. columna
  2. llengua
  3. servidor
sql_test_date
  1. tipus
  2. servidor
sql_test_int
  1. tipus
  2. servidor

Un grup important està format per les funcions que manipulen les declaracions de bases i taules. Per raons històriques, aquesta primera versió de la interfície reprèn gairebé literalment la sintaxi de MySQL3 i haurà de ser revisada, sobretot per fer-hi aparèixer la declaració de les unions. Les funcions sql_create, sql_alter, sql_showtable i sql_drop_table permeten crear, modificar, veure i suprimir una taula. Les funcions sql_create_view, sql_drop_view permeten crear o suprimir una vista. Les funcions sql_listdbs, sql_showbase i sql_selectdb permeten veure les bases accessibles, veure’n el seu contingut i seleccionar-ne una. Fixem-nos que els hostatjadors no autoritzen forçosament aquestes funcions; SPIP provarà d’endevinar-ho, anotant els seus assajos en el fitxer spip.log.

Funció Arguments
sql_create
  1. nom de la taula
  2. taula nom de columna => tipus SQL i valor per defecte
  3. taula nom d’índex => columna(es)
  4. veritable si hi ha auto-increment
  5. veritable si és temporal
  6. servidor
sql_alter
  1. petició MYSQL Alter
  2. servidor
sql_showtable
  1. RegExp
  2. veritable si la taula ha estat declarada per SPIP
  3. servidor
sql_drop_table
  1. nom de la taula
  2. veritable si és necessari inserir la condició existe
  3. servidor
sql_create_view
  1. nom de la vista
  2. petició de selecció de camps (creada per exemple amb sql_get_select)
  3. servidor
sql_drop_view
  1. nom de la vista
  2. veritable si cal inserir la condició existe
  3. servidor
sql_listdbs
sql_selectdb
  1. nom de la base
  2. servidor
sql_showbase
  1. RegExp
  2. servidor

Dues funcions permeten regular la codificació de caràcters en les comunicacions amb el servidor:

-  sql_set_charset, demana utilitzar el codi indicat;

-  sql_get_charset, demana si un codi amb un nom determinat es troba disponible al servidor.

Funció Arguments
sql_get_charset
  1. RegExp
  2. servidor
sql_set_charset
  1. codificació
  2. servidor

Un últim grup de funcions ofereix algunes eines de gestió de les peticions i de les taules; ens remetrem als seus homònims a la documentació dels servidors SQL. No obstant, assenyalem que sql_explain s’utilitza implícitament pel depurador d’SPIP, accessible pels botons d’administració de l’espai públic, quan se li demana el pla de càlcul d’un bucle (o de tot un esquelet).

Funció Arguments
sql_optimize
  1. petició
  2. servidor
sql_repair
  1. taula
  2. servidor
sql_explain
  1. petició
  2. servidor
sql_error
  1. petició
  2. servidor
sql_errno
sql_version

Fora de grup, la funció general sql_query, nom que hauria hagut de portar l’històric spip_query; es recomana evitar en qualsevol cas el seu ús.

Portar SPIP a altres servidors SQL

Aquesta secció està destinada a aquells que desitgen porter SPIP a d’altres servidors SQL, o perquè necessiten informacions més tècniques, sobretot en la gestió de versions de la interfície. Les funcions del fitxer ecrire/base/abstract_sql.php estudiades més amunt s’acontenten en oferir una interfície homogènia als diferents servidors, però elles mateixes no fan cap càlcul. És en el fitxer ecrire/base/connect_sql.php on hi ha el treball efectiu.

La funció essencial és spip_connect que obre la connexió al servidor SQL indicat pel seu argument (o, si aquest no hi és, el servidor principal) localitzant les connexions ja fetes. Aquesta obertura consisteix en incloure un fitxer de connexió creat durant la instal·lació d’SPIP pels scripts presents en el directori install. Un fitxer de connexió es redueix essencialment a aplicar la funció spip_connect_db als valors proporcionats durant la instal·lació.

La funció spip_connect_db rep en particular com argument el tipus de servidor SQL. Aquest tipus ha de ser el nom d’un fitxer present al directori req. Aquest fitxer es carrega i ha de definir totes les funcions d’interfícies definides a la secció anterior, més la funció req_type_dist que s’aplicarà immediatament en els mateixos arguments que spip_connect_db, excepte el tipus. És aquesta funció la que ha d’establir efectivament la connexió.

Portar SPIP a altres servidors SQL consisteix, per tant, en definir aquest joc de funcions i a situar-lo en el directori req.

El gestor de versions d’interfície es recolza en el segon argument de spip_connect que indica la versió, i agafa per defecte la versió actual. Totes les funcions de la interfície estan definides en el fitxer abstract_sql, s’anomenen sql_X i són les úniques que s’anomenen així. Es connecten totes cridant una variant de spip_connect del que el primer argument és el número de la versió de la interfície. En el cas que el fitxer abstract_sql necessités una revisió, serà reanomenat abstract_sql_N, i se li aplicarà el següent Sed (N designa el número de versió):

s/\(sql_[A-Za-z_0-9 ]*\)/\1_N/

Aplicant també aquest script a les extensions d’SPIP basades en aquesta versió, se’ls permetrà cridar les seves funcions, que es carregaran sense col·lisió de noms, al haver prefixat el Sed el nom dels antics amb el seu número de versió. Només caldrà que afegim una instrucció include que ens porti cap al fitxer abstract_sql_N. Pel que fa al codi a portar, haurem d’anomenar de nou, paral·lelament els fitxers del directori req, i escriure les noves versions.

La coexistència de diverses versions de la interfície durant l’execució d’SPIP reposa sobre l’estructura que descriu el servidor. Aquesta és, de fet, una taula que conté:

-  link, recurs que indica la connexió;

-  db, nom de la base de dades;

-  prefixe, nom del prefix de la taula;

-  ldap, nom de l’eventual fitxer que descriu el servidor LDAP.

Les altres entrades són els números de versions disponibles, i el seu valor és la taula de funcions implementant aquesta versió de la interfície.

Les altres funcions del fitxer connect_sql es refereixen essencialment a la gestió de versions i al tractament d’alguns casos particulars de declaracions de taules estàndards d’SPIP.

La substitució de spip_query en l’antic codi d’SPIP per aquest joc de funcions s’ha fet, en part, automàticament per scripts Sed. Aquests scripts formen part de les provisions de versions successives del codi, i poden, per tant, ser recuperades per ser aplicades a les extensions d’SPIP que volen migrar cap a la nova arquitectura. Heus aquí la llista:

9916 9918 9919 10394 10492 10493 10497 10501 10504 10508 10509 10519 10520 10707 10742

Alerta no obstant: aquests scripts no funcionen en qualsevol ús de spip_query. És indispensable llegir-ne cadascun d’ells per tal de verificar que s’apliquen bé al codi a migrar.

Autor merce Publié le : Mis à jour : 26/10/12

Traductions : català, English, Español, français