/CQLite

Simplified SQLite interface for C

Primary LanguageCMIT LicenseMIT

CQLite

The goal of CQLite is to provide a slightly simplified C interface for processing SQLite query results in order to reduce the amount of boiler-plate code developers need to write to interact with a database. CQLite identifies a few common operations performed on SQLite databases and provides a way to abstract away the common bits of code meeded to perform these actions which include:

  • Reading through all results of a SELECT query
  • Reading the result of a COUNT query
  • Inserting a new record into the database and retrieving its generated row id

Application developers define their queries and their models as well as how to convert a query result into an appropriate model and then call into CQLite to perform all the work to actually execute the query.

Suppose we wanted to track running race results in a SQLite database. We might define our races table to look something like:

CREATE TABLE IF NOT EXISTS races
    (
    id INTEGER PRIMARY KEY,
    distance REAL,
    name TEXT,
    city TEXT,
    state TEXT
    );

We might also have a model type defined to hold data for races

typedef struct
    {
    sqlite_int64    id;
    double          distance;
    char *          name;
    char *          city;
    char            state[3];
    } race_t;

Suppose we wanted to retrieve a list of all marathons from the database. Rather than manully writing the code to loop through and read all results, we could use CQLite and only need to define a function to read a query row result into a race model as well as a function to add a race model to a list of models.

static int race_add_to_list
    (
    sqlite3_stmt *  query,              //!< Query pointing at row result                                            
    void *          model_list,         //!< List of all models returned so far                                      
    int             next_model_list_idx //!< Index into the model list into which the query row result should be read
    )
{
race_t * race_list;

race_list = (race_t*)model_list;

return race_from_row_result( query, &race_list[next_model_list_idx] );
}    

static int race_from_row_result
    (
    sqlite3_stmt *  query,      //!< Query pointing at row result         
    void *          model_out   //!< (out) Model populated from row result
    )
{
int         success;
race_t *    race;

race = (race_t*)model_out;
race_init( race );

race->id = sqlite3_column_int64( query, 0 );
race->distance = sqlite3_column_double( query, 1 );

success = ( CQLITE_SUCCESS == cqlite_dynamic_string_read( query, 2, &race->name ) );

if( success )
    {
    success = ( CQLITE_SUCCESS == cqlite_dynamic_string_read( query, 3, &race->city ) );
    }

if( success )
    {
    success = ( CQLITE_SUCCESS == cqlite_fixed_string_read( query, 4, race->state, sizeof( race->state ) ) );
    }

return success;
}

Finally, our function to execute the actual query to get the list of marathons becomes simple and declarative.

int get_marathons
    (
    sqlite3 *   db,
    race_t **   races_out,
    int *       races_cnt_out
    )
{
cqlite_rcode_t rcode;

rcode = cqlite_select_query_execute
    (
    db,
    "SELECT * FROM races WHERE distance = 26.2;",
    "SELECT COUNT(*) FROM races WHERE distance = 26.2;",
    race_add_to_list,
    sizeof( race_t ),
    races_out,
    races_cnt_out
    );

return ( CQLITE_SUCCESS == rcode );
}  

The benefit is that the same race_add_to_list() and race_from_query() functions can be re-used to perform other queries--such as selecting only races that occur in Boston or selecting only 10Ks that occur in Boston--with a minimal amount of additional code. Adding additional fields to the race model and table then only requires updating the single function where a race model is read from a query row result.