SQLite - Una breve intro

Hace varios años conocí SQLite, una excelente base de datos escrita por D. Ricard Hipp, quien también escribió otros proyectos como CVSTrac (sirvió de inspiración para el famoso The Trac), Fossil-SCM el scm que adoro, entre otros proyectos. La idea de escribir sobre SQLite mientras leía el artículo sobre SQLite en el IPhone

Lo que me gusta de SQLite es que su API es muy sencilla y que cuanta con cosas para los perezosos (”sqlite3_execute”) y con cosas para personas que piensan que lo primordial es el performance. Un código vale más que mil palabras, miren un pequeño ejemplo de SQLite…

/**
 * Coded by crodas,
 *
 * The author disclaims the copyright of this code.
 */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>

int table_exists(sqlite3 * dbh, const char * table) {
    sqlite3_stmt* pStmtPrep;
    int rc,exists;
    /* preparamos el SQL a ejecutar, muy util para evitar SQL injections */
    rc=sqlite3_prepare(dbh,"select * from sqlite_master where type='table' and name=?",-1 /* null terminated str*/, &pStmtPrep,NULL);
    if (rc!=SQLITE_OK) {
        printf("This is an SQL bug %s",sqlite3_errmsg(dbh));
        exit(-1);
    }

    /* asignamos a la primera variabla (?) el valor de table*/
    sqlite3_bind_text(pStmtPrep,1,table,-1,0);

    /* ejecutamos */
    rc = sqlite3_step(pStmtPrep);
    switch(rc) {
        case SQLITE_ERROR:
        case SQLITE_BUSY:
            printf("Database locked or unknown error");
            fflush(stdout);
            exit(-1);
            break;
        case SQLITE_DONE:
            exists = -1; /* no existe */
            break;
        case SQLITE_ROW:
            exists = 1; /* existe */

    }

    /* a liberar memoria! */
    sqlite3_finalize(pStmtPrep);
    return exists;
}

int main() {
    sqlite3 *  dbh;
    int rc,i;
    sqlite3_stmt* pStmtPrep;

    if (sqlite3_open_v2("test.db",&dbh,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,0)!=SQLITE_OK) {
        printf("Can't  open db, please check permissions on this folder. %s\n",sqlite3_errmsg(dbh));
        return -1;
    }

    /**
     *  Hice una pequenha funcion que ve si existe.
     *  lo bueno de la funcion es que muestra el primer ejemplo
     *  el SQLPrepare, y muestra como saber si la consulta
     *  tiene resultado o no
     */
    if (table_exists(dbh,"testing")==-1) {
        printf("Table doesn't exists,execute\n");
        /* tabla sencilla, utilizo exec porque no espero resultado */
        sqlite3_exec(dbh,"create table testing (id int, text varchar(250))",0,0,0);
        /* sql preparada para el insert */

        int data_id[] = {5,5,5,8,0}; /* el ID no es PK, en la vida real seria PK*/
        const char*  data_data[] = {"Some text","Another text","foobar","This is the 8 text",0};
        rc=sqlite3_prepare(dbh,"insert into testing values(?,?)",-1, &pStmtPrep,NULL);
        /* no controlo nada porque ya mostre en la funcion anterior como controlar errores */
        for(i=0;;i++) {
            if (data_id[i] == 0) break;
            /* cambiamos ? ? por sus valores */
            sqlite3_bind_int(pStmtPrep,1,data_id[i]);
            sqlite3_bind_text(pStmtPrep,2,data_data[i],-1,0);

            /* ejecutamos */
            rc=sqlite3_step(pStmtPrep);
            /* se tendria que controlar la salida*/

            /* ya que no hay ciclos, para cambiar los parametros */
            /* debemos reinicar al Statement */
            sqlite3_reset(pStmtPrep);
        }

        sqlite3_finalize(pStmtPrep);
    }

    /* ahora queremos los datos con id 5,6,7,8 */
    int data[] = {5,6,7,8,0};
    char * text;
    rc=sqlite3_prepare(dbh,"select * from testing where id=?",-1 /* null terminated str*/, &pStmtPrep,NULL);
    /* no controlo nada porque ya mostre en la funcion anterior como controlar errores */
    for(i=0;;i++) {
        if (data[i] == 0) break;
        /* asignar valor a ? */
        sqlite3_bind_int(pStmtPrep,1,data[i]);
        /* mientas haya resultado */
        while (sqlite3_step(pStmtPrep)==SQLITE_ROW) {
            text = sqlite3_column_text(pStmtPrep,1); /* id = 0, text=1 */
            printf("%d: %s\n",data[i],text);
        }

        /* reinicar el stmt */
        sqlite3_reset(pStmtPrep);
    }

    /* */
    sqlite3_finalize(pStmtPrep);

    sqlite3_close(dbh);
}

