Revista Tecnología

Usando SQLite en nuestros programas en C/C++ (I)

Publicado el 23 marzo 2015 por Gaspar Fernández Moreno @gaspar_fm

Usando SQLite en nuestros programas en C/C++ (I)
Foto: Eirik Stavelin (Flickr CC-by)
A menudo, nuestros programas necesitan almacenar información (temporal o no) de forma ordenada, rápida y que no nos complique la vida. Luego también necesitamos poder acceder a ella con la misma facilidad. Para eso vale SQLite. Tendremos un pequeño motor de base de datos que con sólo un par de archivos (.h y .c) más un archivo de datos lo tendremos todo listo.

Una pequeña introducción

SQLite nos proporciona una forma muy sencilla de introducir y eliminar información (si estamos familiarizados con el lenguaje SQL) sin las complicaciones de tener un motor de base de datos corriendo (MySQL, MariaDB, PostreSQL, MSSQL...). Por un lado, al no realizar conexiones, todo debería ir mucho más rápido, en bases de datos relativamente pequeñas se nota. Además, no podremos hacer llamadas de forma remota (como hemos hecho siempre, conectando con el gestor de base de datos), ni tampoco podremos montar clusters ni nada de eso (directamente, seguro que hay algún proyecto por ahí que lo permita). Las instrucciones SQL soportadas no son muchas (comparado con motores grandes) pero en muchísimos casos tendremos suficiente.

¿Quién usa SQLite?

En un primer momento, podríamos no adoptar una tecnología que no esté ampliamente aceptada (es normal, en cualquier momento es desatendida y ¡todo nuestro código a la basura!), y una tecnología que no sea libre (sobre todo para poder estudiarla, para ver que no tenga una cara oculta). Éstas son dos condiciones que pongo personalmente cuando empiezo a trabajar con alguna.
SQLite se usa en muchos programas como Skype, algunos programas de Adobe, Firefox, Chrome, Safari y muchos más. Además, tenemos extensiones para SQLite en muchos lenguajes de programación como Java, Python, PHP, y muchísimos más ( Wikipedia)

¿Dónde me bajo lo necesario?

Directamente en la web oficial: SQLite download. Yo siempre descargo sqlite-amalgamation, aquí tenemos todo el sistema sqlite en dos archivos sqlite.h y sqlite.c listos para trabajar.
Si utilizas GNU/Linux, lo más probable es que tu distribución tenga un paquete sqlite y otro sqlite-dev con el código fuente (necesitaremos los dos).

Para los ejemplos cuento con que tenemos sqlite3.h en el mismo directorio que el código de nuestro programa.

Compilar el código

Todos los códigos que pondré en el post se compilan de la misma manera con gcc. Aunque tenemos dos posibilidades. Por un lado podemos incluir sqlite en nuestro ejecutable. Nuestro programa pesará más, pero no será necesario tener la biblioteca instalada. Eso sí, para compilar, necesitamos el archivo sqlite3.c de la amalgama de código de sqlite. Esto lo haremos así:

$ gcc -o ejecutable fuente.c sqlite3.c -ldl -lpthread

(Si usas Windows, necesitarás otras bibliotecas diferentes de dl y phtread).

Por otro lado, si queremos aprovechar la biblioteca dinámica de sqlite3 instalada en nuestro sistema (ya que muchas aplicaciones lo utilizan, ahorramos cerca de 1Mb de código en cada ejecutable), lo podemos hacer de la siguiente manera:

$ gcc -o ejecutable fuente.c -lsqlite3

De esta forma, los ejecutables ocuparán muchísimo menos, pero necesitaremos tener SQLite instalado en nuestro sistema.

Hola mundo - Sacando la versión del motor

Como primer programa, antes de hacer nada con la base de datos, vamos a consultar la versión de SQLite que tenemos. Por ejemplo, si utilizamos la versión de SQLite instalada en el sistema es muy interesante, porque puede que nosotros estemos utilizando características propias de una versión de la biblioteca y debemos asegurarnos de que la versión que tiene el usuario es igual o posterior.

sqversion.c

En este caso, para visualizar la versión, nos viene bien la forma bonita, a modo de cadena de caracteres, con sus puntos y todo; pero cuando queremos realizar la comparación, nos será mucho más fácil utilizar la forma numérica.

Abriendo y cerrando la base de datos

Este pequeño programa no hace nada... bueno sólo abre y cierra la base de datos, que normalmente se hará con éxito. Si queremos probar un caso en el que falle, así rápidamente, podemos quitar los permisos de lectura:

$ chmod -r test.db

al archivo test.db para que veamos que también puede fallar el programa.

Nuestra primera consulta : Creando una tabla

Podemos usar este pequeño código para empezar creando una tabla. En este ejemplo, vamos a hacer un pequeño log de eventos de nuestro programa, en el que podemos almacenar la marca de tiempo, nivel (si es más o menos crítico), tipo (otro número), y mensaje.

De nuevo, si lo ejecutamos, nos dirá que la tabla ha sido creada, y saldrá del programa.

Insertando información en la tabla

Para ello, vamos a cambiar el SQL por lo siguiente:

Hemos insertado cuatro filas de datos, en las que rellenamos la fecha y hora, ponemos valores numéricos en level y type y un mensaje final.
Me he complicado la vida un poco para poner la fecha y hora, más que nada para que se vayan sumando algunos días y las fechas sean diferentes.

Obteniendo datos de la tabla

Ahora el tema va a ser ligeramente diferente, porque vamos a extraer información en lugar de generarla y, en muchos casos, puede ser gran cantidad de información la que estamos extrayendo. En este caso, cuando llamamos a la función sqlite3_exec(), el tercer parámetro que hasta ahora es NULL, va a ser el nombre de una función de callback encargada de recibir los datos por parte de SQLite, y ya, nosotros veremos lo que hacemos con ellos. La forma de pasar los datos, será parecida a cómo recibe los argumentos (desde la función main()) un programa en C o C++, gracias a dos variables: una que devuelve el número de argumentos recibidos (argc) y otra que recibe el contenido de éstos (argv). Además, tendremos otra variable más que recibirá los nombres de los campos. Esta función será llamada a cada fila recibida.

Podemos hacer algo como esto:

En este caso tendremos una salida completa con todos los valores de la tabla, aunque bien podemos usar WHERE, LIMIT, etc.

Tenemos un argumento curioso para sqlite3_exec(), que directamente he colocado como 0 en todas las peticiones, ese valor será una variable que podemos pasar al callback cada vez que se ejecute, y que puede tomar el valor que queramos (y también recibir, que para eso es un puntero), lo que significa que tenemos muchas más posibilidades, como poner un identificador para decidir qué hacer con la información recibida o crear otra estructura basada en listas enlazadas para almacenar todo el SELECT y poder leerlo tras ejecutar sqlite3_exec() si todo ha ido bien.

Una pequeña buena práctica

Es bueno utilizar llamadas a sqlite3_initialize() cuando vamos a empezar a utilizar la biblioteca. Y a sqlite3_shutdown() cuando terminamos de utilizarla y no vamos a hacerlo más. Es verdad que no hace realmente falta, pero podemos compilar especificando el define SQLITE_OMIT_AUTOINIT, como su nombre indica, no auto-inicializará. No ganaremos excesiva velocidad no inicializando porque sqlite3_initialize() cuando ya está el sistema inicializado no hará nada, pero son llamadas y comprobaciones que podemos hacer y si nuestra aplicación exige mucho uso de SQLite o el sistema es modesto, seguro que se agradece.


Volver a la Portada de Logo Paperblog