Preparar consultas vinculadas: DatabasePrepare

En muchos casos, es necesario incluir parámetros en las consultas SQL. Dado que la consulta SQL es «originalmente» una cadena que responde a una sintaxis especial, puede formarse mediante una simple llamada a StringFormat o por concatenación, añadiendo los valores de los parámetros en los lugares adecuados. Ya hemos utilizado esta técnica en consultas para crear una tabla («CREATE TABLE %s '%s' (%s);»), pero aquí sólo una parte de los parámetros contenía datos (la lista de valores se sustituía por %s entre paréntesis), y el resto representaba una opción y un nombre de tabla. En esta sección, nos centraremos exclusivamente en la sustitución de datos en una consulta. Hacer esto de una manera SQL nativa es importante por varias razones.

En primer lugar, la consulta SQL sólo se pasa al motor SQLite como una cadena, y allí se analiza en componentes, se comprueba si es correcta y se «compila» de cierta manera (por supuesto, no se trata de un compilador MQL5). A continuación, la base de datos ejecuta la consulta compilada. Por eso entrecomillamos la palabra «originalmente».

Cuando hay que ejecutar la misma consulta con distintos parámetros (por ejemplo, al insertar muchos registros en una tabla; poco a poco nos acercamos a esta tarea), compilar y comprobar por separado la consulta para cada registro resulta bastante ineficaz. Es más correcto compilar la consulta una vez, y luego ejecutarla en bloque, simplemente sustituyendo valores diferentes.

Esta operación de compilación se denomina preparación de la consulta y la realiza la función DatabasePrepare.

Las consultas preparadas tienen un propósito más: con su ayuda, el motor SQLite devuelve los resultados de la ejecución de la consulta al código MQL5 (encontrará más información al respecto en las secciones Ejecutar consultas preparadas y Lectura por separado de los campos del registro de resultados de la consulta).

El último, pero no por ello menos importante, momento asociado a las consultas parametrizadas es que protegen su programa de posibles ataques de hackers denominados inyección SQL. En primer lugar, esto es crítico para las bases de datos de sitios públicos, donde la información introducida por los usuarios se registra en la base de datos incrustándola en consultas SQL: si en este caso se utiliza una simple sustitución de formato «%s», el usuario podrá introducir alguna cadena larga en lugar de los datos esperados con comandos SQL adicionales, y pasará a formar parte de la consulta SQL original, distorsionando su significado. Pero si la consulta SQL se compila, no puede modificarse por los datos de entrada: siempre se tratan como datos.

Aunque el programa MQL no es un programa servidor, puede almacenar la información recibida del usuario en la base de datos.

int DatabasePrepare(int database, const string sql, ...)

La función DatabasePrepare crea un manejador en la base de datos especificada para la consulta en la cadena sql. La database debe ser abierta previamente por la función DatabaseOpen.

Las ubicaciones de los parámetros de consulta se especifican en la cadena sql mediante fragmentos '?1', '?2', '?3', etc. La numeración significa el índice del parámetro utilizado en el futuro al asignarle un valor de entrada, en las funciones DatabaseBind. No es necesario que los números de la cadena sql vayan en orden y pueden repetirse si es necesario insertar el mismo parámetro en distintos lugares de la consulta.

¡Atención! La indexación en los fragmentos sustituidos '?n' comienza en 1, mientras que en las funciones DatabaseBind comienza en 0. Por ejemplo, el parámetro '?1' del cuerpo de la consulta obtendrá el valor al llamar a DatabaseBind en el índice 0, el parámetro '?2' en el índice 1, y así sucesivamente. Este desplazamiento constante de 1 se mantiene aunque haya huecos (accidentales o intencionados) en la numeración de los parámetros '?n'.

Si tiene previsto vincular todos los parámetros estrictamente en orden, puede utilizar una notación abreviada: en lugar de cada parámetro, indique simplemente el símbolo '?' sin número: en este caso, los parámetros se numeran automáticamente. Cualquier parámetro '?' sin número obtiene el número que es en 1 mayor que el máximo de los parámetros leídos a la izquierda (con números explícitos o calculados según el mismo principio, y el primero obtendrá el número 1, es decir, '?1').

Así, la solicitud

SELECT * FROM table WHERE risk > ?1 AND signal = ?2

es equivalente a:

SELECT * FROM table WHERE risk > ? AND signal = ?

Si algunos de los parámetros son constantes o la consulta se está preparando para ejecutarse una sola vez con el fin de obtener un resultado, los valores de los parámetros se pueden pasar a la función DatabasePrepare como una lista separada por comas en lugar de una elipsis (igual que en Print o Comment).

Los parámetros de consulta sólo pueden utilizarse para establecer valores en las columnas de la tabla (al escribir, modificar o filtrar condiciones). Los nombres de tablas, columnas, opciones y palabras clave SQL no pueden pasarse a través de parámetros '?'/'?n'.

La función DatabasePrepare por sí misma no satisface la consulta. El manejador que devuelve debe pasarse a las llamadas a función DatabaseRead o DatabaseReadBind. Estas funciones ejecutan la consulta y ponen el resultado a disposición para su lectura (puede ser un registro o muchos). Por supuesto, si hay marcadores de posición de parámetros ('?' o '?n') en la consulta, y los valores para ellos no se especificaron en DatabasePrepare, antes de ejecutar la consulta, es necesario vincular los parámetros y los datos utilizando las funciones DatabaseBind apropiadas.

Si no se asigna un valor a un parámetro, se sustituye por NULL durante la ejecución de la consulta.

En caso de error, la función DatabasePrepare devolverá INVALID_HANDLE.

En las secciones siguientes se presentará un ejemplo de utilización de DatabasePrepare, después de explorar otras características relacionadas con las consultas preparadas.