Class Amalgalite::Requires::Bootstrap
In: ext/amalgalite3_requires_bootstrap.c
Parent: Object

Bootstrapping module to help require when Amalgalite::Requires is not availble in files.

Methods

lift  

Classes and Modules

Class Amalgalite::Requires::Bootstrap::Error

Constants

DEFAULT_DB = rb_str_new2( "lib.db" )   constants for default db, table, column, rowid, contents
DEFAULT_TABLE = rb_str_new2( "bootstrap" )
DEFAULT_ROWID_COLUMN = rb_str_new2( "id" )
DEFAULT_FILENAME_COLUMN = rb_str_new2( "filename" )
DEFAULT_CONTENTS_COLUMN = rb_str_new2( "contents" )

Public Class methods

WARNING WARNING WARNING WARNING WARNING WARNING WARNING

This is a boostrap mechanism to eval all the code in a particular column in a specially formatted table in an sqlite database. It should only be used for a specific purpose, mainly loading the Amalgalite ruby code directly from an sqlite table.

Amalgalite::Requires adds in the ability to require code that is in an sqlite database. Since Amalgalite::Requires is itself ruby code, if Amalgalite::Requires was in an sqlite database, it could not require itself. Therefore this method is made available. It is a pure C extension method that directly calls the sqlite3 C functions directly and uses the ruby C api to eval the data in the table.

This method attaches to an sqlite3 database (filename) and then does:

    SELECT filename_column_name, content_column_name
      FROM table_name
  ORDER BY rowid_column_name

For each row returned it does an eval on the code in the content_column_name and then updates _$"_ directly with the value from filename_column_name.

The database to be opened by lift must be an sqlite3 UTF-8 database.

[Source]

/**
 * call-seq:
 *   Amalgalite::Requires::Bootstrap.lift( 'dbfile' => "lib.db", 'table_name' => "bootload", 'rowid_column' => "id", 'filename_column' => "filename",  'content_column' => "contents" )
 *
 * *WARNING* *WARNING* *WARNING* *WARNING* *WARNING* *WARNING* *WARNING*
 *
 * This is a boostrap mechanism to eval all the code in a particular column in a
 * specially formatted table in an sqlite database.  It should only be used for
 * a specific purpose, mainly loading the Amalgalite ruby code directly from an
 * sqlite table.  
 *
 * Amalgalite::Requires adds in the ability to _require_ code that is in an
 * sqlite database.  Since Amalgalite::Requires is itself ruby code, if
 * Amalgalite::Requires was in an sqlite database, it could not _require_
 * itself.  Therefore this method is made available.  It is a pure C extension
 * method that directly calls the sqlite3 C functions directly and uses the ruby
 * C api to eval the data in the table.
 *
 * This method attaches to an sqlite3 database (filename) and then does:
 *
 *     SELECT filename_column_name, content_column_name 
 *       FROM table_name
 *   ORDER BY rowid_column_name
 *
 * For each row returned it does an _eval_ on the code in the
 * *content_column_name* and then updates _$"_ directly with the value from
 * *filename_column_name*.
 *
 * The database to be opened by _lift_ *must* be an sqlite3 UTF-8 database.
 *
 */
