La interfaz de SPIP con SQL

Las funciones que se necesitan y lo que hay que saber para extender SPIP en su utilización con SQL.

Recordemos para empezar que el Structured Query Language se ha estandarizado muy poco a poco, y cada implementación cubre las lagunas de la especificación con sus propias soluciones. Las primeras versiones de SPIP utilizaban una única implementación de SQL, lo que permitía centralizar la utilización mediante una única función (spip_query) cuyo único argumento era la consulta SQL.

Al portar SPIP a distintas implementaciones de SQL, ha sido necesario renunciar a ese modelo; esta función sigue disponible por necesidades de compatibilidad, pero su uso debe ser evitado en lo sucesivo. Es preferible usar el conjunto de funciones siguiente, que tiene además la ventaja de neutralizar la mayor parte de las técnicas de ataque por inyección de código.

La primera vez que SPIP se portó sobre un servidor distinto de MySQL3 fue para el servidor PostGreSQL versión 8.2.

Inmediatamente después se portó para SQLite 2 y 3.

El proceso de portar SPIP a distintas bases de datos se fundamenta en reemplazar la función spip_query por tantas funciones como instrucciones SQL (select, update, etc), cada una de ellas con tantos argumentos como cláusulas admita la instrucción (where, limit, etc), más un argumento opcional que especifica la base SQL. Los operandos SQL (en particular las fechas y los números en hexadecimal) permanecen escritos en sintaxis MySQL, y las funciones de interfaz se encargan de reescribirlos en caso de necesidad.

Se proponen algunas funciones suplementarias: algunas indispensables (acceso a las líneas sucesivas de un resultado de select y algunas como abreviaciones (descuento, acceso a una línea que se sabe que es única, etc). Se proporcionan también funciones de información general: alfabetos utilizados, presencia de un LDAP, etc.

El proceso de portar SPIP a SQLite no necesitó revisar el juego de funciones definido en un principio para portar a PostGres, lo cual demuestra la perdurabilidad de la interfaz así definida. No obstante, ya que estas utilidades están destinadas a evolucionar antes o después, también esta nueva interfaz, propuesta con SPIP 2.0, integra desde este momento un gestor de sus propias versiones, lo que le permite utilizar una conexión SQL con varias versiones de la interfaz al mismo tiempo. De este modo, las extensiones de SPIP que respeten esta nueva interfaz se aseguran su funcionamiento con las versiones posteriores de SPIP.

Destacamos para terminar que la elección de desarrollar esta arquitectura fue impuesta por la inexistencia de una biblioteca de nivel de abstracción equivalente que estuviera disponible tan libremente como SPIP; las extensiones de PHP ofrecidas actualmente se contentan con uniformizar las llamadas a las funciones de base, lo cual es solamente la punta del iceberg al que se enfrenta el desarrollador de aplicaciones sobre bases de datos heterogéneas.

Este artículo se compone de tres partes. Las dos primeras están destinadas a los desarrolladores que quieren escribir extensiones para SPIP y presentan en primer lugar la arquitectura general, y a continuación las funciones disponibles.

La tercera parte detalla la implementación y está destinada a los contribuidores y contribuidoras de SPIP que quieran portarlo a otras implementaciones de SQL o mejorar las existentes. En todo el artículo, se hablará de servidor SQL para denominar una implementación de SQL usada por SPIP, aunque ciertas implementaciones no sean de los servidores propiamente dichos.

Arquitectura general

En un primer momento, se puede considerar que la interfaz de SPIP con las implementaciones de SQL se reduce únicamente a la función siguiente, definida en el archivo ecrire/base/abstract_sql.php:

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

Esta función empieza, si no se ha hecho antes, por conectarse al servidor especificado en el segundo argumento. Este argumento se omite a menudo, y designa la implementación SQL elegida durante la instalación y guardada por SPIP en un archivo de conexión (ver Les bases de données en SPIP). Si no es así, el argumento debe indicar explícitamente el nombre del archivo de conexión a utilizar, omitiendo la extensión .php. El resultado devuelto es otra función, que realiza el tipo de acción solicitado por la cadena pasada como primer argumento (por ejemplo select o fetch) sobre la base de datos SQL indicada por el fichero de conexión.

El tercer argumento indica lo que se debe hacer cuando una función no puede ser devuelta. Si está ausente o es igual a false, se lanzará un error fatal. En otro caso, se distinguen dos situaciones. Si la conexión indicada es desconocida o inoperante, se devolverá el valor false.Si no es así, se devolverá una estructura de datos que describe la conexión (se describirá esta estructura en la última parte de este artículo), lo que permite por un lado verificar si una función existe sin arriesgarse a tener un error fatal, y por otro lado obtener diversas informaciones antes de utilizarla.

Esta visión minimalista de la interfaz permite hacer de todo, pero con una sintaxis bastante opaca. Si por ejemplo $db es el nombre de una base de datos en el servidor principal, se la seleccionará mediante

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

Para clarificar la escritura, existe un juego de funciones que encadenan estas dos instrucciones para los casos más frecuentes. Por ejemplo, existe

sql_selectdb($nom, $serveur='') 

Lo cual permite reescribir la operación anterior de forma más sencilla:

sql_selectdb($db)

En general, la interfaz de SPIP con los servidores SQL es un juego de funciones cuyo prefijo es sql_f y cuyo último argumento, opcional, es el nombre del servidor. Estas funciones llaman a sql_serveur para obtener una función f que aplican sobre sus argumentos, incluido el nombre del servidor.

Todas las funciones cuyo nombre se construye de esta manera están reservadas para la interfaz de SPIP con servidores SQL.

Desde un punto de vista orientado a objetos, este juego de funciones representa los métodos del objeto sql, pero se escribe sql_f en lugar de sql->f, y no es necesario instanciar una clase. La presencia del nombre del servidor en todas las llamadas permite simular las operaciones que implican al objeto actual self).

