From 9018a4683ce0bf32cd6bd9dfb64c794893678bf4 Mon Sep 17 00:00:00 2001 From: Laurent Dami Date: Fri, 11 Jul 2014 06:06:19 +0200 Subject: [PATCH] various code refactorings, completion of the doc --- dbdimp.c | 59 +- lib/DBD/SQLite.pm | 12 +- lib/DBD/SQLite/VirtualTable.pm | 707 ++++++++++++++++++--- lib/DBD/SQLite/VirtualTable/FileContent.pm | 286 ++++++--- lib/DBD/SQLite/VirtualTable/PerlData.pm | 163 +++-- t/virtual_table/00_base.t | 3 +- t/virtual_table/01_destroy.t | 14 +- t/virtual_table/10_filecontent.t | 8 +- t/virtual_table/11_filecontent_fulltext.t | 15 +- 9 files changed, 973 insertions(+), 294 deletions(-) diff --git a/dbdimp.c b/dbdimp.c index 0fac6b4..36bb26c 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -2950,8 +2950,8 @@ static int perl_vt_New(const char *method, /* check the return value */ if ( count != 1 ) { - *pzErr = sqlite3_mprintf("vtab->NEW() should return one value, got %d", - count ); + *pzErr = sqlite3_mprintf("vtab->%s() should return one value, got %d", + method, count ); SP -= count; /* Clear the stack */ goto cleanup; } @@ -2959,11 +2959,12 @@ static int perl_vt_New(const char *method, /* get the VirtualTable instance */ SV *perl_instance = POPs; if ( !sv_isobject(perl_instance) ) { - *pzErr = sqlite3_mprintf("vtab->NEW() should return a blessed reference"); + *pzErr = sqlite3_mprintf("vtab->%s() should return a blessed reference", + method); goto cleanup; } - /* call the ->DECLARE_VTAB() method */ + /* call the ->VTAB_TO_DECLARE() method */ PUSHMARK(SP); XPUSHs(perl_instance); PUTBACK; @@ -3047,12 +3048,12 @@ op2str(unsigned char op) { return "="; case SQLITE_INDEX_CONSTRAINT_GT: return ">"; - case SQLITE_INDEX_CONSTRAINT_LE: - return "<="; - case SQLITE_INDEX_CONSTRAINT_LT: - return "<"; case SQLITE_INDEX_CONSTRAINT_GE: return ">="; + case SQLITE_INDEX_CONSTRAINT_LT: + return "<"; + case SQLITE_INDEX_CONSTRAINT_LE: + return "<="; case SQLITE_INDEX_CONSTRAINT_MATCH: return "MATCH"; default: @@ -3081,7 +3082,7 @@ static int perl_vt_BestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *pIdxInfo){ } /* build the "order_by" datastructure */ - AV *order_by = newAV(); + AV *order_by = newAV(); for (i=0; inOrderBy; i++){ struct sqlite3_index_orderby const *pOrder = &pIdxInfo->aOrderBy[i]; HV *order = newHV(); @@ -3197,14 +3198,8 @@ static int perl_vt_Close(sqlite3_vtab_cursor *pVtabCursor){ SAVETMPS; int count; - /* call the close() method */ - PUSHMARK(SP); - XPUSHs(((perl_vtab_cursor *) pVtabCursor)->perl_cursor_instance); - PUTBACK; - count = call_method("CLOSE", G_VOID); - SPAGAIN; - SP -= count; - + /* Note : no 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_instance); @@ -3542,8 +3537,12 @@ sqlite_db_destroy_module_data(void *pAux) int sqlite_db_create_module(pTHX_ SV *dbh, const char *name, const char *perl_class) { + dSP; + ENTER; + SAVETMPS; + D_imp_dbh(dbh); - int rc; + int count, rc, retval = TRUE; if (!DBIc_ACTIVE(imp_dbh)) { sqlite_error(dbh, -2, "attempt to create module on inactive database handle"); @@ -3553,7 +3552,7 @@ sqlite_db_create_module(pTHX_ SV *dbh, const char *name, const char *perl_class) /* load the module if needed */ char *module_ISA = sqlite3_mprintf("%s::ISA", perl_class); if (!get_av(module_ISA, 0)) { - char *loading_code = sqlite3_mprintf("require %s", perl_class); + char *loading_code = sqlite3_mprintf("use %s", perl_class); eval_pv(loading_code, TRUE); sqlite3_free(loading_code); } @@ -3566,20 +3565,34 @@ sqlite_db_create_module(pTHX_ SV *dbh, const char *name, const char *perl_class) sv_rvweaken(init_data->dbh); init_data->perl_class = sqlite3_mprintf(perl_class); - + /* register within sqlite */ rc = sqlite3_create_module_v2( imp_dbh->db, name, &perl_vt_Module, init_data, sqlite_db_destroy_module_data ); - if ( rc != SQLITE_OK ) { sqlite_error(dbh, rc, form("sqlite_create_module failed with error %s", sqlite3_errmsg(imp_dbh->db))); - return FALSE; + retval = FALSE; } - return TRUE; + + + /* call the CREATE_MODULE() method */ + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVpv(perl_class, 0))); + XPUSHs(sv_2mortal(newSVpv(name, 0))); + PUTBACK; + count = call_method("CREATE_MODULE", G_VOID); + SPAGAIN; + SP -= count; + + PUTBACK; + FREETMPS; + LEAVE; + + return retval; } diff --git a/lib/DBD/SQLite.pm b/lib/DBD/SQLite.pm index 8efa7a5..663ee4e 100644 --- a/lib/DBD/SQLite.pm +++ b/lib/DBD/SQLite.pm @@ -2413,8 +2413,9 @@ tables but are implemented internally through specific functions. The fulltext or R* tree features described in the previous chapters are examples of such virtual tables, implemented in C code. -C also supports virtual tables implemented in Perl code: -see L. This can have many interesting uses +C also supports virtual tables implemented in I: +see L for using or implementing such +virtual tables. These can have many interesting uses for joining regular DBMS data with some other kind of data within your Perl programs. Bundled with the present distribution are : @@ -2423,13 +2424,13 @@ Perl programs. Bundled with the present distribution are : =item * L : implements a virtual -column that exposes content from files. This is especially useful -in conjuction with a fulltext index; see L. +column that exposes file contents. This is especially useful +in conjunction with a fulltext index; see L. =item * L : binds to a Perl array -within your main program. This can be used for simple import/export +within the Perl program. This can be used for simple import/export operations, for debugging purposes, for joining data from different sources, etc. @@ -2437,7 +2438,6 @@ sources, etc. Other Perl virtual tables may also be published separately on CPAN. - =head1 FOR DBD::SQLITE EXTENSION AUTHORS Since 1.30_01, you can retrieve the bundled sqlite C source and/or diff --git a/lib/DBD/SQLite/VirtualTable.pm b/lib/DBD/SQLite/VirtualTable.pm index 56ceff5..16376a8 100644 --- a/lib/DBD/SQLite/VirtualTable.pm +++ b/lib/DBD/SQLite/VirtualTable.pm @@ -1,6 +1,6 @@ -# TODO : fix bug with column name / type - +#====================================================================== package DBD::SQLite::VirtualTable; +#====================================================================== use strict; use warnings; use Scalar::Util qw/weaken/; @@ -12,21 +12,21 @@ our $VERSION = '0.01'; our @ISA; -sub DESTROY_MODULE { - my $class = shift; -} +#---------------------------------------------------------------------- +# methods for registering/destroying the module +#---------------------------------------------------------------------- -sub CREATE { - my $class = shift; - return $class->NEW(@_); -} +sub CREATE_MODULE { my ($class, $mod_name) = @_; } +sub DESTROY_MODULE { my ($class, $mod_name) = @_; } -sub CONNECT { - my $class = shift; - return $class->NEW(@_); -} +#---------------------------------------------------------------------- +# methods for creating/destroying instances +#---------------------------------------------------------------------- -sub NEW { # called when instanciating a virtual table +sub CREATE { my $class = shift; return $class->NEW(@_); } +sub CONNECT { my $class = shift; return $class->NEW(@_); } + +sub _PREPARE_SELF { my ($class, $dbh_ref, $module_name, $db_name, $vtab_name, @args) = @_; my @columns; @@ -44,7 +44,7 @@ sub NEW { # called when instanciating a virtual table } } - # build $self and initialize + # build $self my $self = { dbh_ref => $dbh_ref, module_name => $module_name, @@ -54,31 +54,15 @@ sub NEW { # called when instanciating a virtual table options => \%options, }; weaken $self->{dbh_ref}; - bless $self, $class; - $self->initialize(); return $self; } -sub dbh { - my $self = shift; - return ${$self->{dbh_ref}}; -} +sub NEW { + my $class = shift; - -sub initialize { - my $self = shift; -} - - - - -sub DROP { - my $self = shift; -} - -sub DISCONNECT { - my $self = shift; + my $self = $class->_PREPARE_SELF(@_); + bless $self, $class; } @@ -91,27 +75,27 @@ sub VTAB_TO_DECLARE { return $sql; } +sub DROP { my $self = shift; } +sub DISCONNECT { my $self = shift; } + + +#---------------------------------------------------------------------- +# methods for initiating a search +#---------------------------------------------------------------------- sub BEST_INDEX { my ($self, $constraints, $order_by) = @_; - # print STDERR Dump [BEST_INDEX => { - # where => $constraints, - # order => $order_by, - # }]; - my $ix = 0; - - foreach my $constraint (@$constraints) { - # TMP HACK -- should put real values instead + foreach my $constraint (grep {$_->{usable}} @$constraints) { $constraint->{argvIndex} = $ix++; $constraint->{omit} = 0; } - # TMP HACK -- should put real values instead + # stupid default values -- subclasses should put real values instead my $outputs = { idxNum => 1, - idxStr => "foobar", + idxStr => "", orderByConsumed => 0, estimatedCost => 1.0, estimatedRows => undef, @@ -126,11 +110,13 @@ sub OPEN { my $class = ref $self; my $cursor_class = $class . "::Cursor"; - return $cursor_class->NEW($self, @_); } +#---------------------------------------------------------------------- +# methods for insert/delete/update +#---------------------------------------------------------------------- sub _SQLITE_UPDATE { my ($self, $old_rowid, $new_rowid, @values) = @_; @@ -164,22 +150,41 @@ sub UPDATE { die "UPDATE() should be redefined in subclass"; } +#---------------------------------------------------------------------- +# remaining methods of the sqlite API +#---------------------------------------------------------------------- sub BEGIN_TRANSACTION {return 0} sub SYNC_TRANSACTION {return 0} sub COMMIT_TRANSACTION {return 0} sub ROLLBACK_TRANSACTION {return 0} - sub SAVEPOINT {return 0} sub RELEASE {return 0} sub ROLLBACK_TO {return 0} +sub FIND_METHOD {return 0} +sub RENAME {return 0} -sub DESTROY { + +#---------------------------------------------------------------------- +# utility methods +#---------------------------------------------------------------------- + +sub dbh { my $self = shift; + return ${$self->{dbh_ref}}; } +sub sqlite_table_info { + my $self = shift; + + my $sql = "PRAGMA table_info($self->{vtab_name})"; + return $self->dbh->selectall_arrayref($sql, {Slice => {}}); +} + +#====================================================================== package DBD::SQLite::VirtualTable::Cursor; +#====================================================================== use strict; use warnings; @@ -190,42 +195,13 @@ sub NEW { bless $self, $class; } -sub FILTER { - my ($self, $idxNum, $idxStr, @values) = @_; - - return; -} - - -sub EOF { - my ($self) = @_; - - # stupid implementation, to be redefined in subclasses - return 1; -} - - -sub NEXT { - my ($self) = @_; -} - - -sub COLUMN { - my ($self, $idxCol) = @_; -} - -sub ROWID { - my ($self) = @_; - - # stupid implementation, to be redefined in subclasses - return 1; -} - - -sub CLOSE { - my ($self) = @_; -} +# 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 } 1; @@ -234,23 +210,572 @@ __END__ =head1 NAME -DBD::SQLite::VirtualTable -- Abstract parent class for implementing virtual tables +DBD::SQLite::VirtualTable -- SQLite virtual tables implemented in Perl =head1 SYNOPSIS - package My::Virtual::Table; - use parent 'DBD::SQLite::VirtualTable'; - - sub ... + # register the virtual table module within sqlite + $dbh->sqlite_create_module(mod_name => "DBD::SQLite::VirtualTable::Subclass"); + + # create a virtual table + $dbh->do("CREATE VIRTUAL TABLE vtbl USING mod_name(arg1, arg2, ...)") + + # use it as any regular table + my $sth = $dbh->prepare("SELECT * FROM vtbl WHERE ..."); + +B : VirtualTable subclasses or instances are not called +directly from Perl code; everything happens indirectly through SQL +statements within SQLite. + =head1 DESCRIPTION -TODO +This module is an abstract class for implementing SQLite virtual tables, +written in Perl. Such tables look like regular tables, and are accessed +through regular SQL instructions and regular L API; but the implementation +is done through hidden calls to a Perl class. +This is the same idea as Perl's L, but +at the SQLite level. -=head1 METHODS +The current abstract class cannot be used directly, so the +synopsis above is just to give a general idea. Concrete, usable +classes bundled with the present distribution are : -TODO +=over +=item * + +L : implements a virtual +column that exposes file contents. This is especially useful +in conjunction with a fulltext index; see L. + +=item * + +L : binds to a Perl array +within the Perl program. This can be used for simple import/export +operations, for debugging purposes, for joining data from different +sources, etc. + +=back + +Other Perl virtual tables may also be published separately on CPAN. + +The following chapters document the structure of the abstract class +and explain how to write new subclasses; this is meant for +B, not for end users. If you just need to use a +virtual table module, refer to that module's documentation. + + +=head1 ARCHITECTURE + +=head2 Classes + +A virtual table module for SQLite is implemented through a pair +of classes : + +=over + +=item * + +the B class implements methods for creating or connecting +a virtual table, for destroying it, for opening new searches, etc. + +=item * + +the B class implements methods for performing a specific +SQL statement + +=back + + +=head2 Methods + +Most methods in both classes are not called directly from Perl +code : instead, they are callbacks, called from the sqlite kernel. +Following common Perl conventions, such methods have names in +uppercase. + + +=head1 TABLE METHODS + +=head2 Class methods for registering the module + +=head3 CREATE_MODULE + + $class->CREATE_MODULE($sqlite_module_name); + +Called when the client code invokes + + $dbh->sqlite_create_module($sqlite_module_name => $class); + +The default implementation is empty. + + +=head3 DESTROY_MODULE + + $class->DESTROY_MODULE(); + +Called automatically when the database handle is disconnected. +The default implementation is empty. + + +=head2 Class methods for creating a vtable instance + + +=head3 CREATE + + $class->CREATE($dbh_ref, $module_name, $db_name, $vtab_name, @args); + +Called when sqlite receives a statement + + CREATE VIRTUAL TABLE $db_name.$vtab_name USING $module_name(@args) + +The default implementation just calls L. + +=head3 CONNECT + + $class->CONNECT($dbh_ref, $module_name, $db_name, $vtab_name, @args); + +Called when attempting to access a virtual table that had been created +during previous database connection. The creation arguments were stored +within the sqlite database and are passed again to the CONNECT method. + +The default implementation just calls L. + + +=head3 _PREPARE_SELF + + $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, + +The method parses C<@args> to differentiate between I +(strings of shape C<$key>=C<$value> or C<$key>=C<"$value">, stored in +C<< $self->{options} >>), and I (other C<@args>, stored in +C<< $self->{columns} >>). It creates a hashref with the following fields : + +=over + +=item C + +a weak reference to the C<$dbh> database handle (see +L for an explanation of weak references). + +=item C + +name of the module as declared to sqlite (not to be confounded +with the Perl class name). + +=item C + +name of the database (usuallly C<'main'> or C<'temp'>), but it +may also be an attached database + +=item C + +name of the virtual table + +=item C + +arrayref of column declarations + +=item C + +hashref of option declarations + +=back + +This method should not be redefined, since it performs +general work which is supposed to be useful for all subclasses. +Instead, subclasses may override the L method. + + +=head3 NEW + + $class->NEW($dbh_ref, $module_name, $db_name, $vtab_name, @args); + +Instantiates a virtual table. + + +=head2 Instance methods called from the sqlite kernel + + +=head3 DROP + +Called whenever a virtual table is destroyed from the +database through the C SQL instruction. + +Just after the C call, the Perl instance +will be destroyed (and will therefore automatically +call the C method if such a method is present). + +The default implementation for DROP is empty. + +B : this corresponds to the C method +in the SQLite documentation; here it was not named +C, to avoid any confusion with the standard +Perl method C for object destruction. + + +=head3 DISCONNECT + +Called for every virtual table just before the database handle +is disconnected. + +Just after the C call, the Perl instance +will be destroyed (and will therefore automatically +call the C method if such a method is present). + +The default implementation for DISCONNECT is empty. + +=head3 VTAB_TO_DECLARE + +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). + +The default implementation returns: + + CREATE TABLE $self->{vtab_name}(@{$self->{columns}}) + +=head3 BEST_INDEX + + my $index_info = $vtab->BEST_INDEX($constraints, $order_by) + +This is the most complex method to redefined in subclasses. +This method will be called at the beginning of a new query on the +virtual table; the job of the method is to assemble some information +that will be used + +=over + +=item a) + +by the sqlite kernel to decide about the best search strategy + +=item b) + +by the cursor L method to produce the desired subset +of rows from the virtual table. + +=back + +By calling this method, the SQLite core is saying to the virtual table +that it needs to access some subset of the rows in the virtual table +and it wants to know the most efficient way to do that access. The +C method replies with information that the SQLite core can +then use to conduct an efficient search of the virtual table. + +The method takes as input a list of C<$constraints> and a list +of C<$order_by> instructions. It returns a hashref of indexing +properties, described below; furthermore, the method also adds +supplementary information within the input C<$constraints>. +Detailed explanations are given in +L. + +=head4 Input constraints + +Elements of the C<$constraints> arrayref correspond to +specific clauses of the C part of the SQL query. +Each constraint is a hashref with keys : + +=over + +=item C+ +the integer index of the column on the left-hand side of the constraint + +=item C + +the comparison operator, expressed as string containing +C<< '=' >>, C<< '>' >>, C<< '>=' >>, C<< '<' >>, C<< '<=' >> or C<< 'MATCH' >>. + +=item C + +a boolean indicating if that constraint is usable; some constraints +might not be usable because of the way tables are ordered in a join. + +=back + +The C<$constraints> arrayref is used both for input and for output. +While iterating over the array, the method should +add the following keys into usable constraints : + +=over + +=item C + +An index into the C<@values> array that will be passed to +the cursor's L method. In other words, if the current +constraint corresponds to the SQL fragment C, +and the corresponding C takes value 5, this means that +the C method will receive C<123> in C<$values[5]>. + +=item C + +A boolean telling to the sqlite core that it can safely omit +to double check that constraint before returning the resultset +to the calling program; this means that the FILTER method has fulfilled +the filtering job on that constraint and there is no need to do any +further checking. + +=back + +The C method will not necessarily receive all constraints +from the SQL C clause : for example a constraint like +C<< col1 < col2 + col3 >> cannot be handled at this level. +Furthemore, the C might decide to ignore some of the +received constraints. This is why a second pass over the results +will be performed by the sqlite core. + + +=head4 "order_by" input information + +The C<$order_by> arrayref corresponds to the C clauses +in the SQL query. Each entry is a hashref with keys : + +=over + +=item C+ +the integer index of the column being ordered + +=item C + +a boolean telling of the ordering is DESCending or ascending + +=back + +This information could be used by some subclasses for +optimizing the query strategfy; but usually the sqlite core will +perform another sorting pass once all results are gathered. + +=head4 Hashref information returned by BEST_INDEX + +The method should return a hashref with the following keys : + +=over + +=item C + +An arbitrary integer associated with that index; this information will +be passed back to L. + +=item C + +An arbitrary str associated with that index; this information will +be passed back to L. + +=item C + +A boolean telling the sqlite core if the C<$order_by> information +has been taken into account or not. + +=item C + +A float that should be set to the estimated number of disk access +operations required to execute this query against the virtual +table. The SQLite core will often call BEST_INDEX multiple times with +different constraints, obtain multiple cost estimates, then choose the +query plan that gives the lowest estimate. + +=item C + +An integer giving the estimated number of rows returned by that query. + +=back + + + +=head3 OPEN + +Called to instanciate a new cursor. +The default implementation appends C<"::Cursor"> to the current +classname and calls C within that cursor class. + +=head3 _SQLITE_UPDATE + +This is the dispatch method implementing the C callback +for virtual tables. The default implementation applies the algorithm +described in L to decide +to call L, L or L; so there is no reason +to override this method in subclasses. + +=head3 INSERT + + my $rowid = $vtab->INSERT($new_rowid, @values); + +This method should be overridden in subclasses to implement +insertion of a new row into the virtual table. +The size of the C<@values> array corresponds to the +number of columns declared through L. +The C<$new_rowid> may be explicitly given, or it may be +C, in which case the method must compute a new id +and return it as the result of the method call. + +=head3 DELETE + + $vtab->INSERT($old_rowid); + +This method should be overridden in subclasses to implement +deletion of a row from the virtual table. + +=head3 UPDATE + + $vtab->UPDATE($old_rowid, $new_rowid, @values); + +This method should be overridden in subclasses to implement +a row update within the virtual table. Usually C<$old_rowid> is equal +to C<$new_rowid>, which is a regular update; however, the rowid +could be changed from a SQL statement such as + + UPDATE table SET rowid=rowid+1 WHERE ...; + +=head3 BEGIN_TRANSACTION + +Called to begin a transaction on the virtual table. + +=head3 SYNC_TRANSACTION + +Called to signal the start of a two-phase commit on the virtual table. + +=head3 SYNC_TRANSACTION + +Called to commit a virtual table transaction. + +=head3 ROLLBACK_TRANSACTION + +Called to rollback a virtual table transaction. + +=head3 RENAME + + $vtab->RENAME($new_name) + +Called to rename a virtual table. + +=head3 SAVEPOINT + + $vtab->SAVEPOINT($savepoint) + +Called to signal the virtual table to save its current state +at savepoint C<$savepoint> (an integer). + +=head3 ROLLBACK_TO + + $vtab->ROLLBACK_TO($savepoint) + +Called to signal the virtual table to return to the state +C<$savepoint>. This will invalidate all savepoints with values +greater than C<$savepoint>. + +=head3 RELEASE + + $vtab->RELEASE($savepoint) + +Called to invalidate all savepoints with values +greater or equal to C<$savepoint>. + + +=head2 Utility instance methods + +Methods in this section are in lower case, because they +are not called directly from the sqlite kernel; these +are utility methods to be called from other methods +described above. + +=head3 dbh + +This method returns the database handle (C<$dbh>) associated with +the current virtual table. + + +=head1 CURSOR METHODS + +=head2 Class methods + +=head3 NEW + + my $cursor = $cursor_class->NEW($vtable, @args) + +Instanciates a new cursor. +The default implementation just returns a blessed hashref +with keys C and C. + +=head2 Instance methods + +=head3 FILTER + + $cursor->FILTER($idxNum, $idxStr, @values); + +This method begins a search of a virtual table. + +The C<$idxNum> and C<$idxStr> arguments correspond to values returned +by L for the chosen index. The specific meanings of +those values are unimportant to SQLite, as long as C and +C agree on what that meaning is. + +The C method may have requested the values of certain +expressions using the C values of the +C<$constraints> list. Those values are passed to C through +the C<@values> array. + +If the virtual table contains one or more rows that match the search +criteria, then the cursor must be left point at the first +row. Subsequent calls to L must return false. If there are +no rows match, then the cursor must be left in a state that will cause +L to return true. The SQLite engine will use the +L and L methods to access that row content. The L +method will be used to advance to the next row. + + +=head3 EOF + +This method must return false if the cursor currently points to a +valid row of data, or true otherwise. This method is called by the SQL +engine immediately after each L and L invocation. + +=head3 NEXT + +This method advances the cursor to the next row of a +result set initiated by L. If the cursor is already pointing at +the last row when this method is called, then the cursor no longer +points to valid data and a subsequent call to the L method must +return true. If the cursor is successfully advanced to +another row of content, then subsequent calls to L must return +false. + +=head3 COLUMN + + my $value = $cursor->COLUMN($idxCol); + +The SQLite core invokes this method in order to find the value for the +N-th column of the current row. N is zero-based so the first column is +numbered 0. + +=head3 ROWID + + my $value = $cursor->ROWID; + +Returns the I of row that the cursor is currently pointing at. + + +=head1 SEE ALSO + +L is another module for virtual tables written +in Perl, but designed for the reverse use case : instead of starting a +Perl program, and embedding the SQLite library into it, the intended +use is to start an sqlite program, and embed the Perl interpreter +into it. + +=head1 AUTHOR + +Laurent Dami Edami@cpan.orgE =head1 COPYRIGHT AND LICENSE diff --git a/lib/DBD/SQLite/VirtualTable/FileContent.pm b/lib/DBD/SQLite/VirtualTable/FileContent.pm index 7e4e3a2..d01fce8 100644 --- a/lib/DBD/SQLite/VirtualTable/FileContent.pm +++ b/lib/DBD/SQLite/VirtualTable/FileContent.pm @@ -1,92 +1,104 @@ +#====================================================================== package DBD::SQLite::VirtualTable::FileContent; +#====================================================================== use strict; use warnings; use base 'DBD::SQLite::VirtualTable'; +use List::MoreUtils qw/none/; + +my %option_ok = map {($_ => 1)} qw/source content_col path_col + expose root get_content/; + +my %defaults = ( + content_col => "content", + path_col => "path", + expose => "*", + get_content => "DBD::SQLite::VirtualTable::FileContent::get_content", +); -=head1 NAME +#---------------------------------------------------------------------- +# object instanciation +#---------------------------------------------------------------------- -DBD::SQLite::VirtualTable::FileContent -- virtual table for viewing file contents +sub NEW { + my $class = shift; + my $self = $class->_PREPARE_SELF(@_); -=head1 SYNOPSIS + local $" = ", "; # for array interpolation in strings - -- $dbh->sqlite_create_module(filesys => "DBD::SQLite::VirtualTable::FileContent"); + # initial parameter check + !@{$self->{columns}} + or die "${class}->NEW(): illegal options: @{$self->{columns}}"; + $self->{options}{source} + or die "${class}->NEW(): missing (source=...)"; + my @bad_options = grep {!$option_ok{$_}} keys %{$self->{options}}; + !@bad_options + or die "${class}->NEW(): bad options: @bad_options"; - CREATE VIRTUAL TABLE tbl USING filesys(file_content, - index_table = idx, - path_col = path, - expose = "path, col1, col2, col3", - root = "/foo/bar") - - - -- OR : expose = * - -=head1 DESCRIPTION - -A "FileContent" virtual table is like a database view on some underlying -I, which has a column containing paths to -files; the virtual table then adds a supplementary column which exposes -the content from those files. - -This is especially useful as an "external content" to some -fulltext table (see L) : the index -table stores some metadata about files, and then the fulltext engine -can index both the metadata and the file contents. - -=head1 METHODS - -=head2 new - - -=cut - - -sub initialize { - my $self = shift; - - # verifications - @{$self->{columns}} == 1 - or die "FileContent virtual table should declare exactly 1 content column"; - for my $opt (qw/index_table path_col/) { - $self->{options}{$opt} - or die "FileContent virtual table: option '$opt' is missing"; + # defaults ... tempted to use //= but we still want to support perl 5.8 :-( + foreach my $k (keys %defaults) { + defined $self->{options}{$k} + or $self->{options}{$k} = $defaults{$k}; } - # get list of columns from the index table - my $ix_table = $self->{options}{index_table}; - my $sql = "PRAGMA table_info($ix_table)"; - my $base_cols = $self->dbh->selectcol_arrayref($sql, {Columns => [2]}); - @$base_cols - or die "wrong index table: $ix_table"; + # get list of columns from the source table + my $src_table = $self->{options}{source}; + my $sql = "PRAGMA table_info($src_table)"; + my $dbh = ${$self->{dbh_ref}}; # can't use method ->dbh, not blessed yet + my $src_info = $dbh->selectall_arrayref($sql, {Slice => [1, 2]}); + @$src_info + or die "${class}->NEW(source=$src_table): no such table in database"; + + # associate each source colname with its type info or " " (should eval true) + my %src_col = map { ($_->[0] => $_->[1] || " ") } @$src_info; + # check / complete the exposed columns - $self->{options}{expose} = "*" if not exists $self->{options}{expose}; my @exposed_cols; if ($self->{options}{expose} eq '*') { - @exposed_cols = @$base_cols; + @exposed_cols = map {$_->[0]} @$src_info; } else { - @exposed_cols = split /\s*,\s*/, ($self->{options}{expose} || ""); - my %is_ok_col = map {$_ => 1} @$base_cols; - my @bad_cols = grep {!$is_ok_col{$_}} @exposed_cols; - local $" = ", "; - die "table $ix_table has no column named @bad_cols" if @bad_cols; + @exposed_cols = split /\s*,\s*/, $self->{options}{expose}; + my @bad_cols = grep { !$src_col{$_} } @exposed_cols; + die "table $src_table has no column named @bad_cols" if @bad_cols; } - push @{$self->{columns}}, @exposed_cols; + none {$_ eq $self->{options}{content_col}} @exposed_cols + or die "$class: $self->{options}{content_col} cannot be both the " + . "content_col and an exposed col"; + + # build the list of columns for this table + $self->{columns} = [ "$self->{options}{content_col} TEXT", + map {"$_ $src_col{$_}"} @exposed_cols ]; + + # acquire a coderef to the get_content() implementation + no strict 'refs'; + $self->{get_content} = \ &{$self->{options}{get_content}}; + + bless $self, $class; +} + +sub _build_headers { + my $self = shift; + + my $cols = $self->sqlite_table_info; + + # headers : names of columns, without type information + $self->{headers} = [ map {$_->{name}} @$cols ]; } -sub _SQLITE_UPDATE { - my ($self, $old_rowid, $new_rowid, @values) = @_; - - die "readonly database"; -} - +#---------------------------------------------------------------------- +# method for initiating a search +#---------------------------------------------------------------------- sub BEST_INDEX { my ($self, $constraints, $order_by) = @_; + $self->_build_headers if !$self->{headers}; + my @conditions; my $ix = 0; foreach my $constraint (grep {$_->{usable}} @$constraints) { @@ -96,12 +108,14 @@ sub BEST_INDEX { next if $col == 0; # for other columns, build a fragment for SQL WHERE on the underlying table - my $colname = $col == -1 ? "rowid" : $self->{columns}[$col]; + my $colname = $col == -1 ? "rowid" : $self->{headers}[$col]; push @conditions, "$colname $constraint->{op} ?"; $constraint->{argvIndex} = $ix++; $constraint->{omit} = 1; # SQLite doesn't need to re-check the op } + # TODO : exploit $order_by to add ordering clauses within idxStr + my $outputs = { idxNum => 1, idxStr => join(" AND ", @conditions), @@ -113,8 +127,46 @@ sub BEST_INDEX { return $outputs; } + +#---------------------------------------------------------------------- +# method for preventing updates +#---------------------------------------------------------------------- + +sub _SQLITE_UPDATE { + my ($self, $old_rowid, $new_rowid, @values) = @_; + + die "attempt to update a readonly virtual table"; +} + + +#---------------------------------------------------------------------- +# file slurping function (not a method!) +#---------------------------------------------------------------------- + +sub get_content { + my ($path, $root) = @_; + + $path = "$root/$path" if $root; + + my $content = ""; + if (open my $fh, "<", $path) { + local $/; # slurp the whole file into a scalar + $content = <$fh>; + close $fh; + } + else { + warn "can't open $path"; + } + + return $content; +} + + + + +#====================================================================== package DBD::SQLite::VirtualTable::FileContent::Cursor; -use 5.010; +#====================================================================== use strict; use warnings; use base "DBD::SQLite::VirtualTable::Cursor"; @@ -127,10 +179,10 @@ sub FILTER { # build SQL local $" = ", "; - my @cols = @{$vtable->{columns}}; + my @cols = @{$vtable->{headers}}; $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}{index_table}"; + my $sql = "SELECT @cols FROM $vtable->{options}{source}"; $sql .= " WHERE $idxStr" if $idxStr; # request on the index table @@ -175,35 +227,107 @@ sub file_content { my $root = $self->{vtable}{options}{root}; my $path = $self->{row}[-1]; - $path = "$root/$path" if $root; - my $content = ""; - if (open my $fh, "<", $path) { - local $/; # slurp the whole file into a scalar - $content = <$fh>; - close $fh; - } - else { - warn "can't open $path"; - } - - return $content; + return $self->{vtable}{get_content}->($path, $root); } + 1; __END__ +=head1 NAME +DBD::SQLite::VirtualTable::FileContent -- virtual table for viewing file contents + + +=head1 SYNOPSIS + +Within Perl : + + $dbh->sqlite_create_module(fcontent => "DBD::SQLite::VirtualTable::FileContent"); + +Then, within SQL : + + CREATE VIRTUAL TABLE tbl USING fcontent( + source = src_table, + content_col = content, + path_col = path, + expose = "path, col1, col2, col3", -- or "*" + root = "/foo/bar" + get_content = Foo::Bar::read_from_file + ); + + SELECT col1, path, content FROM tbl WHERE ...; + +=head1 DESCRIPTION + +A "FileContent" virtual table is bound to some underlying I, which has a column containing paths to files. The virtual +table behaves like a database view on the source table, with an added +column which exposes the content from those files. + +This is especially useful as an "external content" to some +fulltext table (see L) : the index +table stores some metadata about files, and then the fulltext engine +can index both the metadata and the file contents. + +=head1 PARAMETERS + +Parameters for creating a C virtual table are +specified within the C statement, just +like regular column declarations, but with an '=' sign. +Authorized parameters are : + +=over + +=item C + +The name of the I. +This parameter is mandatory. All other parameters are optional. + +=item C + +The name of the virtual column exposing file contents. +The default is C. + +=item C + +The name of the column in C that contains paths to files. +The default is C. + +=item C + +A comma-separated list (within double quotes) of source column names +to be exposed by the virtual table. The default is C<"*">, which means +all source columns. + +=item C + +An optional root directory that will be prepended to the I column +when opening files. + +=item C + +Fully qualified name of a Perl function for reading file contents. +The default implementation just slurps the entire file into a string; +but this hook can point to more sophisticated implementations, like for +example a function that would remove html tags. The hooked function is +called like this : + + $file_content = $get_content->($path, $root); + +=back + +=head1 AUTHOR + +Laurent Dami Edami@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright Laurent Dami, 2014. -Parts of the code are borrowed from L, -copyright (C) 2006, 2009 by Qindel Formacion y Servicios, S. L. - This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. diff --git a/lib/DBD/SQLite/VirtualTable/PerlData.pm b/lib/DBD/SQLite/VirtualTable/PerlData.pm index cd30c6e..5e3f15c 100644 --- a/lib/DBD/SQLite/VirtualTable/PerlData.pm +++ b/lib/DBD/SQLite/VirtualTable/PerlData.pm @@ -1,40 +1,12 @@ +#====================================================================== package DBD::SQLite::VirtualTable::PerlData; +#====================================================================== use strict; use warnings; use base 'DBD::SQLite::VirtualTable'; use List::MoreUtils qw/mesh/; -=head1 NAME - -DBD::SQLite::VirtualTable::PerlData -- virtual table for connecting to perl data - - -=head1 SYNOPSIS - - -- $dbh->sqlite_create_module(perl => "DBD::SQLite::VirtualTable::PerlData"); - - CREATE VIRTUAL TABLE tbl USING perl(foo, bar, etc, - arrayrefs="some_global_variable") - - CREATE VIRTUAL TABLE tbl USING perl(foo, bar, etc, - hashrefs="some_global_variable") - - CREATE VIRTUAL TABLE tbl USING perl(single_col - colref="some_global_variable") - - -=head1 DESCRIPTION - - -=head1 METHODS - -=head2 new - -=cut - - - # private data for translating comparison operators from Sqlite to Perl my $TXT = 0; my $NUM = 1; @@ -48,10 +20,13 @@ my %SQLOP2PERLOP = ( 'MATCH' => [ '=~', '=~' ], ); +#---------------------------------------------------------------------- +# instanciation methods +#---------------------------------------------------------------------- -sub initialize { - my $self = shift; - my $class = ref $self; +sub NEW { + my $class = shift; + my $self = $class->_PREPARE_SELF(@_); # verifications my $n_cols = @{$self->{columns}}; @@ -68,36 +43,33 @@ sub initialize { no strict "refs"; defined ${$symbolic_ref} or die "$class: can't find global variable \$$symbolic_ref"; - $self->{rows} = \${$symbolic_ref}; + $self->{rows} = \ ${$symbolic_ref}; + + bless $self, $class; } - -sub initialize_bis { +sub _build_headers_optypes { my $self = shift; - # the code below cannot happen within initialize() because VTAB_TO_DECLARE() - # has not been called until the end of NEW(). So we do it here, which is - # called lazily at the first invocation if BEST_INDEX(). + my $cols = $self->sqlite_table_info; - # get names and types of columns after they have been parsed by sqlite - my $sth = $self->dbh->prepare("PRAGMA table_info($self->{vtab_name})"); - $sth->execute; + # headers : names of columns, without type information + $self->{headers} = [ map {$_->{name}} @$cols ]; - # build private data 'headers' and 'optypes' - while (my $row = $sth->fetch) { - my ($colname, $coltype) = @{$row}[1, 2]; - push @{$self->{headers}}, $colname; - - # apply algorithm from datatype3.html" for type affinity - push @{$self->{optypes}}, $coltype =~ /INT|REAL|FLOA|DOUB/i ? $NUM : $TXT; - } + # optypes : either $NUM or $TEXT for each column + # (applying algorithm from datatype3.html" for type affinity) + $self->{optypes} + = [ map {$_->{type} =~ /INT|REAL|FLOA|DOUB/i ? $NUM : $TXT} @$cols ]; } +#---------------------------------------------------------------------- +# method for initiating a search +#---------------------------------------------------------------------- sub BEST_INDEX { my ($self, $constraints, $order_by) = @_; - $self->initialize_bis if not exists $self->{headers}; + $self->_build_headers_optypes if !$self->{headers}; # for each constraint, build a Perl code fragment. Those will be gathered # in FILTER() for deciding which rows match the constraints. @@ -107,8 +79,8 @@ sub BEST_INDEX { my $col = $constraint->{col}; my ($member, $optype); - # build a Perl code fragment. Those will be gathered - # in FILTER() for deciding which rows match the constraints. + # build a Perl code fragment. Those fragments will be gathered + # and eval-ed in FILTER(), for deciding which rows match the constraints. if ($col == -1) { # constraint on rowid $member = '$i'; @@ -126,12 +98,12 @@ sub BEST_INDEX { my $quote = $op eq '=~' ? 'qr' : 'q'; push @conditions, "($member $op ${quote}{%s})"; - # info passed back to the sqlite kernel -- see vtab.html in sqlite doc + # info passed back to the SQLite core -- see vtab.html in sqlite doc $constraint->{argvIndex} = $ix++; $constraint->{omit} = 1; } - # further info for the sqlite kernel + # further info for the SQLite core my $outputs = { idxNum => 1, idxStr => (join(" && ", @conditions) || "1"), @@ -144,6 +116,10 @@ sub BEST_INDEX { } +#---------------------------------------------------------------------- +# methods for data update +#---------------------------------------------------------------------- + sub _build_new_row { my ($self, $values) = @_; @@ -191,10 +167,9 @@ sub UPDATE { } - - +#====================================================================== package DBD::SQLite::VirtualTable::PerlData::Cursor; -use 5.010; +#====================================================================== use strict; use warnings; use base "DBD::SQLite::VirtualTable::Cursor"; @@ -211,8 +186,6 @@ sub FILTER { # build a method coderef to fetch matching rows my $perl_code = sprintf "sub {my (\$self, \$i) = \@_; $idxStr}", @values; -# print STDERR "PERL $perl_code\n"; - $self->{is_wanted_row} = eval $perl_code or die "couldn't eval q{$perl_code} : $@"; @@ -263,24 +236,74 @@ __END__ =head1 NAME -DBD::SQLite::VirtualTable -- Abstract parent class for implementing virtual tables +DBD::SQLite::VirtualTable::PerlData -- virtual table hooked to Perl data =head1 SYNOPSIS - package My::Virtual::Table; - use parent 'DBD::SQLite::VirtualTable'; - - sub ... +Within Perl : + + $dbh->sqlite_create_module(perl => "DBD::SQLite::VirtualTable::PerlData"); + +Then, within SQL : + + + CREATE VIRTUAL TABLE atbl USING perl(foo, bar, etc, + arrayrefs="some::global::var::aref") + + CREATE VIRTUAL TABLE htbl USING perl(foo, bar, etc, + hashrefs="some::global::var::href") + + CREATE VIRTUAL TABLE ctbl USING perl(single_col + colref="some::global::var::ref") + + + SELECT foo, bar FROM atbl WHERE ...; + =head1 DESCRIPTION -TODO - -=head1 METHODS - -TODO +A C virtual table is a database view on some datastructure +within a Perl program. The data can be read or modified both from SQL +and from Perl. This is useful for simple import/export +operations, for debugging purposes, for joining data from different +sources, etc. +=head1 PARAMETERS + +Parameters for creating a C virtual table are specified +within the C statement, mixed with regular +column declarations, but with an '=' sign. + +The only authorized (and mandatory) parameter is the one that +specifies the Perl datastructure to which the virtual table is bound. +The Perl data must be given as a fully qualified name of a global variable; +it can be one of three different kinds : + +=over + +=item C + +arrayref that contains an arrayref for each row + +=item C + +arrayref that contains a hashref for each row + +=item C + +arrayref that contains a single scalar for each row +(obviously this is a single-column virtual table) + +=back + +=head1 USAGE + +[TODO] + +=head1 AUTHOR + +Laurent Dami Edami@cpan.orgE =head1 COPYRIGHT AND LICENSE diff --git a/t/virtual_table/00_base.t b/t/virtual_table/00_base.t index 6c8f7c1..89c20e1 100644 --- a/t/virtual_table/00_base.t +++ b/t/virtual_table/00_base.t @@ -29,6 +29,7 @@ is $rows->[0]{bar}, "auto_vivify:1", "bar column"; $sql = "SELECT * FROM foobar "; $rows = $dbh->selectall_arrayref($sql, {Slice => {}}); is scalar(@$rows), 5, "got 5 rows again"; + is_deeply([sort keys %{$rows->[0]}], [qw/bar foo/], "col list OK"); @@ -43,7 +44,7 @@ use warnings; use base 'DBD::SQLite::VirtualTable'; use YAML; -sub initialize { +sub INITIALIZE { my $self = shift; # stupid pragma call, just to check that the dbh is OK $self->dbh->do("PRAGMA application_id=999"); diff --git a/t/virtual_table/01_destroy.t b/t/virtual_table/01_destroy.t index 75e71c5..0e15205 100644 --- a/t/virtual_table/01_destroy.t +++ b/t/virtual_table/01_destroy.t @@ -9,24 +9,25 @@ use t::lib::Test qw/connect_ok/; use Test::More; use Test::NoWarnings; -plan tests => 23; +plan tests => 20; my $dbfile = "tmp.sqlite"; my $dbh = connect_ok( dbfile => $dbfile, RaiseError => 1, AutoCommit => 1 ); -ok !$DBD::SQLite::VirtualTable::T::INITIALIZE_COUNT, "no vtab initialized"; +ok !$DBD::SQLite::VirtualTable::T::CREATE_COUNT && + !$DBD::SQLite::VirtualTable::T::CONNECT_COUNT, "no vtab created"; # create 2 separate SQLite modules from the same Perl class $dbh->sqlite_create_module(vtab1 => "DBD::SQLite::VirtualTable::T"); $dbh->sqlite_create_module(vtab2 => "DBD::SQLite::VirtualTable::T"); -ok !$DBD::SQLite::VirtualTable::T::INITIALIZE_COUNT, "still no vtab"; +ok !$DBD::SQLite::VirtualTable::T::CREATE_COUNT && + !$DBD::SQLite::VirtualTable::T::CONNECT_COUNT, "still no vtab"; # create 2 virtual tables from module vtab1 ok $dbh->do("CREATE VIRTUAL TABLE foobar USING vtab1(foo, bar)"), "create foobar"; ok $dbh->do("CREATE VIRTUAL TABLE barfoo USING vtab1(foo, bar)"), "create barfoo"; is $DBD::SQLite::VirtualTable::T::CREATE_COUNT, 2, "2 vtab created"; ok !$DBD::SQLite::VirtualTable::T::CONNECT_COUNT, "no vtab connected"; -is $DBD::SQLite::VirtualTable::T::INITIALIZE_COUNT, 2, "2 vtab initialized"; # destructor is called when a vtable is dropped ok !$DBD::SQLite::VirtualTable::T::DESTROY_COUNT, "no vtab destroyed"; @@ -43,18 +44,15 @@ is $DBD::SQLite::VirtualTable::T::DESTROY_MODULE_COUNT, 2, "2 modules destroyed" # reconnect, check that we go through the CONNECT method undef $DBD::SQLite::VirtualTable::T::CREATE_COUNT; undef $DBD::SQLite::VirtualTable::T::CONNECT_COUNT; -undef $DBD::SQLite::VirtualTable::T::INITIALIZE_COUNT; $dbh = connect_ok( dbfile => $dbfile, RaiseError => 1, AutoCommit => 1 ); $dbh->sqlite_create_module(vtab1 => "DBD::SQLite::VirtualTable::T"); ok !$DBD::SQLite::VirtualTable::T::CREATE_COUNT, "no vtab created"; ok !$DBD::SQLite::VirtualTable::T::CONNECT_COUNT, "no vtab connected"; -ok !$DBD::SQLite::VirtualTable::T::INITIALIZE_COUNT, "no vtab initialized"; 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"; -is $DBD::SQLite::VirtualTable::T::INITIALIZE_COUNT, 1, "1 vtab initialized"; package DBD::SQLite::VirtualTable::T; @@ -62,7 +60,6 @@ use base 'DBD::SQLite::VirtualTable'; our $CREATE_COUNT; our $CONNECT_COUNT; -our $INITIALIZE_COUNT; our $DESTROY_COUNT; our $DESTROY_MODULE_COUNT; our $DROP_COUNT; @@ -70,7 +67,6 @@ our $DISCONNECT_COUNT; sub CREATE {$CREATE_COUNT++; return shift->SUPER::CREATE(@_)} sub CONNECT {$CONNECT_COUNT++; return shift->SUPER::CONNECT(@_)} -sub initialize {$INITIALIZE_COUNT++} sub DROP {$DROP_COUNT++} sub DISCONNECT {$DISCONNECT_COUNT++} sub DESTROY {$DESTROY_COUNT++} diff --git a/t/virtual_table/10_filecontent.t b/t/virtual_table/10_filecontent.t index 603d112..4b9fae7 100644 --- a/t/virtual_table/10_filecontent.t +++ b/t/virtual_table/10_filecontent.t @@ -34,11 +34,9 @@ ok $dbh->sqlite_create_module(fs => "DBD::SQLite::VirtualTable::FileContent"), ok $dbh->do(<<""), "create vtable"; - CREATE VIRTUAL TABLE vfs USING fs(content, - index_table = base, - path_col = path, - expose = "path, foo, bar", - root = "$FindBin::Bin") + CREATE VIRTUAL TABLE vfs USING fs(source = base, + expose = "path, foo, bar", + root = "$FindBin::Bin") my $sql = "SELECT content, bar, rowid FROM vfs WHERE foo='foo2'"; my $rows = $dbh->selectall_arrayref($sql, {Slice => {}}); diff --git a/t/virtual_table/11_filecontent_fulltext.t b/t/virtual_table/11_filecontent_fulltext.t index def5b7d..8b6c577 100644 --- a/t/virtual_table/11_filecontent_fulltext.t +++ b/t/virtual_table/11_filecontent_fulltext.t @@ -46,22 +46,20 @@ my @perl_files = grep {/\.(pl|pm|pod)$/} @files; # open database my $dbh = connect_ok( dbfile => $dbfile, RaiseError => 1, AutoCommit => 1 ); -# create index table +# create the source table and populate it $dbh->do("CREATE TABLE files (id INTEGER PRIMARY KEY, path TEXT)"); my $sth = $dbh->prepare("INSERT INTO files(path) VALUES (?)"); $sth->execute($_) foreach @perl_files; -# create vtab table +# create the virtual table $dbh->sqlite_create_module(fs => "DBD::SQLite::VirtualTable::FileContent"); $dbh->do(<<""); - CREATE VIRTUAL TABLE vfs USING fs(content, - index_table = files, - path_col = path, - expose = "path", - root = "$distrib_dir") + CREATE VIRTUAL TABLE vfs USING fs(source = files, + expose = "path", + root = "$distrib_dir") -# create fts table +# create the fulltext indexing table and populate it $dbh->do('CREATE VIRTUAL TABLE fts USING fts4(content="vfs")'); note "building fts index...."; $dbh->do("INSERT INTO fts(fts) VALUES ('rebuild')"); @@ -89,6 +87,7 @@ foreach my $test (@tests) { } # see if data was properly stored: disconnect, reconnect and test again +$dbh->disconnect; undef $dbh; $dbh = connect_ok( dbfile => $dbfile, RaiseError => 1, AutoCommit => 1 ); $dbh->sqlite_create_module(fs => "DBD::SQLite::VirtualTable::FileContent");