1
0
Fork 0
mirror of https://github.com/DBD-SQLite/DBD-SQLite synced 2025-06-07 14:19:10 -04:00

various code refactorings, completion of the doc

This commit is contained in:
Laurent Dami 2014-07-11 06:06:19 +02:00
parent d6a77c88ea
commit 9018a4683c
9 changed files with 973 additions and 294 deletions

View file

@ -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; i<pIdxInfo->nOrderBy; 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;
}

View file

@ -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<DBD::SQLite> also supports virtual tables implemented in Perl code:
see L<DBD::SQLite::VirtualTable>. This can have many interesting uses
C<DBD::SQLite> also supports virtual tables implemented in I<Perl code>:
see L<DBD::SQLite::VirtualTable> 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<DBD::SQLite::VirtualTable::FileContent> : implements a virtual
column that exposes content from files. This is especially useful
in conjuction with a fulltext index; see L<DBD::SQLite::Fulltext_search>.
column that exposes file contents. This is especially useful
in conjunction with a fulltext index; see L<DBD::SQLite::Fulltext_search>.
=item *
L<DBD::SQLite::VirtualTable::PerlData> : 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

View file

@ -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<Note> : 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<DBI> API; but the implementation
is done through hidden calls to a Perl class.
This is the same idea as Perl's L<tied variables|perltie>, 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<DBD::SQLite::VirtualTable::FileContent> : implements a virtual
column that exposes file contents. This is especially useful
in conjunction with a fulltext index; see L<DBD::SQLite::Fulltext_search>.
=item *
L<DBD::SQLite::VirtualTable::PerlData> : 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<module authors>, 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<table> class implements methods for creating or connecting
a virtual table, for destroying it, for opening new searches, etc.
=item *
the B<cursor> 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</NEW>.
=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</NEW>.
=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<CREATE VIRTUAL TABLE> statement; each subclass should decide
what to do with this information,
The method parses C<@args> to differentiate between I<options>
(strings of shape C<$key>=C<$value> or C<$key>=C<"$value">, stored in
C<< $self->{options} >>), and I<columns> (other C<@args>, stored in
C<< $self->{columns} >>). It creates a hashref with the following fields :
=over
=item C<dbh_ref>
a weak reference to the C<$dbh> database handle (see
L<Scalar::Util> for an explanation of weak references).
=item C<module_name>
name of the module as declared to sqlite (not to be confounded
with the Perl class name).
=item C<db_name>
name of the database (usuallly C<'main'> or C<'temp'>), but it
may also be an attached database
=item C<vtab_name>
name of the virtual table
=item C<columns>
arrayref of column declarations
=item C<options>
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</NEW> 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<DROP TABLE> SQL instruction.
Just after the C<DROP()> call, the Perl instance
will be destroyed (and will therefore automatically
call the C<DESTROY()> method if such a method is present).
The default implementation for DROP is empty.
B<Note> : this corresponds to the C<xDestroy> method
in the SQLite documentation; here it was not named
C<DESTROY>, to avoid any confusion with the standard
Perl method C<DESTROY> for object destruction.
=head3 DISCONNECT
Called for every virtual table just before the database handle
is disconnected.
Just after the C<DISCONNECT()> call, the Perl instance
will be destroyed (and will therefore automatically
call the C<DESTROY()> 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</CREATE> or L</CONNECT>,
to register the columns of the virtual table within the sqlite kernel.
The method should return a string containing a SQL C<CREATE TABLE> statement;
but only the column declaration parts will be considered (see
L<http://sqlite.org/c3ref/declare_vtab.html>).
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</FILTER> 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<BEST_INDEX> 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<http://sqlite.org/vtab.html#xbestindex>.
=head4 Input constraints
Elements of the C<$constraints> arrayref correspond to
specific clauses of the C<WHERE ...> part of the SQL query.
Each constraint is a hashref with keys :
=over
=item C<col>
the integer index of the column on the left-hand side of the constraint
=item C<op>
the comparison operator, expressed as string containing
C<< '=' >>, C<< '>' >>, C<< '>=' >>, C<< '<' >>, C<< '<=' >> or C<< 'MATCH' >>.
=item C<usable>
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<argvIndex>
An index into the C<@values> array that will be passed to
the cursor's L</FILTER> method. In other words, if the current
constraint corresponds to the SQL fragment C<WHERE ... AND foo < 123 ...>,
and the corresponding C<argvIndex> takes value 5, this means that
the C<FILTER> method will receive C<123> in C<$values[5]>.
=item C<omit>
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<BEST_INDEX> method will not necessarily receive all constraints
from the SQL C<WHERE> clause : for example a constraint like
C<< col1 < col2 + col3 >> cannot be handled at this level.
Furthemore, the C<BEST_INDEX> 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<ORDER BY> clauses
in the SQL query. Each entry is a hashref with keys :
=over
=item C<col>
the integer index of the column being ordered
=item C<desc>
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<idxNum>
An arbitrary integer associated with that index; this information will
be passed back to L</FILTER>.
=item C<idxStr>
An arbitrary str associated with that index; this information will
be passed back to L</FILTER>.
=item C<orderByConsumed>
A boolean telling the sqlite core if the C<$order_by> information
has been taken into account or not.
=item C<estimatedCost>
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<estimatedRows>
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<NEW()> within that cursor class.
=head3 _SQLITE_UPDATE
This is the dispatch method implementing the C<xUpdate()> callback
for virtual tables. The default implementation applies the algorithm
described in L<http://sqlite.org/vtab.html#xupdate> to decide
to call L</INSERT>, L</DELETE> or L</UPDATE>; 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</VTAB_TO_DECLARE>.
The C<$new_rowid> may be explicitly given, or it may be
C<undef>, 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<vtable> and C<args>.
=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</BEST_INDEX> for the chosen index. The specific meanings of
those values are unimportant to SQLite, as long as C<BEST_INDEX> and
C<FILTER> agree on what that meaning is.
The C<BEST_INDEX> method may have requested the values of certain
expressions using the C<argvIndex> values of the
C<$constraints> list. Those values are passed to C<FILTER> 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</EOF> must return false. If there are
no rows match, then the cursor must be left in a state that will cause
L</EOF> to return true. The SQLite engine will use the
L</COLUMN> and L</ROWID> methods to access that row content. The L</NEXT>
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</FILTER> and L</NEXT> invocation.
=head3 NEXT
This method advances the cursor to the next row of a
result set initiated by L</FILTER>. 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</EOF> method must
return true. If the cursor is successfully advanced to
another row of content, then subsequent calls to L</EOF> 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<rowid> of row that the cursor is currently pointing at.
=head1 SEE ALSO
L<SQLite::VirtualTable> 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 E<lt>dami@cpan.orgE<gt>
=head1 COPYRIGHT AND LICENSE

View file

@ -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<index table>, 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<DBD::SQLite::Fulltext_search>) : 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<source
table>, 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<DBD::SQLite::Fulltext_search>) : 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<FileContent> virtual table are
specified within the C<CREATE VIRTUAL TABLE> statement, just
like regular column declarations, but with an '=' sign.
Authorized parameters are :
=over
=item C<source>
The name of the I<source table>.
This parameter is mandatory. All other parameters are optional.
=item C<content_col>
The name of the virtual column exposing file contents.
The default is C<content>.
=item C<path_col>
The name of the column in C<source> that contains paths to files.
The default is C<path>.
=item C<expose>
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<root>
An optional root directory that will be prepended to the I<path> column
when opening files.
=item C<get_content>
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 E<lt>dami@cpan.orgE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright Laurent Dami, 2014.
Parts of the code are borrowed from L<SQLite::VirtualTable>,
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.

View file

@ -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<PerlData> 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<PerlData> virtual table are specified
within the C<CREATE VIRTUAL TABLE> 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<arrayrefs>
arrayref that contains an arrayref for each row
=item C<hashrefs>
arrayref that contains a hashref for each row
=item C<colref>
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 E<lt>dami@cpan.orgE<gt>
=head1 COPYRIGHT AND LICENSE

View file

@ -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");

View file

@ -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++}

View file

@ -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 => {}});

View file

@ -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");