Hace unos días empezamos a programar utilizando SQLite para gestionar nuestra información usando SQL de toda la vida.
Bien, SQLite tiene la interfaz clásica, que vimos en el post anterior: sqlite3_open(), sqlite3_close(), sqlite3_exec()... con utilidades básicas para trabajar.
Por un lado, podemos tener más control sobre la base de datos, por ejemplo, podemos definir cómo abirmos la base de datos, tenemos códigos de error extendidos, ventajas con respecto al tratamiento interno de la memoria y algunas cosas más. La utilización es muy parecida.
Prepared statements con SQLite
Por otro lado, el enfoque con callbacks está muy bien, pero en ocasiones necesitamos el resultado de un sqlite3_exec() inmediatamente, justo debajo de esa línea, sin que tengamos que pasar contextos a una función y cambiar nuestra manera de pensar, es decir, hago una query y debajo tengo el resultado. Vamos a utilizar prepared statements con SQLite:
Aquí vemos cómo podemos lanzar la SELECT y obtener debajo el resultado, con alguna información adicional (para conocer algunas funciones más de SQLite), para ello tendremos:
- sqlite3_step(sqlite3_stmt*) : Itera entre los resultados devueltos en la consulta. Si tenemos una consulta que devuelve 4 resultados, tendremos que llamar a esta función 4 veces, cada vez podrá extraer los datos de una fila. Si todo va bien devuelve SQLITE_ROW, si no hay más filas, SQLITE_DONE y otra cosa en caso de error.
- sqlite3_data_count(sqlite3_stmt*) : Nos devuelve el número de filas. Siempre y cuando hayamos hecho un sqlite3_step() antes.
- sqlite3_column_count(sqlite3_stmt*) : Devuelve el número de columnas que hay en la fila actual
- sqlite3_column_name(sqlite3_stmt*, int index) : Devuelve el nombre de la columna número index
- sqlite3_column_decltype(sqlite3_stmt*, int) : Devuelve el tipo de dato exacto de la columna especificada. Aunque en SQLite tenemos sólo 5 tipos de dato (NULL, INT, FLOAT, BLOB, TEXT), hay datos derivados mucho más complejos, por ejemplo DATETIME para fecha y hora, que convierte directamente a TEXT.
- sqlite3_column_type(sqlite3_stmt*, int) : Devuelve el tipo de dato de SQLite para la columna dada, (SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL)
- sqlite3_column_bytes(sqlite3_stmt*, int) : Devuelve la longitud en bytes del dato de la columna (como si fuera un string)
- sqlite3_column_text(sqlite3_stmt*, int) : Devuelve el dato de la columna especificada en forma de cadena de caracteres (char*))
Escogiendo el tipo de variable correcto en C
Anteriormente hemos visto cómo los valores devueltos por SQLite se convierten a string (todos son convertibles), y podemos verlos en pantalla. Aunque a veces, seguro que no nos interesa eso, si pedimos un número a SQLite, lo queremos en forma de número, sin necesidad de transformarlo luego. Con este segundo ejemplo, vemos que dependiendo del valor devuelto por sqlite3_column_type() extraemos el valor con uno u otro tipo:
Vinculando argumentos (binding arguments)
Además de la precompilación de las consultas que vamos a utilizar para poder ejecutarlas más rápido (sobre todo si ejecutamos la consulta repetidas veces), una de las ventajas de los prepared statements es que nos permiten vincular argumentos de forma segura en una consulta. Siempre hay caracteres que no se pueden pasar en un sistema, cuando los argumentos son de tipo TEXT, si metemos una comilla entre el texto podemos cerrar la query y puede que no queramos eso. También puede ser utilizado por usuarios malintencionados para romper nuestros programas. Y bueno, también nos vale para tratar todos los argumentos que pasamos a SQLite de la misma forma, y olvidarnos de poner comillas en los textos, dejar los números sin ellas y poner bien los NULL, vamos a hacer una pequeña consulta con parámetros sobre el mismo programa de antes:
En este caso, vemos que hemos colocado interrogaciones en la query inicial, cada interrogación será sustituída por un valor que luego especificaremos con sqlite3_bind_xxxx(), en este caso, vamos a especificar rangos de fechas y un número.
sqlite3_bind_int() sólo necesita la query, la posición del parámetro que va a modificar (¡¡ojo!! El primero es el 1 y no el 0) y su valor.
sqlite3_bind_text() es un poco especial, son cadenas de caracteres, y sabemos que C es muy suyo para eso. Tenemos que pasarle:
- La query, vamos el puntero al sqlite_stmt para el que queremos asignar el parámetro
- El número de parámetro a vincular
- La cadena que queremos vincular... hasta aquí vamos bien, es similar al sqlite_bind_int()
- Tamaño de la cadena (por si tenemos un número exacto de caracteres), pero si no es así, ponemos un número negativo y cogerá hasta el primer terminador (\0) que encuentre.
- SQLITE_STATIC o SQLITE_TRANSIENT, dependiendo de si el valor de la cadena que hemos pasado cambiará con el tiempo o no. Como el valor tenemos que conservarlo, imaginemos que usamos una variable para especificar la cadena, y dicha variable será sobreescrita en breve (puede que porque sea una variable temporal, o porque los sqlite3_bind_text() están dentro de una función y las cadenas son variables locales). Como aquí vemos que la cadena no cambia usamos SQLITE_STATIC, pero en caso de duda, mejor usamos SQLITE_TRANSIENT, hacemos que SQLite se haga una copia de la cadena, y nos curamos en salud.
SQLite y C++
Es cierto que todos los ejemplos que he puesto son para lenguaje C, sin ++, aunque podemos utilizarlo de forma muy parecida. De todas formas, seguro que los programadores de C++ agradecen tener una biblioteca que utilice esta base de datos a la manera C++ (the C++ way!), y que ahorre un poco de tiempo, y escritura.
Hay varias bibliotecas para utilizarlo, pero la única que he visto más o menos en desarrollo todavía es sqlite3pp (Al menos, actualizada en 2015).
En su github hay varios ejemplos interesantes.
Foto: Travis Warren (Flickr CC-by)