|
| 1 | +#include <stdio.h> |
| 2 | +#include <sqlite3_ruby.h> |
| 3 | + |
| 4 | +#undef ENABLE_TRACE |
| 5 | + |
| 6 | +#ifdef ENABLE_TRACE |
| 7 | +static FILE* pf; |
| 8 | +# define TRACE(str) \ |
| 9 | + fprintf(pf, "%s:%d:%s\n", __FILE__, __LINE__, str); \ |
| 10 | + fflush(pf); |
| 11 | +#else |
| 12 | +# define TRACE(str) ; |
| 13 | +#endif |
| 14 | + |
| 15 | +VALUE cSqlite3Module; |
| 16 | + |
| 17 | +/** structure for ruby virtual table: inherits from sqlite3_vtab */ |
| 18 | +typedef struct { |
| 19 | + // mandatory sqlite3 fields |
| 20 | + const sqlite3_module* pModule; |
| 21 | + int nRef; |
| 22 | + char *zErrMsg; |
| 23 | + // Ruby fields |
| 24 | + VALUE vtable; |
| 25 | +} ruby_sqlite3_vtab; |
| 26 | + |
| 27 | +/** structure for ruby cursors: inherits from sqlite3_vtab_cursor */ |
| 28 | +typedef struct { |
| 29 | + ruby_sqlite3_vtab* pVTab; |
| 30 | + VALUE row; |
| 31 | + int rowid; |
| 32 | +} ruby_sqlite3_vtab_cursor; |
| 33 | + |
| 34 | + |
| 35 | +/** |
| 36 | + * lookup for a ruby class <ModuleName>::<TableName> and create an instance of this class |
| 37 | + * This instance is then used to bind sqlite vtab callbacks |
| 38 | + */ |
| 39 | +static int xCreate(sqlite3* db, VALUE *module_name, |
| 40 | + int argc, char **argv, |
| 41 | + ruby_sqlite3_vtab **ppVTab, |
| 42 | + char **pzErr) |
| 43 | +{ |
| 44 | + VALUE sql_stmt, module, ruby_class; |
| 45 | + ID table_id, module_id; |
| 46 | + VALUE ruby_class_args[0]; |
| 47 | + const char* module_name_cstr = (const char*)StringValuePtr(*module_name); |
| 48 | + const char* table_name_cstr = (const char*)argv[2]; |
| 49 | + TRACE("xCreate"); |
| 50 | + |
| 51 | + |
| 52 | + // lookup for ruby class named like <module_id>::<table_name> |
| 53 | + module_id = rb_intern( module_name_cstr ); |
| 54 | + module = rb_const_get(rb_cObject, module_id); |
| 55 | + table_id = rb_intern( table_name_cstr ); |
| 56 | + ruby_class = rb_const_get(module, table_id); |
| 57 | + |
| 58 | + // alloc a new ruby_sqlite3_vtab object |
| 59 | + // and store related attributes |
| 60 | + (*ppVTab) = (ruby_sqlite3_vtab*)malloc(sizeof(ruby_sqlite3_vtab)); |
| 61 | + |
| 62 | + // create a new instance |
| 63 | + (*ppVTab)->vtable = rb_class_new_instance(0, ruby_class_args, ruby_class); |
| 64 | + |
| 65 | + // call the create function |
| 66 | + sql_stmt = rb_funcall((*ppVTab)->vtable, rb_intern("create_statement"), 0); |
| 67 | + |
| 68 | +#ifdef HAVE_RUBY_ENCODING_H |
| 69 | + if(!UTF8_P(sql_stmt)) { |
| 70 | + sql_stmt = rb_str_export_to_enc(sql_stmt, rb_utf8_encoding()); |
| 71 | + } |
| 72 | +#endif |
| 73 | + if ( sqlite3_declare_vtab(db, StringValuePtr(sql_stmt)) ) |
| 74 | + rb_raise(rb_eArgError, "fail to declare virtual table"); |
| 75 | + |
| 76 | + TRACE("xCreate done"); |
| 77 | + return SQLITE_OK; |
| 78 | +} |
| 79 | + |
| 80 | +static int xConnect(sqlite3* db, void *pAux, |
| 81 | + int argc, char **argv, |
| 82 | + ruby_sqlite3_vtab **ppVTab, |
| 83 | + char **pzErr) |
| 84 | +{ |
| 85 | + TRACE("xConnect"); |
| 86 | + return xCreate(db, pAux, argc, argv, ppVTab, pzErr); |
| 87 | +} |
| 88 | + |
| 89 | +static int xBestIndex(ruby_sqlite3_vtab *pVTab, sqlite3_index_info* info) |
| 90 | +{ |
| 91 | + TRACE("xBestIndex"); |
| 92 | + return SQLITE_OK; |
| 93 | +} |
| 94 | + |
| 95 | +static int xDestroy(ruby_sqlite3_vtab *pVTab) |
| 96 | +{ |
| 97 | + TRACE("xDestroy"); |
| 98 | + free(pVTab); |
| 99 | + return SQLITE_OK; |
| 100 | +} |
| 101 | + |
| 102 | +static int xDisconnect(ruby_sqlite3_vtab *pVTab) |
| 103 | +{ |
| 104 | + TRACE("xDisconnect"); |
| 105 | + return xDestroy(pVTab); |
| 106 | +} |
| 107 | + |
| 108 | +static int xOpen(ruby_sqlite3_vtab *pVTab, ruby_sqlite3_vtab_cursor **ppCursor) |
| 109 | +{ |
| 110 | + TRACE("xOpen"); |
| 111 | + rb_funcall( pVTab->vtable, rb_intern("open"), 0 ); |
| 112 | + *ppCursor = (ruby_sqlite3_vtab_cursor*)malloc(sizeof(ruby_sqlite3_vtab_cursor)); |
| 113 | + (*ppCursor)->pVTab = pVTab; |
| 114 | + (*ppCursor)->rowid = 0; |
| 115 | + return SQLITE_OK; |
| 116 | +} |
| 117 | + |
| 118 | +static int xClose(ruby_sqlite3_vtab_cursor* cursor) |
| 119 | +{ |
| 120 | + TRACE("xClose"); |
| 121 | + rb_funcall( cursor->pVTab->vtable, rb_intern("close"), 0 ); |
| 122 | + free(cursor); |
| 123 | + return SQLITE_OK; |
| 124 | +} |
| 125 | + |
| 126 | +static int xNext(ruby_sqlite3_vtab_cursor* cursor) |
| 127 | +{ |
| 128 | + TRACE("xNext"); |
| 129 | + cursor->row = rb_funcall(cursor->pVTab->vtable, rb_intern("next"), 0); |
| 130 | + ++(cursor->rowid); |
| 131 | + return SQLITE_OK; |
| 132 | +} |
| 133 | + |
| 134 | +static int xFilter(ruby_sqlite3_vtab_cursor* cursor, int idxNum, const char *idxStr, |
| 135 | + int argc, sqlite3_value **argv) |
| 136 | +{ |
| 137 | + TRACE("xFilter"); |
| 138 | + cursor->rowid = 0; |
| 139 | + return xNext(cursor); |
| 140 | +} |
| 141 | + |
| 142 | +static int xEof(ruby_sqlite3_vtab_cursor* cursor) |
| 143 | +{ |
| 144 | + TRACE("xEof"); |
| 145 | + return (cursor->row == Qnil); |
| 146 | +} |
| 147 | + |
| 148 | +static int xColumn(ruby_sqlite3_vtab_cursor* cursor, sqlite3_context* context, int i) |
| 149 | +{ |
| 150 | + VALUE val = rb_ary_entry(cursor->row, i); |
| 151 | + TRACE("xColumn(%d)"); |
| 152 | + |
| 153 | + set_sqlite3_func_result(context, val); |
| 154 | + return SQLITE_OK; |
| 155 | +} |
| 156 | + |
| 157 | +static int xRowid(ruby_sqlite3_vtab_cursor* cursor, sqlite_int64 *pRowid) |
| 158 | +{ |
| 159 | + TRACE("xRowid"); |
| 160 | + *pRowid = cursor->rowid; |
| 161 | + return SQLITE_OK; |
| 162 | +} |
| 163 | + |
| 164 | +static sqlite3_module ruby_proxy_module = |
| 165 | +{ |
| 166 | + 0, /* iVersion */ |
| 167 | + xCreate, /* xCreate - create a vtable */ |
| 168 | + xConnect, /* xConnect - associate a vtable with a connection */ |
| 169 | + xBestIndex, /* xBestIndex - best index */ |
| 170 | + xDisconnect, /* xDisconnect - disassociate a vtable with a connection */ |
| 171 | + xDestroy, /* xDestroy - destroy a vtable */ |
| 172 | + xOpen, /* xOpen - open a cursor */ |
| 173 | + xClose, /* xClose - close a cursor */ |
| 174 | + xFilter, /* xFilter - configure scan constraints */ |
| 175 | + xNext, /* xNext - advance a cursor */ |
| 176 | + xEof, /* xEof - indicate end of result set*/ |
| 177 | + xColumn, /* xColumn - read data */ |
| 178 | + xRowid, /* xRowid - read data */ |
| 179 | + NULL, /* xUpdate - write data */ |
| 180 | + NULL, /* xBegin - begin transaction */ |
| 181 | + NULL, /* xSync - sync transaction */ |
| 182 | + NULL, /* xCommit - commit transaction */ |
| 183 | + NULL, /* xRollback - rollback transaction */ |
| 184 | + NULL, /* xFindFunction - function overloading */ |
| 185 | +}; |
| 186 | + |
| 187 | +static void deallocate(void * ctx) |
| 188 | +{ |
| 189 | + sqlite3ModuleRubyPtr c = (sqlite3ModuleRubyPtr)ctx; |
| 190 | + xfree(c); |
| 191 | +} |
| 192 | + |
| 193 | +static VALUE allocate(VALUE klass) |
| 194 | +{ |
| 195 | + sqlite3ModuleRubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3ModuleRuby)); |
| 196 | + ctx->module = NULL; |
| 197 | + |
| 198 | + return Data_Wrap_Struct(klass, NULL, deallocate, ctx); |
| 199 | +} |
| 200 | + |
| 201 | +static VALUE initialize(VALUE self, VALUE db, VALUE name) |
| 202 | +{ |
| 203 | + sqlite3RubyPtr db_ctx; |
| 204 | + sqlite3ModuleRubyPtr ctx; |
| 205 | + |
| 206 | + StringValue(name); |
| 207 | + |
| 208 | + Data_Get_Struct(db, sqlite3Ruby, db_ctx); |
| 209 | + Data_Get_Struct(self, sqlite3ModuleRuby, ctx); |
| 210 | + |
| 211 | + |
| 212 | + if(!db_ctx->db) |
| 213 | + rb_raise(rb_eArgError, "initializing a module on a closed database"); |
| 214 | + |
| 215 | +#ifdef HAVE_RUBY_ENCODING_H |
| 216 | + if(!UTF8_P(name)) { |
| 217 | + name = rb_str_export_to_enc(name, rb_utf8_encoding()); |
| 218 | + } |
| 219 | +#endif |
| 220 | + |
| 221 | + // make possible to access to ruby object from c |
| 222 | + ctx->module_name = name; |
| 223 | + |
| 224 | + sqlite3_create_module( |
| 225 | + db_ctx->db, |
| 226 | + (const char *)StringValuePtr(name), |
| 227 | + &ruby_proxy_module, |
| 228 | + &(ctx->module_name) //the vtable required the module name |
| 229 | + ); |
| 230 | + |
| 231 | + TRACE("module initialized"); |
| 232 | + return self; |
| 233 | +} |
| 234 | + |
| 235 | +void init_sqlite3_module() |
| 236 | +{ |
| 237 | +#ifdef ENABLE_TRACE |
| 238 | + pf = fopen("trace.log", "w"); |
| 239 | +#endif |
| 240 | + cSqlite3Module = rb_define_class_under(mSqlite3, "Module", rb_cObject); |
| 241 | + rb_define_alloc_func(cSqlite3Module, allocate); |
| 242 | + rb_define_method(cSqlite3Module, "initialize", initialize, 2); |
| 243 | +} |
| 244 | + |
0 commit comments