Este juego de funciones hace por tanto innecesario utilizar sql_serveur la mayor parte del tiempo. Destacamos también la función (presente en SPIP desde hace tiempo):

spip_connect($serveur='')

la cual simplemente abre la conexión al servidor, y es por tanto equivalente a

sql_serveur('', $serveur, true)

y que devuelve false si el servidor no está disponible, y la estructura de datos que describe las posibilidades del servidor en otro caso.

Funciones disponibles

Las funciones de la interfaz SQL se pueden clasificar en varios grupos, para los cuales se dará cada vez una tabla que muestra sus argumentos. Para los ejemplos, se puede acudir al código fuente de SPIP.

Un primer grupo de funciones se refieren a la lectura de las tablas SQL. Las funciones imprescindibles son:

-  sql_select cuyos argumentos son las cláusulas SQL habituales de esta instrucción, y cuyo resultado es un recurso Select.

-  sql_fetch, utilizada casi siempre dentro de un bucle, que permite recuperar las líneas sucesivas de un recurso Selec, su resultado es una tabla indexada por el nombre de los campos.

-  sql_free que indica al servidor que puede liberar un recurso.

Otras funciones ofrecen composiciones frecuentes de estas operaciones:

-  sql_fetsel con los mismos argumentos que sql_select, la cual se aplica sobre los argumentos, seguida de sql_fetch sobre el recurso devuelto, es práctica para las consultas cuyo resultado está formado por una única línea.

-  sql_getfetsel con los mismos argumentos que sql_select, la cual se aplica aplica sobre los argumentos, seguida de sql_fetch sobre el recurso devuelto, y finalmente extrae de la tabla devuelta el campo cuyo nombre se indica como primer argumento (la lista de campos, por tanto, no contiene más que uno); es práctica para las consultas cuyo resultado está formado por una única línea de un solo campo.

-  sql_allfetsel con los mismos argumentos que sql_select, la cual se aplica aplica sobre los argumentos, seguida de sql_fetch sobre el recurso devuelto, mientras éste devuelva una tabla no vacía, sql_allfetsel devuelve para finalizar la tabla de todas las tablas devueltas por sql_fetch (cuidado con saturar la memoria con esta función).

-  sql_countsel con los mismos argumentos que sql_select excepto el primero, el cual se aplica sobre COUNT(*) y sus otros argumentos y retorna el número calculado; es práctica para conocer el número de líneas que devolverá la consulta Select descrita de esta manera.

-  sql_count la cual devuelve el número de líneas de un recurso Select, como si se hubiera añadido COUNT(*) en la consulta.

-  sql_get_select utiliza los mismos argumentos que sql_select, pero devuelve el código SQL de la consulta, sin ejecutarlo. Esta función puede ser útil para las necesidades de ciertos plugins o para crear vistas SQL fácilmente.

Las cuatro primeras funciones llaman para finalizar a sql_free, y por tanto es preferible usarlas siempre que sea posible antes que el trío select-fetch-free cuyo último elemento se pierde fácilmente.

La siguiente tabla especifica el contenido y el orden de los argumentos esperados por estas funciones.

