lundi 29 juin 2015

How to handle type errors when working with blobs in SQLite?

Is there a good way to handle type errors when working with blobs in SQLite? For example, the following code registers two functions create_vector and display_vector. Basically, create_vector stores a std::vector as a blob and display_vector converts this blob into text, so that we can see it:

/* In order to use

sqlite> .load "./blob.so" 
sqlite> select display_vector(create_vector());
[ 1.200000, 3.400000, 5.600000 ]

*/

#include <vector>
#include <string>
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1

extern "C" {
    int sqlite3_blob_init(
        sqlite3 * db,
        char ** err,
        sqlite3_api_routines const * const api
    );
}

// Cleanup handler that deletes a pointer 
template <typename T>
void simple_cleanup(void * v) {
   delete static_cast <T *> (v);
}

// Creates and returns a std::vector as a blob
static void create_vector(
    sqlite3_context *context,
    int argc,
    sqlite3_value **argv
){
    // Create a dummy vector
    auto * v = new std::vector <double> {1.2,3.4,5.6};
    sqlite3_result_blob(context,v,sizeof(v),
        simple_cleanup <std::vector <double>>);
}

// Converts a std::vector into text
static void display_vector(
    sqlite3_context *context,
    int argc,
    sqlite3_value **argv
){
    // Grab the vector.  Note, if this is not a vector, then sqlite will
    // almost certainly segfault.
    auto const * const v =static_cast <std::vector<double> const * const> (
        sqlite3_value_blob(argv[0]));

    // Assuming we have a vector, convert it into a string
    auto s = std::string("[ "); 
    for(auto const & x : *v) {
        // If we're not on the first element, add a comma
        if(s.size() !=2) s += ", ";

        // Add the number
        s += std::to_string(x);
    }
    s += " ]";

    // Return the text
    sqlite3_result_text(
        context,sqlite3_mprintf("%s",s.c_str()),s.size(),sqlite3_free);
}

// Register our blob functions
int sqlite3_blob_init(
    sqlite3 *db,
    char **err,
    sqlite3_api_routines const * const api
){
    SQLITE_EXTENSION_INIT2(api)

    // Register the create_vector function
    if( int ret = sqlite3_create_function(
        db, "create_vector", 0, SQLITE_ANY, 0, create_vector, 0, 0)
    ) {
        *err=sqlite3_mprintf("Error registering create_vector: %s",
            sqlite3_errmsg(db));
        return ret;
    }

    // Register the display_vector function
    if( int ret = sqlite3_create_function(
        db, "display_vector", 1, SQLITE_ANY, 0, display_vector, 0, 0)
    ) {
        *err=sqlite3_mprintf("Error registering display_vector: %s",
            sqlite3_errmsg(db));
        return ret;
    }

    // If we've made it this far, we should be ok
    return SQLITE_OK;
}

We can compile this with:

$ make
g++ -g -std=c++14 blob.cpp -shared -o blob.so -fPIC

Now, if we use these functions as advertised, everything works fine:

sqlite> .load "./blob.so"
sqlite> select display_vector(create_vector());
[ 1.200000, 3.400000, 5.600000 ]

However, if we try to use display_vector on a non-vector, we segfault:

sqlite> .load "./blob.so"
sqlite> select display_vector(2);
Segmentation fault

Really, the issue is that the static_cast in display_vector vector is not correct. In any case, is there a good way check the type of the blob or even guarantee that we have a blob? Is there a good way to prevent a segfault when a new extension requires an input of a certain type?

Aucun commentaire:

Enregistrer un commentaire