VALUE am_bootstrap_lift( VALUE self, VALUE args )
{
    sqlite3*        db = NULL;
    sqlite3_stmt* stmt = NULL;
    int             rc;
    int  last_row_good; 
    char*    raise_msg = NULL;

    VALUE     am_db_c  = rb_const_get( cARB, rb_intern("DEFAULT_DB") );
    VALUE    am_tbl_c  = rb_const_get( cARB, rb_intern("DEFAULT_TABLE") );
    VALUE     am_pk_c  = rb_const_get( cARB, rb_intern("DEFAULT_ROWID_COLUMN") );
    VALUE  am_fname_c  = rb_const_get( cARB, rb_intern("DEFAULT_FILENAME_COLUMN") );
    VALUE am_content_c = rb_const_get( cARB, rb_intern("DEFAULT_CONTENTS_COLUMN") );

    char*     dbfile = NULL;
    char*    tbl_name = NULL;
    char*      pk_col = NULL;
    char*   fname_col = NULL;
    char* content_col = NULL;

    char*            sql = NULL;
    const char* sql_tail = NULL;
    int        sql_bytes = 0;
    
    const unsigned char* result_text = NULL;
    int                result_length = 0;

    VALUE     require_name = Qnil;  /* ruby string of the file name for use in eval */
    VALUE   eval_this_code = Qnil;  /* ruby string of the code to eval from the db  */
    VALUE toplevel_binding = rb_const_get( rb_cObject, rb_intern("TOPLEVEL_BINDING") ) ;
    VALUE    sqlite_errmsg = Qnil;
    VALUE              tmp = Qnil;

    ID             eval_id = rb_intern("eval");


    if (   Qnil == args  ) {
        args = rb_hash_new();
    } else {
        args = rb_ary_shift( args );
    }

    Check_Type( args, T_HASH );
    
    /* get the arguments */
    dbfile      = ( Qnil == (tmp = rb_hash_aref( args, rb_str_new2( "dbfile"          ) ) ) ) ? StringValuePtr( am_db_c )      : StringValuePtr( tmp );
    tbl_name    = ( Qnil == (tmp = rb_hash_aref( args, rb_str_new2( "table_name"      ) ) ) ) ? StringValuePtr( am_tbl_c )     : StringValuePtr( tmp );
    pk_col      = ( Qnil == (tmp = rb_hash_aref( args, rb_str_new2( "rowid_column"    ) ) ) ) ? StringValuePtr( am_pk_c )      : StringValuePtr( tmp );
    fname_col   = ( Qnil == (tmp = rb_hash_aref( args, rb_str_new2( "filename_column" ) ) ) ) ? StringValuePtr( am_fname_c )   : StringValuePtr( tmp );
    content_col = ( Qnil == (tmp = rb_hash_aref( args, rb_str_new2( "contents_column" ) ) ) ) ? StringValuePtr( am_content_c ) : StringValuePtr( tmp );


    /* open the database */
    rc = sqlite3_open_v2( dbfile , &db, SQLITE_OPEN_READONLY, NULL);
    if ( SQLITE_OK != rc ) {
        asprintf(&raise_msg,"Failure to open database %s for bootload: [SQLITE_ERROR %d] : %s", dbfile, rc, sqlite3_errmsg( db ) );
        am_bootstrap_cleanup_and_raise( raise_msg, db, stmt );
    }

    /* prepare the db query */
    sql_bytes = asprintf( &sql, "SELECT %s, %s FROM %s ORDER BY %s", fname_col, content_col, tbl_name, pk_col );
    rc = sqlite3_prepare_v2( db, sql, sql_bytes, &stmt, &sql_tail ) ;
    free( sql );
    if ( SQLITE_OK != rc) {
        asprintf( &raise_msg, 
                  "Failure to prepare bootload select statement table = '%s', rowid col = '%s', filename col ='%s', contents col = '%s' : [SQLITE_ERROR %d] %s\n",
                  tbl_name, pk_col, fname_col, content_col, rc, sqlite3_errmsg( db ));
        am_bootstrap_cleanup_and_raise( raise_msg, db, stmt );
    }

    /* loop over the resulting rows, eval'ing and loading $" */
    last_row_good = -1;
    while ( SQLITE_ROW == ( rc = sqlite3_step( stmt ) ) ) {
        /* file name */
        result_text   = sqlite3_column_text( stmt, 0 );
        result_length = sqlite3_column_bytes( stmt, 0 );
        require_name  = rb_str_new( (const char*)result_text, result_length );

        /* ruby code */
        result_text    = sqlite3_column_text( stmt, 1 );
        result_length  = sqlite3_column_bytes( stmt, 1 );
        eval_this_code = rb_str_new( (const char*)result_text, result_length );

        /* Kernel.eval( code, TOPLEVEL_BINDING, filename, 1 ) */ 
        rb_funcall(rb_mKernel, eval_id, 4, eval_this_code, toplevel_binding, require_name, INT2FIX(1) );

        /* TODO: for ruby 1.9 -- put in ? sqlite3://path/to/database?tablename=tbl_name#require_name */
        /* update $LOADED_FEATURES */
        rb_ary_push( rb_gv_get( "$LOADED_FEATURES" ), require_name );
    }

    /* if there was some sqlite error in the processing of the rows */
    if ( SQLITE_DONE != rc ) {
        asprintf( &raise_msg, "Failure in bootloading, last successfully loaded rowid was %d : [SQLITE_ERROR %d] %s\n", 
                  last_row_good, rc, sqlite3_errmsg( db ) );
        am_bootstrap_cleanup_and_raise( raise_msg, db, stmt );
    }

    /* finalize the statement */    
    rc = sqlite3_finalize( stmt );
    if ( SQLITE_OK != rc ) {
        asprintf( &raise_msg, "Failure to finalize bootload statement : [SQLITE_ERROR %d]\n", rc, sqlite3_errmsg( db ) );
        am_bootstrap_cleanup_and_raise( raise_msg, db, stmt );
    }

    stmt = NULL;

    /* close the database */
    rc = sqlite3_close( db );
    if ( SQLITE_OK != rc ) {
        asprintf( &raise_msg, "Failure to close database : [SQLITE_ERROR %d] : %s\n", rc, sqlite3_errmsg( db )),
        am_bootstrap_cleanup_and_raise( raise_msg, db,stmt );
    }

    return Qnil;
}

[Validate]