Función Argumentos
sql_select
  1. lista de campos: cadena o array
  2. lista de tablas: cadena o array
  3. cláusula Where: cadena o array
  4. cláusula Groupby: cadena con coma de separación o array
  5. cláusula Orderby: cadena con coma de separación o array
  6. cláusula Limit: uno o dos enteros separados por comas
  7. cláusula Having: cadena o array
  8. servidor
sql_fetch
  1. recurso
  2. servidor
sql_free
  1. recurso
  2. servidor
sql_count
  1. recurso
  2. servidor
sql_countsel
  1. lista de tablas: cadena o array
  2. clause Where: cadena o array
  3. clause Groupby: cadena o array
  4. cláusula Orderby: cadena con coma de separación o array
  5. cláusula Limit: uno o dos enteros separados por comas
  6. cláusula Having: cadena o array
  7. servidor
sql_fetsel
  1. lista de campos: cadena o array
  2. lista de tablas: cadena o array
  3. cláusula Where: cadena o array
  4. cláusula Groupby: cadena con coma de separación o array
  5. cláusula Orderby: cadena con coma de separación o array
  6. cláusula Limit: uno o dos enteros separados por comas
  7. cláusula Having: cadena o array
  8. servidor
sql_allfetsel
  1. lista de campos: cadena o array
  2. lista de tablas: cadena o array
  3. cláusula Where: cadena o array
  4. cláusula Groupby: cadena con coma de separación o array
  5. cláusula Orderby: cadena con coma de separación o array
  6. cláusula Limit: uno o dos enteros separados por comas
  7. cláusula Having: cadena o array
  8. servidor
sql_getfetsel
  1. nombre de un campo: cadena
  2. lista de tablas: cadena o array
  3. cláusula Where: cadena o array
  4. cláusula Groupby: cadena o array
  5. cláusula Orderby: cadena con coma de separación o array
  6. cláusula Limit: uno o dos enteros separados por comas
  7. cláusula Having: cadena o array
  8. servidor
sql_get_select
  1. lista de campos: cadena o array
  2. lista de tablas: cadena o array
  3. cláusula Where: cadena o array
  4. cláusula Groupby: cadena con coma de separación o array
  5. cláusula Orderby: cadena con coma de separación o array
  6. cláusula Limit: uno o dos enteros separados por comas
  7. cláusula Having: cadena o array
  8. servidor

En las funciones anteriores, si la cláusula Select es proporcionada en forma de array, sus elementos se concatenarán, separados por comas. En caso de usar una array para las cláusulas Where y Having, los elementos deben ser cadenas (las subarrays en notación prefija también se tienen en cuenta, pero se reservan para el compilador). Estas cadenas serán unidas en un grupo mayor (es decir, serán concatenadas con AND como separador).

La cláusula From es una cadena (el caso de la array se reserva para el compilador de SPIP). Atención: Si es necesario referenciar las tablas en las otras cláusulas, es necesario definir alias en este parámetro y utilizarlos sistemáticamente. Por tanto, se escribirá:

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

o bien

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

mientras que lo siguiente no se entenderá:

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

Un segundo grupo de funciones está compuesto por las que modifican el contenido de las tablas. Estas funciones son difíciles de definir, ya que la sintaxis de los valores a introducir en las tablas cambia de un servidor SQL a otro (especialmente las fechas). Por esta razón, estas funciones deben disponer de la descripción de la tabla a modificar, a fin de conocer el tipo de los valores esperados por el servidor SQL. SPIP obtiene automáticamente esta información (proporcionada en el momento de creación de la tabla) pero es posible proporcionar una descripción arbitraria (penúltimo argumento de estas funciones, opcional y por otra parte útil en contados casos).

SPIP proporciona por tanto una función de inserción, sql_insertq, y una función de actualización, sql_updateq, las cuales toman un array campo=>valor y se ocupan de citar los valores en función del tipo (con la función sql_quote especificada más abajo). También está disponible sql_insertq_multi que permite realizar inserciones de varias entradas mediante un array de array campo=>valor. Para las actualizaciones donde los nuevos valores dependan de los antiguos (como en cpt=cpt+1), se utiliza sql_update donde los valores serán tomados literalmente, pero hará falta impedir cuidadosamente las posibilidades de ataque por inyección de código. También existe sql_replace, función que efectúa una actualización sobre una línea correspondiente a una clave primaria, o bien inserta los valores si esta línea no existe, además de una función sql_replace_multi para las actualizaciones o inserciones múltiples. Finalmente, sql_delete borra las líneas de una tabla correspondientes a una cláusula Where.