Lo que me gusta es que tenemos a toda una base de datos relacional en nuestro programa, y si de rendimiento se trata se puede hacer “prepare” de todas las consultas SQL al principio del programa y luego ir ejecutando, no es cosa complicada,  se ahoraría un montón de tiempo y procesador.

Otra cosa que me gusta mucho es que fácilmente extendible, una vez necesitaba una función para comprimir y descomprimir (la función compress y decompress del MySQL), envíe mi consulta en la lista de SQLite, y Dr. Hipp me respondío lo siguiente:

/*
** SQL function to compress content into a blob using libz
*/
static void compressFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  int nIn, nOut;
  long int nOut2;
  const unsigned char *inBuf;
  unsigned char *outBuf;
  assert( argc==1 );
  nIn = sqlite3_value_bytes(argv[0]);
  inBuf = sqlite3_value_blob(argv[0]);
  nOut = 13 + nIn + (nIn+999)/1000;
  outBuf = malloc( nOut+4 );
  outBuf[0] = nIn>>24 & 0xff;
  outBuf[1] = nIn>>16 & 0xff;
  outBuf[2] = nIn>>8 & 0xff;
  outBuf[3] = nIn & 0xff;
  nOut2 = (long int)nOut;
  compress(&outBuf[4], &nOut2, inBuf, nIn);
  sqlite3_result_blob(context, outBuf, nOut2+4, free);
}

/*
** An SQL function to decompress.
*/
static void uncompressFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  unsigned int nIn, nOut, rc;
  const unsigned char *inBuf;
  unsigned char *outBuf;
  long int nOut2;

  assert( argc==1 );
  nIn = sqlite3_value_bytes(argv[0]);
  if( nIn<=4 ){
    return;
  }
  inBuf = sqlite3_value_blob(argv[0]);
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  outBuf = malloc( nOut );
  nOut2 = (long int)nOut;
  rc = uncompress(outBuf, &nOut2, &inBuf[4], nIn);
  if( rc!=Z_OK ){
    free(outBuf);
  }else{
    sqlite3_result_blob(context, outBuf, nOut2, free);
  }
}

/* Make the functions above accessible to SQLite as follows:
*/
  sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
     compressFunc, 0, 0);
  sqlite3_create_function(db, "uncompress", 1, SQLITE_UTF8, 0,
     uncompressFunc, 0, 0);

Como vieron no es cosa de otro mundo, muy sencillo, y ya extendimos SQLite!. Ahora que estaré de vacaciones empiezaré a jugar con el SQlite VFS, veremos que sale…

4 Comments

  1. Pablito says:

    Lo q siempre me gusto de sqlite es para sistemas 24/7.
    Esos sistemas q tienen q seguir funcionando no importa q pase, como una 4×4.
    Tonces usas un SQLite local como “cache” contra un DB server central y si algo pasa, el programa ni se entera pq sigue trabajando localmente. No importa q pase con el DB server.
    Obviamente no vas a bajarle 4Gb de la DB central a un sqlite, pero si ciertas cositas, es un bicho muy curioso la verdad :)

  2. César Rodas says:

    @Pablito yo probré con grandes datos y funciona muy muy bien… lo que no me gusta es que solo un insert bloquea toda la DB, pero no hay otra forma, despues es excelente.

  3. hola como andan muchachos..
    Yo sabia del unlock , que te permite desbloquear la base de datos para realizar concurrencia.. Aunque advierten su uso…
    Mira esto http://www.mail-archive.com/sqlite-users@sqlite.org/msg25079.html

    Un Abrazo Grande Gerard

Leave a Reply