From 0ec13083f19731d2fb673ab3846693fd2c4a0797 Mon Sep 17 00:00:00 2001 From: Laurent Dami Date: Sun, 20 Jul 2014 18:57:19 +0200 Subject: [PATCH] implementation of FIND_FUNCTION, plus a couple of cosmetic changes in various places --- MANIFEST | 1 + dbdimp.c | 337 ++++++++++++--------- lib/DBD/SQLite/VirtualTable.pm | 64 +++- lib/DBD/SQLite/VirtualTable/FileContent.pm | 10 +- t/virtual_table/00_base.t | 11 +- t/virtual_table/01_destroy.t | 4 +- t/virtual_table/02_find_function.t | 173 +++++++++++ 7 files changed, 432 insertions(+), 168 deletions(-) create mode 100644 t/virtual_table/02_find_function.t diff --git a/MANIFEST b/MANIFEST index e660ef1..5b588e4 100644 --- a/MANIFEST +++ b/MANIFEST @@ -112,6 +112,7 @@ t/rt_88228_sqlite_3_8_0_crash.t t/rt_96878_fts_contentless_table.t t/virtual_table/00_base.t t/virtual_table/01_destroy.t +t/virtual_table/02_find_function.t t/virtual_table/10_filecontent.t t/virtual_table/11_filecontent_fulltext.t t/virtual_table/20_perldata.t diff --git a/dbdimp.c b/dbdimp.c index 19a6d71..5185199 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -1500,7 +1500,6 @@ sqlite_db_func_dispatcher(int is_unicode, sqlite3_context *context, int argc, sq } PUTBACK; - FREETMPS; LEAVE; } @@ -2779,6 +2778,7 @@ int sqlite_db_register_fts3_perl_tokenizer(pTHX_ SV *dbh) typedef struct perl_vtab { sqlite3_vtab base; SV *perl_vtab_obj; + HV *functions; } perl_vtab; typedef struct perl_vtab_cursor { @@ -2793,30 +2793,9 @@ typedef struct perl_vtab_init { -static int _call_perl_vtab_method(sqlite3_vtab *pVTab, const char *method) { - dTHX; - dSP; - ENTER; - SAVETMPS; - int count; - - PUSHMARK(SP); - XPUSHs(((perl_vtab *) pVTab)->perl_vtab_obj); - PUTBACK; - count = call_method (method, G_VOID); - SPAGAIN; - SP -= count; - - PUTBACK; - FREETMPS; - LEAVE; - - return SQLITE_OK; -} - - - static int _call_perl_vtab_method_int(sqlite3_vtab *pVTab, - const char *method, int i) { +/* auxiliary routine for generalized method calls. Arg "i" may be unused */ +static int _call_perl_vtab_method(sqlite3_vtab *pVTab, + const char *method, int i) { dTHX; dSP; ENTER; @@ -2831,7 +2810,6 @@ static int _call_perl_vtab_method(sqlite3_vtab *pVTab, const char *method) { SPAGAIN; SP -= count; - PUTBACK; FREETMPS; LEAVE; @@ -2841,8 +2819,6 @@ static int _call_perl_vtab_method(sqlite3_vtab *pVTab, const char *method) { - - static int perl_vt_New(const char *method, sqlite3 *db, void *pAux, int argc, const char *const *argv, @@ -2852,11 +2828,13 @@ static int perl_vt_New(const char *method, perl_vtab *vt; perl_vtab_init *init_data = (perl_vtab_init *)pAux; int count, i; + int rc = SQLITE_ERROR; /* allocate a perl_vtab structure */ vt = (perl_vtab *) sqlite3_malloc(sizeof(*vt)); if( vt==NULL ) return SQLITE_NOMEM; memset(vt, 0, sizeof(*vt)); + vt->functions = newHV(); ENTER; SAVETMPS; @@ -2907,20 +2885,23 @@ static int perl_vt_New(const char *method, /* call sqlite3_declare_vtab with the sql returned from method VTAB_TO_DECLARE(), converted to utf8 */ SV *sql = POPs; - int rc; rc = sqlite3_declare_vtab(db, SvPVutf8_nolen(sql)); - /* record the VirtualTable perl instance within the vtab structure */ - vt->perl_vtab_obj = SvREFCNT_inc(perl_vtab_obj); - cleanup: - *ppVTab = &vt->base; + if (rc == SQLITE_OK) { + /* record the VirtualTable perl instance within the vtab structure */ + vt->perl_vtab_obj = SvREFCNT_inc(perl_vtab_obj); + *ppVTab = &vt->base; + } + else { + sqlite3_free(vt); + } PUTBACK; FREETMPS; LEAVE; - return SQLITE_OK; + return rc; } @@ -2937,36 +2918,32 @@ static int perl_vt_Connect(sqlite3 *db, void *pAux, } -static int perl_vt_Disconnect(sqlite3_vtab *pVTab){ +static int _free_perl_vtab(perl_vtab *pVTab){ dTHX; - _call_perl_vtab_method(pVTab, "DISCONNECT"); + SvREFCNT_dec(pVTab->perl_vtab_obj); - perl_vtab *perl_pVTab = (perl_vtab *) pVTab; - SvREFCNT_dec(perl_pVTab->perl_vtab_obj); - - sqlite3_free(perl_pVTab); + /* deallocate coderefs that were declared through FindFunction() */ + hv_undef(pVTab->functions); + SvREFCNT_dec(pVTab->functions); + sqlite3_free(pVTab); return SQLITE_OK; } +static int perl_vt_Disconnect(sqlite3_vtab *pVTab){ + _call_perl_vtab_method(pVTab, "DISCONNECT", 0); + return _free_perl_vtab((perl_vtab *)pVTab); +} static int perl_vt_Drop(sqlite3_vtab *pVTab){ - dTHX; - - _call_perl_vtab_method(pVTab, "DROP"); - - perl_vtab *perl_pVTab = (perl_vtab *) pVTab; - SvREFCNT_dec(perl_pVTab->perl_vtab_obj); - - sqlite3_free(perl_pVTab); - - return SQLITE_OK; + _call_perl_vtab_method(pVTab, "DROP", 0); + return _free_perl_vtab((perl_vtab *)pVTab); } static char * -op2str(unsigned char op) { +_constraint_op_to_string(unsigned char op) { switch (op) { case SQLITE_INDEX_CONSTRAINT_EQ: return "="; @@ -2987,20 +2964,21 @@ op2str(unsigned char op) { static int perl_vt_BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo){ - int i, count; - dTHX; dSP; ENTER; SAVETMPS; + int i, count; + /* build the "where_constraints" datastructure */ AV *constraints = newAV(); for (i=0; inConstraint; i++){ struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[i]; HV *constraint = newHV(); + char *op_str = _constraint_op_to_string(pCons->op); hv_stores(constraint, "col", newSViv(pCons->iColumn)); - hv_stores(constraint, "op", newSVpv(op2str(pCons->op), 0)); + hv_stores(constraint, "op", newSVpv(op_str, 0)); hv_stores(constraint, "usable", pCons->usable ? &PL_sv_yes : &PL_sv_no); av_push(constraints, newRV_noinc((SV*) constraint)); } @@ -3015,7 +2993,7 @@ static int perl_vt_BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo){ av_push( order_by, newRV_noinc((SV*) order)); } - /* call the ->best_index() method */ + /* call the ->BEST_INDEX() method */ PUSHMARK(SP); XPUSHs( ((perl_vtab *) pVTab)->perl_vtab_obj); XPUSHs( sv_2mortal( newRV_noinc((SV*) constraints))); @@ -3038,7 +3016,7 @@ static int perl_vt_BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo){ if (val && SvOK(*val)) { STRLEN len; char *str = SvPVutf8(*val, len); - pIdxInfo->idxStr = sqlite3_malloc(len+1); + pIdxInfo->idxStr = sqlite3_malloc(len+1); memcpy(pIdxInfo->idxStr, str, len); pIdxInfo->idxStr[len] = 0; pIdxInfo->needToFreeIdxStr = 1; @@ -3053,17 +3031,15 @@ static int perl_vt_BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo){ /* loop over constraints to get back the "argvIndex" and "omit" keys that shoud have been added by the best_index() method call */ for (i=0; inConstraint; i++){ - struct sqlite3_index_constraint_usage *pConsUsage - = &pIdxInfo->aConstraintUsage[i]; SV **rv = av_fetch(constraints, i, FALSE); if (!(rv && SvROK(*rv) && SvTYPE(SvRV(*rv)) == SVt_PVHV)) croak("the call to BEST_INDEX() has corrupted constraint data"); - HV *hv = (HV*)SvRV(*rv); - SV **val; - val = hv_fetch(hv, "argvIndex", 9, FALSE); - + HV *hv = (HV*)SvRV(*rv); + SV **val = hv_fetch(hv, "argvIndex", 9, FALSE); int argvIndex = (val && SvOK(*val)) ? SvIV(*val) + 1: 0; + struct sqlite3_index_constraint_usage *pConsUsage + = &pIdxInfo->aConstraintUsage[i]; pConsUsage->argvIndex = argvIndex; val = hv_fetch(hv, "omit", 4, FALSE); pConsUsage->omit = (val && SvTRUE(*val)) ? 1 : 0; @@ -3085,6 +3061,14 @@ static int perl_vt_Open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ SAVETMPS; int count; + int rc = SQLITE_ERROR; + SV *perl_cursor; + + /* allocate a perl_vtab_cursor structure */ + perl_vtab_cursor *cursor; + cursor = (perl_vtab_cursor *) sqlite3_malloc(sizeof(*cursor)); + if( cursor==NULL ) return SQLITE_NOMEM; + memset(cursor, 0, sizeof(*cursor)); /* call the ->OPEN() method */ PUSHMARK(SP); @@ -3092,27 +3076,36 @@ static int perl_vt_Open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ PUTBACK; count = call_method ("OPEN", G_SCALAR); SPAGAIN; - if (count != 1) - croak("vtab->OPEN() method returned %d vals instead of 1", count); - SV *perl_cursor = POPs; - if ( !sv_isobject(perl_cursor) ) - croak("vtab->OPEN() method did not return a blessed cursor"); + if (count != 1) { + warn("vtab->OPEN() method returned %d vals instead of 1", count); + SP -= count; + goto cleanup; + } + perl_cursor = POPs; + if ( !sv_isobject(perl_cursor) ) { + warn("vtab->OPEN() method did not return a blessed cursor"); + goto cleanup; + } - /* allocate a perl_vtab_cursor structure */ - perl_vtab_cursor *cursor; - cursor = (perl_vtab_cursor *) sqlite3_malloc(sizeof(*cursor)); - if( cursor==NULL ) return SQLITE_NOMEM; - memset(cursor, 0, sizeof(*cursor)); - cursor->perl_cursor_obj = SvREFCNT_inc(perl_cursor); + /* everything went OK */ + rc = SQLITE_OK; + + cleanup: + + if (rc == SQLITE_OK) { + cursor->perl_cursor_obj = SvREFCNT_inc(perl_cursor); + *ppCursor = &cursor->base; + } + else { + sqlite3_free(cursor); + } - /* return that cursor */ - *ppCursor = &cursor->base; PUTBACK; FREETMPS; LEAVE; - return SQLITE_OK; + return rc; } static int perl_vt_Close(sqlite3_vtab_cursor *pVtabCursor){ @@ -3122,8 +3115,8 @@ static int perl_vt_Close(sqlite3_vtab_cursor *pVtabCursor){ SAVETMPS; int count; - /* Note : no call to a CLOSE() method; if needed, the Perl class - can implement a DESTROY() method */ + /* Note : there is no explicit call to a CLOSE() method; if + needed, the Perl class can implement a DESTROY() method */ perl_vtab_cursor *perl_pVTabCursor = (perl_vtab_cursor *) pVtabCursor; SvREFCNT_dec(perl_pVTabCursor->perl_cursor_obj); @@ -3136,12 +3129,9 @@ static int perl_vt_Close(sqlite3_vtab_cursor *pVtabCursor){ return SQLITE_OK; } -static int perl_vt_Filter( - sqlite3_vtab_cursor *pVtabCursor, - int idxNum, const char *idxStr, - int argc, sqlite3_value **argv -){ - +static int perl_vt_Filter( sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv ){ dTHX; dSP; ENTER; @@ -3230,6 +3220,7 @@ static int perl_vt_Column(sqlite3_vtab_cursor *pVtabCursor, ENTER; SAVETMPS; int count; + int rc = SQLITE_ERROR; /* call the column() method */ PUSHMARK(SP); @@ -3246,21 +3237,24 @@ static int perl_vt_Column(sqlite3_vtab_cursor *pVtabCursor, else { SV *result = POPs; sqlite_set_result(aTHX_ context, result, 0 ); + rc = SQLITE_OK; } PUTBACK; FREETMPS; LEAVE; - return SQLITE_OK; + return rc; } -static int perl_vt_Rowid(sqlite3_vtab_cursor *pVtabCursor, sqlite3_int64 *pRowid){ +static int perl_vt_Rowid( sqlite3_vtab_cursor *pVtabCursor, + sqlite3_int64 *pRowid ){ dTHX; dSP; ENTER; SAVETMPS; int count; + int rc = SQLITE_ERROR; /* call the rowid() method */ PUSHMARK(SP); @@ -3274,32 +3268,33 @@ static int perl_vt_Rowid(sqlite3_vtab_cursor *pVtabCursor, sqlite3_int64 *pRowid } else { *pRowid =POPi; + rc = SQLITE_OK; } PUTBACK; FREETMPS; LEAVE; - return SQLITE_OK; + return rc; } -static int perl_vt_Update(sqlite3_vtab *pVTab, - int argc, sqlite3_value **argv, - sqlite3_int64 *pRowid){ +static int perl_vt_Update( sqlite3_vtab *pVTab, + int argc, sqlite3_value **argv, + sqlite3_int64 *pRowid ){ dTHX; dSP; ENTER; SAVETMPS; int count, i; int is_unicode = _last_dbh_is_unicode(); + int rc = SQLITE_ERROR; - /* call the update() method */ + /* call the _SQLITE_UPDATE() method */ PUSHMARK(SP); XPUSHs(((perl_vtab *) pVTab)->perl_vtab_obj); for(i = 0; i < argc; i++) { XPUSHs(stacked_sv_from_sqlite3_value(aTHX_ argv[i], is_unicode)); } - PUTBACK; count = call_method ("_SQLITE_UPDATE", G_SCALAR); SPAGAIN; @@ -3307,19 +3302,22 @@ static int perl_vt_Update(sqlite3_vtab *pVTab, warn("cursor->_SQLITE_UPDATE() returned %d vals instead of 1", count); SP -= count; } - else if (argc > 1 && sqlite3_value_type(argv[0]) == SQLITE_NULL + else { + if (argc > 1 && sqlite3_value_type(argv[0]) == SQLITE_NULL && sqlite3_value_type(argv[1]) == SQLITE_NULL) { - /* this was an insert without any given rowid, so the result of - the method call must be passed in *pRowid*/ - SV *rowidsv = POPs; - if (!SvOK(rowidsv)) - *pRowid = 0; - else if (SvUOK(rowidsv)) - *pRowid = SvUV(rowidsv); - else if (SvIOK(rowidsv)) - *pRowid = SvIV(rowidsv); - else - *pRowid = SvNV(rowidsv); + /* this was an insert without any given rowid, so the result of + the method call must be passed in *pRowid*/ + SV *rowidsv = POPs; + if (!SvOK(rowidsv)) + *pRowid = 0; + else if (SvUOK(rowidsv)) + *pRowid = SvUV(rowidsv); + else if (SvIOK(rowidsv)) + *pRowid = SvIV(rowidsv); + else + *pRowid = SvNV(rowidsv); + } + rc = SQLITE_OK; } @@ -3327,36 +3325,89 @@ static int perl_vt_Update(sqlite3_vtab *pVTab, FREETMPS; LEAVE; - return SQLITE_OK; + return rc; } - - - - static int perl_vt_Begin(sqlite3_vtab *pVTab){ - return _call_perl_vtab_method(pVTab, "BEGIN_TRANSACTION"); + return _call_perl_vtab_method(pVTab, "BEGIN_TRANSACTION", 0); } static int perl_vt_Sync(sqlite3_vtab *pVTab){ - return _call_perl_vtab_method(pVTab, "SYNC_TRANSACTION"); + return _call_perl_vtab_method(pVTab, "SYNC_TRANSACTION", 0); } static int perl_vt_Commit(sqlite3_vtab *pVTab){ - return _call_perl_vtab_method(pVTab, "COMMIT_TRANSACTION"); + return _call_perl_vtab_method(pVTab, "COMMIT_TRANSACTION", 0); } static int perl_vt_Rollback(sqlite3_vtab *pVTab){ - return _call_perl_vtab_method(pVTab, "ROLLBACK_TRANSACTION"); + return _call_perl_vtab_method(pVTab, "ROLLBACK_TRANSACTION", 0); } -static int perl_vt_FindMethod(sqlite3_vtab *pVtab, int nArg, const char *zName, +static int perl_vt_FindFunction(sqlite3_vtab *pVTab, + int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg){ - croak("VT_FINDMETHOD: not implemented yet"); /* TODO */ - return SQLITE_OK; + dTHX; + dSP; + ENTER; + SAVETMPS; + int count; + int is_overloaded = 0; + char *func_name = sqlite3_mprintf("%s\t%d", zName, nArg); + STRLEN len = strlen(func_name); + HV *functions = ((perl_vtab *) pVTab)->functions; + SV* coderef = NULL; + + /* check if that function was already in cache */ + if (hv_exists(functions, func_name, len)) { + SV** val = hv_fetch(functions, func_name, len, FALSE); + if (val && SvOK(*val)) { + coderef = *val; + } + } + else { + /* call the FIND_FUNCTION() method */ + PUSHMARK(SP); + XPUSHs(((perl_vtab *) pVTab)->perl_vtab_obj); + XPUSHs(sv_2mortal(newSViv(nArg))); + XPUSHs(sv_2mortal(newSVpv(zName, 0))); + PUTBACK; + count = call_method ("FIND_FUNCTION", G_SCALAR); + SPAGAIN; + if (count != 1) { + warn("vtab->FIND_FUNCTION() method returned %d vals instead of 1", count); + SP -= count; + goto cleanup; + } + SV *result = POPs; + if (SvTRUE(result)) { + /* the coderef must be valid for the lifetime of pVTab, so + make a copy */ + coderef = newSVsv(result); + } + + /* store result in cache */ + hv_store(functions, func_name, len, coderef ? coderef : &PL_sv_undef, 0); + } + + /* return function information for sqlite3 within *pxFunc and *ppArg */ + is_overloaded = coderef && SvTRUE(coderef); + if (is_overloaded) { + *pxFunc = _last_dbh_is_unicode() ? sqlite_db_func_dispatcher_unicode + : sqlite_db_func_dispatcher_no_unicode; + *ppArg = coderef; + } + + cleanup: + PUTBACK; + FREETMPS; + LEAVE; + sqlite3_free(func_name); + return is_overloaded; } + static int perl_vt_Rename(sqlite3_vtab *pVTab, const char *zNew){ dTHX; dSP; @@ -3379,7 +3430,6 @@ static int perl_vt_Rename(sqlite3_vtab *pVTab, const char *zNew){ rc = POPi; } - PUTBACK; FREETMPS; LEAVE; @@ -3388,42 +3438,41 @@ static int perl_vt_Rename(sqlite3_vtab *pVTab, const char *zNew){ } static int perl_vt_Savepoint(sqlite3_vtab *pVTab, int point){ - return _call_perl_vtab_method_int(pVTab, "SAVEPOINT", point); + return _call_perl_vtab_method(pVTab, "SAVEPOINT", point); } static int perl_vt_Release(sqlite3_vtab *pVTab, int point){ - return _call_perl_vtab_method_int(pVTab, "RELEASE", point); + return _call_perl_vtab_method(pVTab, "RELEASE", point); } static int perl_vt_RollbackTo(sqlite3_vtab *pVTab, int point){ - return _call_perl_vtab_method_int(pVTab, "ROLLBACK_TO", point); + return _call_perl_vtab_method(pVTab, "ROLLBACK_TO", point); } static sqlite3_module perl_vt_Module = { - 1, /* iVersion */ - perl_vt_Create, /* xCreate */ - perl_vt_Connect, /* xConnect */ - perl_vt_BestIndex, /* xBestIndex */ - perl_vt_Disconnect, /* xDisconnect */ - perl_vt_Drop, /* xDestroy */ - perl_vt_Open, /* xOpen - open a cursor */ - perl_vt_Close, /* xClose - close a cursor */ - perl_vt_Filter, /* xFilter - configure scan constraints */ - perl_vt_Next, /* xNext - advance a cursor */ - perl_vt_Eof, /* xEof - check for end of scan */ - perl_vt_Column, /* xColumn - read data */ - perl_vt_Rowid, /* xRowid - read data */ - perl_vt_Update, /* xUpdate (optional) */ - perl_vt_Begin, /* xBegin (optional) */ - perl_vt_Sync, /* xSync (optional) */ - perl_vt_Commit, /* xCommit (optional) */ - perl_vt_Rollback, /* xRollback (optional) */ - /* perl_vt_FindMethod, /\* xFindMethod (optional) *\/ */ - NULL, /* xFindMethod not implemented yet */ - perl_vt_Rename, /* xRename */ - perl_vt_Savepoint, /* xSavepoint (optional) */ - perl_vt_Release, /* xRelease (optional) */ - perl_vt_RollbackTo /* xRollbackTo (optional) */ + 1, /* iVersion */ + perl_vt_Create, /* xCreate */ + perl_vt_Connect, /* xConnect */ + perl_vt_BestIndex, /* xBestIndex */ + perl_vt_Disconnect, /* xDisconnect */ + perl_vt_Drop, /* xDestroy */ + perl_vt_Open, /* xOpen - open a cursor */ + perl_vt_Close, /* xClose - close a cursor */ + perl_vt_Filter, /* xFilter - configure scan constraints */ + perl_vt_Next, /* xNext - advance a cursor */ + perl_vt_Eof, /* xEof - check for end of scan */ + perl_vt_Column, /* xColumn - read data */ + perl_vt_Rowid, /* xRowid - read data */ + perl_vt_Update, /* xUpdate (optional) */ + perl_vt_Begin, /* xBegin (optional) */ + perl_vt_Sync, /* xSync (optional) */ + perl_vt_Commit, /* xCommit (optional) */ + perl_vt_Rollback, /* xRollback (optional) */ + perl_vt_FindFunction, /* xFindFunction (optional) */ + perl_vt_Rename, /* xRename */ + perl_vt_Savepoint, /* xSavepoint (optional) */ + perl_vt_Release, /* xRelease (optional) */ + perl_vt_RollbackTo /* xRollbackTo (optional) */ }; diff --git a/lib/DBD/SQLite/VirtualTable.pm b/lib/DBD/SQLite/VirtualTable.pm index 16376a8..fb7af50 100644 --- a/lib/DBD/SQLite/VirtualTable.pm +++ b/lib/DBD/SQLite/VirtualTable.pm @@ -161,7 +161,7 @@ sub ROLLBACK_TRANSACTION {return 0} sub SAVEPOINT {return 0} sub RELEASE {return 0} sub ROLLBACK_TO {return 0} -sub FIND_METHOD {return 0} +sub FIND_FUNCTION {return 0} sub RENAME {return 0} @@ -196,12 +196,30 @@ sub NEW { } -# methods to be redefined in subclasses (here are stupid implementations) -sub FILTER { my ($self, $idxNum, $idxStr, @values) = @_; return } -sub EOF { my ($self) = @_; return 1 } -sub NEXT { my ($self) = @_; return } -sub COLUMN { my ($self, $idxCol) = @_; return } -sub ROWID { my ($self) = @_; return 1 } +sub FILTER { + my ($self, $idxNum, $idxStr, @values) = @_; + die "FILTER() should be redefined in cursor subclass"; +} + +sub EOF { + my ($self) = @_; + die "EOF() should be redefined in cursor subclass"; +} + +sub NEXT { + my ($self) = @_; + die "NEXT() should be redefined in cursor subclass"; +} + +sub COLUMN { + my ($self, $idxCol) = @_; + die "COLUMN() should be redefined in cursor subclass"; +} + +sub ROWID { + my ($self) = @_; + die "ROWID() should be redefined in cursor subclass"; +} 1; @@ -347,11 +365,10 @@ The default implementation just calls L. $class->_PREPARE_SELF($dbh_ref, $module_name, $db_name, $vtab_name, @args); -Prepares the datastructure for a virtual table instance. - C<@args> is just the collection -of strings (comma-separated) that were given within the -C statement; each subclass should decide -what to do with this information, +Prepares the datastructure for a virtual table instance. C<@args> is + just the collection of strings (comma-separated) that were given + within the C statement; each subclass should + decide what to do with this information, The method parses C<@args> to differentiate between I (strings of shape C<$key>=C<$value> or C<$key>=C<"$value">, stored in @@ -437,8 +454,11 @@ The default implementation for DISCONNECT is empty. This method is called automatically just after L or L, to register the columns of the virtual table within the sqlite kernel. The method should return a string containing a SQL C statement; -but only the column declaration parts will be considered (see -L). +but only the column declaration parts will be considered. +Columns may be declared with the special keyword "HIDDEN", which means that +they are used internally for the the virtual table implementation, and are +not visible to users -- see L +and L for detailed explanations. The default implementation returns: @@ -637,6 +657,22 @@ could be changed from a SQL statement such as UPDATE table SET rowid=rowid+1 WHERE ...; +=head3 FIND_FUNCTION + + $vtab->FIND_FUNCTION($num_args, $func_name); + +When a function uses a column from a virtual table as its first +argument, this method is called to see if the virtual table would like +to overload the function. Parameters are the number of arguments to +the function, and the name of the function. If no overloading is +desired, this method should return false. To overload the function, +this method should return a coderef to the function implementation. + +Each virtual table keeps a cache of results from L calls, +so the method will be called only once for each pair +C<< ($num_args, $func_name) >>. + + =head3 BEGIN_TRANSACTION Called to begin a transaction on the virtual table. diff --git a/lib/DBD/SQLite/VirtualTable/FileContent.pm b/lib/DBD/SQLite/VirtualTable/FileContent.pm index 5b45c61..113e6f3 100644 --- a/lib/DBD/SQLite/VirtualTable/FileContent.pm +++ b/lib/DBD/SQLite/VirtualTable/FileContent.pm @@ -73,7 +73,8 @@ sub NEW { $self->{columns} = [ "$self->{options}{content_col} TEXT", map {"$_ $src_col{$_}"} @exposed_cols ]; - # acquire a coderef to the get_content() implementation + # acquire a coderef to the get_content() implementation, which + # was given as a symbolic reference in %options no strict 'refs'; $self->{get_content} = \ &{$self->{options}{get_content}}; @@ -179,7 +180,7 @@ sub FILTER { # build SQL local $" = ", "; my @cols = @{$vtable->{headers}}; - $cols[0] = 'rowid'; # replace the content column by the rowid + $cols[0] = 'rowid'; # replace the content column by the rowid push @cols, $vtable->{options}{path_col}; # path col in last position my $sql = "SELECT @cols FROM $vtable->{options}{source}"; $sql .= " WHERE $idxStr" if $idxStr; @@ -207,7 +208,6 @@ sub NEXT { $self->{row} = $self->{sth}->fetchrow_arrayref; } - sub COLUMN { my ($self, $idxCol) = @_; @@ -220,14 +220,14 @@ sub ROWID { return $self->{row}[0]; } - sub file_content { my ($self) = @_; my $root = $self->{vtable}{options}{root}; my $path = $self->{row}[-1]; + my $get_content_func = $self->{vtable}{get_content}; - return $self->{vtable}{get_content}->($path, $root); + return $get_content_func->($path, $root); } diff --git a/t/virtual_table/00_base.t b/t/virtual_table/00_base.t index 94f17c0..1865c11 100644 --- a/t/virtual_table/00_base.t +++ b/t/virtual_table/00_base.t @@ -43,10 +43,16 @@ use warnings; use base 'DBD::SQLite::VirtualTable'; use YAML; -sub INITIALIZE { - my $self = shift; +sub NEW { + my $class = shift; + + my $self = $class->_PREPARE_SELF(@_); + bless $self, $class; + # stupid pragma call, just to check that the dbh is OK $self->dbh->do("PRAGMA application_id=999"); + + return $self; } @@ -118,7 +124,6 @@ sub COLUMN { my ($self, $idxCol) = @_; return "auto_vivify:$idxCol"; - return $idxCol; } sub ROWID { diff --git a/t/virtual_table/01_destroy.t b/t/virtual_table/01_destroy.t index 0e15205..50d29c5 100644 --- a/t/virtual_table/01_destroy.t +++ b/t/virtual_table/01_destroy.t @@ -51,8 +51,8 @@ ok !$DBD::SQLite::VirtualTable::T::CREATE_COUNT, "no vtab created"; ok !$DBD::SQLite::VirtualTable::T::CONNECT_COUNT, "no vtab connected"; my $sth = $dbh->prepare("SELECT * FROM barfoo"); -ok !$DBD::SQLite::VirtualTable::T::CREATE_COUNT, "no vtab created"; -is $DBD::SQLite::VirtualTable::T::CONNECT_COUNT, 1, "1 vtab connected"; +ok !$DBD::SQLite::VirtualTable::T::CREATE_COUNT, "no vtab created"; +is $DBD::SQLite::VirtualTable::T::CONNECT_COUNT, 1, "1 vtab connected"; package DBD::SQLite::VirtualTable::T; diff --git a/t/virtual_table/02_find_function.t b/t/virtual_table/02_find_function.t new file mode 100644 index 0000000..b7e49d4 --- /dev/null +++ b/t/virtual_table/02_find_function.t @@ -0,0 +1,173 @@ +#!/usr/bin/perl +use strict; +BEGIN { + $| = 1; + $^W = 1; +} + +use t::lib::Test qw/connect_ok/; +use Test::More; +use Test::NoWarnings; + +plan tests => 15; + +my $dbh = connect_ok( RaiseError => 1, PrintError => 0, AutoCommit => 1 ); + +$dbh->sqlite_create_module(vtab => "DBD::SQLite::VirtualTable::T"); + +ok $dbh->do("CREATE VIRTUAL TABLE foobar USING vtab(foo INTEGER, bar INTEGER)"), + "created foobar"; + +# overload functions "abs" and "substr" +$DBD::SQLite::VirtualTable::T::funcs{abs}{overloaded} + = sub {my $val = shift; return "fake_abs($val)" }; +$DBD::SQLite::VirtualTable::T::funcs{substr}{overloaded} + = sub {my ($val, $offset, $len) = @_; return "fake_substr($val, $offset, $len)" }; + +# make a first query +my $row = $dbh->selectrow_hashref(<<""); + SELECT abs(foo) afoo, + abs(bar) abar, + substr(foo, 3, 5) sfoo, + trim(foo) tfoo + FROM foobar + +is $DBD::SQLite::VirtualTable::T::funcs{abs}{calls}, 1, "abs called"; +is $DBD::SQLite::VirtualTable::T::funcs{substr}{calls}, 1, "substr called"; +is $DBD::SQLite::VirtualTable::T::funcs{trim}{calls}, 1, "trim called"; + +is_deeply $row, { 'abar' => 'fake_abs(1)', + 'afoo' => 'fake_abs(0)', + 'sfoo' => 'fake_substr(0, 3, 5)', + 'tfoo' => '0' }, "func results"; + +# new query : FIND_FUNCTION should not be called again +$row = $dbh->selectrow_hashref(<<""); + SELECT abs(foo) afoo, + abs(bar) abar, + substr(foo, 3, 5) sfoo, + trim(foo) tfoo + FROM foobar + +is $DBD::SQLite::VirtualTable::T::funcs{abs}{calls}, 1, "abs still 1"; +is $DBD::SQLite::VirtualTable::T::funcs{substr}{calls}, 1, "substr still 1"; +is $DBD::SQLite::VirtualTable::T::funcs{trim}{calls}, 1, "trim still 1"; + + +# new table : should issue new calls to FIND_FUNCTION +ok $dbh->do("CREATE VIRTUAL TABLE barfoo USING vtab(foo INTEGER, bar INTEGER)"), + "created barfoo"; + +$row = $dbh->selectrow_hashref(<<""); + SELECT abs(foo) afoo, + abs(bar) abar, + substr(foo, 3, 5) sfoo, + trim(foo) tfoo + FROM barfoo + +is $DBD::SQLite::VirtualTable::T::funcs{abs}{calls}, 2, "abs now 2"; +is $DBD::SQLite::VirtualTable::T::funcs{substr}{calls}, 2, "substr now 2"; +is $DBD::SQLite::VirtualTable::T::funcs{trim}{calls}, 2, "trim now 2"; + + +# drop table : should free references to functions +ok $dbh->do("DROP TABLE foobar"); + +# drop connection +undef $dbh; + +note "done"; + +package DBD::SQLite::VirtualTable::T; +use strict; +use warnings; +use base 'DBD::SQLite::VirtualTable'; +use YAML; + + + +sub BEST_INDEX { + my ($self, $constraints, $order_by) = @_; + + my $ix = 0; + + foreach my $constraint (@$constraints) { + $constraint->{argvIndex} = $ix++; + $constraint->{omit} = 1; # to prevent sqlite core to check values + } + + my $outputs = { + idxNum => 1, + idxStr => "foobar", + orderByConsumed => 0, + estimatedCost => 1.0, + estimatedRows => undef, + }; + + return $outputs; +} + +our %funcs; + + +sub FIND_FUNCTION { + my ($self, $n_arg, $function_name) = @_; + + $funcs{$function_name}{calls} += 1; + my $func = $funcs{$function_name}{overloaded}; + return $func; +} + + +package DBD::SQLite::VirtualTable::T::Cursor; +use strict; +use warnings; +use base 'DBD::SQLite::VirtualTable::Cursor'; +use YAML; + +sub NEW { + my $class = shift; + + my $self = $class->DBD::SQLite::VirtualTable::Cursor::NEW(@_); + $self->{row_count} = 5; + + return $self; +} + +sub FILTER { + my ($self, $idxNum, $idxStr, @values) = @_; + + return; +} + + + +sub EOF { + my $self = shift; + + return !$self->{row_count}; +} + +sub NEXT { + my $self = shift; + + $self->{row_count}--; +} + +sub COLUMN { + my ($self, $idxCol) = @_; + + return $idxCol; +} + +sub ROWID { + my ($self) = @_; + + return $self->{row_count}; +} + + +1; + + +