Función Argumentos
sql_updateq
  1. table
  2. array campo=>valor a citar
  3. cláusula Where
  4. descripción
  5. servidor
sql_update
  1. tabla
  2. array campo=>valor
  3. cláusula Where
  4. descripción
  5. servidor
sql_insertq
  1. tabla
  2. array campo=>valor a citar
  3. descripción
  4. servidor
sql_insertq_multi
  1. tabla
  2. array de array campo=>valor a citar
  3. descripción
  4. servidor
sql_replace
  1. tabla
  2. array campo=>valor a citar
  3. descripción
  4. servidor
sql_replace_multi
  1. tabla
  2. array de array campo=>valor a citar
  3. descripción
  4. servidor
sql_delete
  1. $table
  2. $where
  3. servidor

Un grupo un poco aparte está formado por funciones que se ocupan específicamente de los operandos. Estas funciones no se conectan al servidor, pero devuelven cadenas que dependen de éste:

-  sql_quote toma una cadena o un número, y devuelve un número si el argumento era un número o una cadena que representaba a un entero. Si no, devuelve la cadena inicial rodeada de apóstrofes, con los apóstrofes protegidos según la sintaxis propia del servidor (un \ delante para algunos, un segundo apóstrofe para otros).

-  sql_hex toma una cadena de cifras hexadecimales y devuelve su representación en el servidor SQL dado.

-  sql_in construye una llamada al operando IN, procesando los eventuales valores hexadecimales que aparezcan.

-  sql_test_int es un predicado que retorna Verdadero si el tipo SQL proporcionado designa un entero.

-  sql_test_date es un predicado que retorna Verdadero si el tipo SQL proporcionado designa una fecha.

-  sql_multi aplica una expresión SQL sobre un campo que contiene un bloque múltiple (ver Hacer un sitio multilingüe) para tomar la parte correspondiente al idioma indicado; el interés de efectuar esta operación a nivel de SQL radica esencialmente en poder pedir simultáneamente una selección sobre esta columna.

Función Argumentos
sql_quote
  1. valor
  2. servidor
sql_hex
  1. valor
  2. servidor
sql_in
  1. columna
  2. valores
  3. verdadero si se desea una negación
  4. servidor
sql_multi
  1. columna
  2. idioma
  3. servidor
sql_test_date
  1. tipo
  2. servidor
sql_test_int
  1. tipo
  2. servidor

Un grupo importante está formado por las funciones que manipulan las declaraciones de bases de datos y de tablas. Por motivos históricos, esta primera versión de la interfaz toma casi literalmente la sintaxis de MySQL3 y seguramente tendrá que ser revisada, en particular para hacer que aparezca la declaración de las uniones. Las funciones sql_create, sql_alter, sql_showtable y sql_drop_table permiten crear, modificar, visualizar y suprimir una tabla. Las funciones sql_create_view, sql_drop_view permiten crear o suprimir una visulización. Las funciones sql_listdbs, sql_showbase y sql_selectdb permiten ver las bases accesibles, visualizar su contenido y seleccionar una. Se ha de tener en cuenta que no todos los proveedores de alojamiento web autorizan estas acciones; SPIP intentará averiguarlo, guardando sus intentos en el fichero spip.log.

Función Argumentos
sql_create
  1. nombre de la tabla
  2. array nombre de columna => tipo SQL y valor por defecto
  3. array nombre de índice => columna(s)
  4. verdadero si hay autoincremento
  5. verdadero si es temporal
  6. servidor
sql_alter
  1. consulta MYSQL Alter
  2. servidor
sql_showtable
  1. RegExp
  2. verdadero si la tabla ha sido declarada por SPIP
  3. servidor
sql_drop_table
  1. nombre de la tabla
  2. verdadero si es necesario insertar la condición existe
  3. servidor
sql_create_view
  1. nombre de la vista
  2. consulta de selección de campos (creada por ejemplo con sql_get_select)
  3. servidor
sql_drop_view
  1. nombre de la vista
  2. verdadero si es necesario insertar la condición existe
  3. servidor
sql_listdbs
sql_selectdb
  1. nombre de la base de datos
  2. servidor
sql_showbase
  1. RegExp
  2. servidor

Dos funciones permiten regular la codificación de los caracteres en las comunicaciones con el servidor:

-  sql_set_charset, solicita utilizar la codificación indicada.

-  sql_get_charset, solicita si una codificación dada está disponible en el servidor.

Función Argumentos
sql_get_charset
  1. RegExp
  2. servidor
sql_set_charset
  1. codificación
  2. servidor

Un último grupo de funciones ofrece algunas herramientas de gestión de las consultas y de las tablas; se referirán a sus homónimas en la documentación de los servidores SQL. Señalamos, no obstante, que sql_explain es utilizado implícitamente por el depurador de SPIP, accesible por los botones de administración del espacio público, cuando se solicita el plan de cálculo de un bucle (o de todo un esqueleto).

Función Argumentos
sql_optimize
  1. consulta
  2. servidor
sql_repair
  1. tabla
  2. servidor
sql_explain
  1. consulta
  2. servidor
sql_error
  1. consulta
  2. servidor
sql_errno
sql_version

Fuera de grupo, la función general sql_query, nombre que habría tenido que llevar la histórica spip_query; se recomienda evitar su utilización en cualquier caso.

Portar SPIP a otros servidores SQL

Esta sección está destinada a aquellos que deseen portar SPIP a otros servidores SQL, o necesiten información más técnica, especialmente sobre la gestión de las versiones de la interfaz. Las funciones del archivo ecrire/base/abstract_sql.php estudiadas anteriormente se conforman con ofrecer una interfaz homogénea con los distintos servidores, pero no realizan ningún cálculo ellas mismas. Es en el archivo ecrire/base/connect_sql.php donde se localiza el trabajo efectivo.

La función esencial es spip_connect que abre la conexión al servidor SQL indicado por su argumento (o, si se omite, el servidor principal), teniendo en cuenta las conexiones ya realizadas. Esta abertura consiste en incluir un archivo de conexión creado durante la instalación de SPIP por los scripts presentes en la carpeta install. Un fichero de conexión se puede resumir esencialmente en aplicar la función spip_connect_db a los valores proporcionados durante la instalación.

La función spip_connect_db recibe en particular como argumento el tipo del servidor SQL. Este tipo debe ser el nombre de un archivo presente en la carpeta req. Este archivo es cargado y debe contener todas las funciones de interfaz definidas en la sección anterior, más la función req_tipo_dist que será inmediatamente aplicada sobre los mismos argumentos que spip_connect_db, excepto el tipo. Es esta función la que debe establecer la conexión de forma efectiva.

Portar SPIP a otros servidores SQL consiste por tanto en definir ese juego de funciones y colocarlo en la carpeta req.

El gestor de versiones de la interfaz se localiza en el segundo argumento de spip_connect que indica la versión, y toma la versión actual por defecto. Todas las funciones de la interfaz se definen en el archivo abstract_sql, se llaman sql_X y son las únicas que se llaman así. Todas ellas se conectan llamando a una variante de spip_connect cuyo primer argumento es el número de versión de la interfaz. En caso que el archivo abstract_sql necesite una revisión, se renombrará como abstract_sql_N, y el script Sed siguiente le será aplicado (N se refiere al número de versión):

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

Al aplicar igualmente este script a las extensiones de SPIP basadas en esta versión, se les permitirá llamar a sus funciones, que serán cargadas sin colisión de nombres, ya que el script de Sed habrá añadido el número de versión como prefijo de los nombres de las antiguas. Únicamente será necesario añadir de nuevo una instrucción include en el fichero abstract_sql_N. En el lado del código a portar, será necesario renombrar igualmente los archivos de la carpeta req, y escribir las nuevas versiones.

La coexistencia de varias versiones de la interfaz durante la ejecución de SPIP descansa en la estructura que describe el servidor. Esta estructura es una array que contiene:

-  link, recurso que indica la conexión;

-  db, nombre de la base de datos;

-  prefixe, nombre del prefijo de tabla;

-  ldap, nombre del eventual fichero que describe el servidor LDAP.

Las otras entradas son los números de versiones disponibles, y su valor es un array con las funciones que implementan esta versión de la interfaz.

Las otras funciones del fichero connect_sql se refieren esencialmente a la gestión de las versiones y el tratamiento de algunos casos particulares de declaraciones de tablas estándar de SPIP.

La sustitucion de spip_query en el anterior código de SPIP por este juego de funciones fue realizada en parte automáticamente por scripts Sed. Estos scripts forman parte de las versiones sucesivas del código, y pueden por tanto ser revisados para aplicarlos a extensiones de SPIP que se quieran migrar a la nueva arquitectura. He aquí la lista:

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

Atención no obstante: estos scripts no funcionan para cualquier utilización de spip_query. Es indispensable leer cada uno de ellos para verificar que se aplican bien al código a migrar.

Autor o autora David Sánchez Crespillo Publicado el: Actualizado: 26/10/12

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