File Coverage

blib/lib/DBD/SQLite/VirtualTable/PerlData.pm
Criterion Covered Total %
statement 106 126 84.1
branch 35 52 67.3
condition 9 11 81.8
subroutine 22 24 91.6
pod 4 4 100.0
total 176 217 81.1


line stmt bran cond sub pod time code
1             #======================================================================
2             package DBD::SQLite::VirtualTable::PerlData;
3             #======================================================================
4 5     5   8406 use strict;
  5         13  
  5         156  
5 5     5   30 use warnings;
  5         17  
  5         155  
6 5     5   29 use base 'DBD::SQLite::VirtualTable';
  5         7  
  5         2947  
7 5     5   39 use DBD::SQLite;
  5         11  
  5         163  
8 5 50   5   30 use constant SQLITE_3010000 => $DBD::SQLite::sqlite_version_number >= 3010000 ? 1 : 0;
  5         10  
  5         481  
9 5 50   5   32 use constant SQLITE_3021000 => $DBD::SQLite::sqlite_version_number >= 3021000 ? 1 : 0;
  5         11  
  5         1873  
10              
11             # private data for translating comparison operators from Sqlite to Perl
12             my $TXT = 0;
13             my $NUM = 1;
14             my %SQLOP2PERLOP = (
15             # TXT NUM
16             '=' => [ 'eq', '==' ],
17             '<' => [ 'lt', '<' ],
18             '<=' => [ 'le', '<=' ],
19             '>' => [ 'gt', '>' ],
20             '>=' => [ 'ge', '>=' ],
21             'MATCH' => [ '=~', '=~' ],
22             (SQLITE_3010000 ? (
23             'LIKE' => [ 'DBD::SQLite::strlike', 'DBD::SQLite::strlike' ],
24             'GLOB' => [ 'DBD::SQLite::strglob', 'DBD::SQLite::strglob' ],
25             'REGEXP'=> [ '=~', '=~' ],
26             ) : ()),
27             (SQLITE_3021000 ? (
28             'NE' => [ 'ne', '!=' ],
29             'ISNOT' => [ 'defined', 'defined' ],
30             'ISNOTNULL' => [ 'defined', 'defined' ],
31             'ISNULL' => [ '!defined', '!defined' ],
32             'IS' => [ '!defined', '!defined' ],
33             ) : ()),
34             );
35              
36             #----------------------------------------------------------------------
37             # instanciation methods
38             #----------------------------------------------------------------------
39              
40             sub NEW {
41 11     11 1 25 my $class = shift;
42 11         121 my $self = $class->_PREPARE_SELF(@_);
43              
44             # verifications
45 11         21 my $n_cols = @{$self->{columns}};
  11         28  
46 11 50       46 $n_cols > 0
47             or die "$class: no declared columns";
48 11 50 66     66 !$self->{options}{colref} || $n_cols == 1
49             or die "$class: must have exactly 1 column when using 'colref'";
50             my $symbolic_ref = $self->{options}{arrayrefs}
51             || $self->{options}{hashrefs}
52             || $self->{options}{colref}
53 11 50 66     95 or die "$class: missing option 'arrayrefs' or 'hashrefs' or 'colref'";
54              
55             # bind to the Perl variable
56 5     5   52 no strict "refs";
  5         10  
  5         5442  
57 11 50       19 defined ${$symbolic_ref}
  11         73  
58             or die "$class: can't find global variable \$$symbolic_ref";
59 11         26 $self->{rows} = \ ${$symbolic_ref};
  11         66  
60              
61 11         104 bless $self, $class;
62             }
63              
64             sub _build_headers_optypes {
65 11     11   25 my $self = shift;
66              
67 11         46 my $cols = $self->sqlite_table_info;
68              
69             # headers : names of columns, without type information
70 11         2042 $self->{headers} = [ map {$_->{name}} @$cols ];
  29         115  
71              
72             # optypes : either $NUM or $TEXT for each column
73             # (applying algorithm from datatype3.html" for type affinity)
74             $self->{optypes}
75 11 100       28 = [ map {$_->{type} =~ /INT|REAL|FLOA|DOUB/i ? $NUM : $TXT} @$cols ];
  29         173  
76             }
77              
78             #----------------------------------------------------------------------
79             # method for initiating a search
80             #----------------------------------------------------------------------
81              
82             sub BEST_INDEX {
83 67     67 1 237 my ($self, $constraints, $order_by) = @_;
84              
85 67 100       227 $self->_build_headers_optypes if !$self->{headers};
86              
87             # for each constraint, build a Perl code fragment. Those will be gathered
88             # in FILTER() for deciding which rows match the constraints.
89 67         115 my @conditions;
90 67         119 my $ix = 0;
91 67 100       177 foreach my $constraint (grep {$_->{usable} and exists $SQLOP2PERLOP{ $_->{op} } } @$constraints) {
  62         400  
92 54         108 my $col = $constraint->{col};
93 54         95 my ($member, $optype);
94              
95             # build a Perl code fragment. Those fragments will be gathered
96             # and eval-ed in FILTER(), for deciding which rows match the constraints.
97 54 50       122 if ($col == -1) {
98             # constraint on rowid
99 0         0 $member = '$i';
100 0         0 $optype = $NUM;
101             }
102             else {
103             # constraint on regular column
104 54         91 my $opts = $self->{options};
105             $member = $opts->{arrayrefs} ? "\$row->[$col]"
106             : $opts->{hashrefs} ? "\$row->{$self->{headers}[$col]}"
107 54 50       192 : $opts->{colref} ? "\$row"
    100          
    100          
108             : die "corrupted data in ->{options}";
109 54         111 $optype = $self->{optypes}[$col];
110             }
111 54         127 my $op = $SQLOP2PERLOP{$constraint->{op}}[$optype];
112 54 100       156 if (SQLITE_3021000 && $op =~ /defined/) {
    100          
113 8 100       22 if ($constraint->{op} =~ /NULL/) {
114 6         18 push @conditions,
115             "($op($member))";
116             } else {
117 2         8 push @conditions,
118             "($op($member) && !defined(\$vals[$ix]))";
119             }
120             } elsif (SQLITE_3010000 && $op =~ /str/) {
121 1         4 push @conditions,
122             "(defined($member) && defined(\$vals[$ix]) && !$op(\$vals[$ix], $member))";
123             } else {
124 45         157 push @conditions,
125             "(defined($member) && defined(\$vals[$ix]) && $member $op \$vals[$ix])";
126             }
127             # Note : $vals[$ix] refers to an array of values passed to the
128             # FILTER method (see below); so the eval-ed perl code will be a
129             # closure on those values
130             # info passed back to the SQLite core -- see vtab.html in sqlite doc
131 54         145 $constraint->{argvIndex} = $ix++;
132 54         139 $constraint->{omit} = 1;
133             }
134              
135             # further info for the SQLite core
136 67   100     424 my $outputs = {
137             idxNum => 1,
138             idxStr => (join(" && ", @conditions) || "1"),
139             orderByConsumed => 0,
140             estimatedCost => 1.0,
141             estimatedRows => undef,
142             };
143              
144 67         1900 return $outputs;
145             }
146              
147              
148             #----------------------------------------------------------------------
149             # methods for data update
150             #----------------------------------------------------------------------
151              
152             sub _build_new_row {
153 5     5   6 my ($self, $values) = @_;
154              
155 5         10 my $opts = $self->{options};
156             return $opts->{arrayrefs} ? $values
157 0         0 : $opts->{hashrefs} ? { map {$self->{headers}->[$_], $values->[$_]}
158 0         0 (0 .. @{$self->{headers}} - 1) }
159 5 50       20 : $opts->{colref} ? $values->[0]
    50          
    100          
160             : die "corrupted data in ->{options}";
161             }
162              
163             sub INSERT {
164 5     5 1 12 my ($self, $new_rowid, @values) = @_;
165              
166 5         12 my $new_row = $self->_build_new_row(\@values);
167              
168 5 50       11 if (defined $new_rowid) {
169 0 0       0 not ${$self->{rows}}->[$new_rowid]
  0         0  
170             or die "can't INSERT : rowid $new_rowid already in use";
171 0         0 ${$self->{rows}}->[$new_rowid] = $new_row;
  0         0  
172             }
173             else {
174 5         6 push @${$self->{rows}}, $new_row;
  5         15  
175 5         7 return $#${$self->{rows}};
  5         33  
176             }
177             }
178              
179             sub DELETE {
180 0     0   0 my ($self, $old_rowid) = @_;
181              
182 0         0 delete ${$self->{rows}}->[$old_rowid];
  0         0  
183             }
184              
185             sub UPDATE {
186 0     0 1 0 my ($self, $old_rowid, $new_rowid, @values) = @_;
187              
188 0         0 my $new_row = $self->_build_new_row(\@values);
189              
190 0 0       0 if ($new_rowid == $old_rowid) {
191 0         0 ${$self->{rows}}->[$old_rowid] = $new_row;
  0         0  
192             }
193             else {
194 0         0 delete ${$self->{rows}}->[$old_rowid];
  0         0  
195 0         0 ${$self->{rows}}->[$new_rowid] = $new_row;
  0         0  
196             }
197             }
198              
199              
200             #======================================================================
201             package DBD::SQLite::VirtualTable::PerlData::Cursor;
202             #======================================================================
203 5     5   46 use strict;
  5         12  
  5         127  
204 5     5   69 use warnings;
  5         14  
  5         173  
205 5     5   28 use base "DBD::SQLite::VirtualTable::Cursor";
  5         10  
  5         2178  
206              
207              
208             sub row {
209 1815     1815   2842 my ($self, $i) = @_;
210 1815         2445 return ${$self->{vtable}{rows}}->[$i];
  1815         22919  
211             }
212              
213             sub FILTER {
214 69     69   239 my ($self, $idxNum, $idxStr, @vals) = @_;
215              
216             # build a method coderef to fetch matching rows
217 69         203 my $perl_code = 'sub {my ($self, $i) = @_; my $row = $self->row($i); '
218             . $idxStr
219             . '}';
220              
221             # print STDERR "PERL CODE:\n", $perl_code, "\n";
222              
223 5 50   5   37 $self->{is_wanted_row} = do { no warnings; eval $perl_code }
  5         10  
  5         1838  
  69         106  
  69         8094  
224             or die "couldn't eval q{$perl_code} : $@";
225              
226             # position the cursor to the first matching row (or to eof)
227 69         176 $self->{row_ix} = -1;
228 69         184 $self->NEXT;
229             }
230              
231              
232             sub EOF {
233 1346     1346   2231 my ($self) = @_;
234              
235 1346         1921 return $self->{row_ix} > $#${$self->{vtable}{rows}};
  1346         5655  
236             }
237              
238             sub NEXT {
239 211     211   1805 my ($self) = @_;
240              
241             do {
242 1135         2774 $self->{row_ix} += 1
243             } until $self->EOF
244 211   100     307 || eval {$self->{is_wanted_row}->($self, $self->{row_ix})};
  1071         22431  
245              
246             # NOTE: the eval above is required for cases when user data, injected
247             # into Perl comparison operators, generates errors; for example
248             # WHERE col MATCH '(foo' will die because the regex is not well formed
249             # (no matching parenthesis). In such cases no row is selected and the
250             # query just returns an empty list.
251             }
252              
253              
254             sub COLUMN {
255 744     744   1668 my ($self, $idxCol) = @_;
256              
257 744         1373 my $row = $self->row($self->{row_ix});
258              
259 744         1245 my $opts = $self->{vtable}{options};
260             return $opts->{arrayrefs} ? $row->[$idxCol]
261             : $opts->{hashrefs} ? $row->{$self->{vtable}{headers}[$idxCol]}
262 744 50       4470 : $opts->{colref} ? $row
    100          
    100          
263             : die "corrupted data in ->{options}";
264             }
265              
266             sub ROWID {
267 8     8   18 my ($self) = @_;
268              
269 8         31 return $self->{row_ix} + 1; # rowids start at 1 in SQLite
270             }
271              
272              
273             1;
274              
275             __END__
276              
277             =head1 NAME
278              
279             DBD::SQLite::VirtualTable::PerlData -- virtual table hooked to Perl data
280              
281             =head1 SYNOPSIS
282              
283             Within Perl :
284              
285             $dbh->sqlite_create_module(perl => "DBD::SQLite::VirtualTable::PerlData");
286              
287             Then, within SQL :
288              
289              
290             CREATE VIRTUAL TABLE atbl USING perl(foo, bar, etc,
291             arrayrefs="some::global::var::aref")
292              
293             CREATE VIRTUAL TABLE htbl USING perl(foo, bar, etc,
294             hashrefs="some::global::var::href")
295              
296             CREATE VIRTUAL TABLE ctbl USING perl(single_col
297             colref="some::global::var::ref")
298              
299              
300             SELECT foo, bar FROM atbl WHERE ...;
301              
302              
303             =head1 DESCRIPTION
304              
305             A C<PerlData> virtual table is a database view on some datastructure
306             within a Perl program. The data can be read or modified both from SQL
307             and from Perl. This is useful for simple import/export
308             operations, for debugging purposes, for joining data from different
309             sources, etc.
310              
311              
312             =head1 PARAMETERS
313              
314             Parameters for creating a C<PerlData> virtual table are specified
315             within the C<CREATE VIRTUAL TABLE> statement, mixed with regular
316             column declarations, but with an '=' sign.
317              
318             The only authorized (and mandatory) parameter is the one that
319             specifies the Perl datastructure to which the virtual table is bound.
320             It must be given as the fully qualified name of a global variable;
321             the parameter can be one of three different kinds :
322              
323             =over
324              
325             =item C<arrayrefs>
326              
327             arrayref that contains an arrayref for each row.
328             Each such row will have a size equivalent to the number
329             of columns declared for the virtual table.
330              
331             =item C<hashrefs>
332              
333             arrayref that contains a hashref for each row.
334             Keys in each hashref should correspond to the
335             columns declared for the virtual table.
336              
337             =item C<colref>
338              
339             arrayref that contains a single scalar for each row;
340             obviously, this is a single-column virtual table.
341              
342             =back
343              
344             =head1 USAGE
345              
346             =head2 Common part of all examples : declaring the module
347              
348             In all examples below, the common part is that the Perl
349             program should connect to the database and then declare the
350             C<PerlData> virtual table module, like this
351              
352             # connect to the database
353             my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", '', '',
354             {RaiseError => 1, AutoCommit => 1});
355             # or any other options suitable to your needs
356            
357             # register the module
358             $dbh->sqlite_create_module(perl => "DBD::SQLite::VirtualTable::PerlData");
359              
360             Then create a global arrayref variable, using C<our> instead of C<my>,
361             so that the variable is stored in the symbol table of the enclosing module.
362              
363             package Foo::Bar; # could as well be just "main"
364             our $rows = [ ... ];
365              
366             Finally, create the virtual table and bind it to the global
367             variable (here we assume that C<@$rows> contains arrayrefs) :
368              
369             $dbh->do('CREATE VIRTUAL TABLE temp.vtab'
370             .' USING perl(col1 INT, col2 TEXT, etc,
371             arrayrefs="Foo::Bar::rows');
372              
373             In most cases, the virtual table will be for temporary use, which is
374             the reason why this example prepends C<temp.> in front of the table
375             name : this tells SQLite to cleanup that table when the database
376             handle will be disconnected, without the need to emit an explicit DROP
377             statement.
378              
379             Column names (and optionally their types) are specified in the
380             virtual table declaration, just like for any regular table.
381              
382             =head2 Arrayref example : statistics from files
383              
384             Let's suppose we want to perform some searches over a collection of
385             files, where search constraints may be based on some of the fields
386             returned by L<stat>, such as the size of the file or its last modify
387             time. Here is a way to do it with a virtual table :
388              
389             my @files = ... ; # list of files to inspect
390              
391             # apply the L<stat> function to each file
392             our $file_stats = [ map { [ $_, stat $_ ] } @files];
393              
394             # create a temporary virtual table
395             $dbh->do(<<"");
396             CREATE VIRTUAL TABLE temp.file_stats'
397             USING perl(path, dev, ino, mode, nlink, uid, gid, rdev, size,
398             atime, mtime, ctime, blksize, blocks,
399             arrayrefs="main::file_stats");
400              
401             # search files
402             my $sth = $dbh->prepare(<<"");
403             SELECT * FROM file_stats
404             WHERE mtime BETWEEN ? AND ?
405             AND uid IN (...)
406              
407             =head2 Hashref example : unicode characters
408              
409             Given any unicode character, the L<Unicode::UCD/charinfo> function
410             returns a hashref with various bits of information about that character.
411             So this can be exploited in a virtual table :
412              
413             use Unicode::UCD 'charinfo';
414             our $chars = [map {charinfo($_)} 0x300..0x400]; # arbitrary subrange
415              
416             # create a temporary virtual table
417             $dbh->do(<<"");
418             CREATE VIRTUAL TABLE charinfo USING perl(
419             code, name, block, script, category,
420             hashrefs="main::chars"
421             )
422              
423             # search characters
424             my $sth = $dbh->prepare(<<"");
425             SELECT * FROM charinfo
426             WHERE script='Greek'
427             AND name LIKE '%SIGMA%'
428              
429              
430             =head2 Colref example: SELECT WHERE ... IN ...
431              
432             I<Note: The idea for the following example is borrowed from the
433             C<test_intarray.h> file in SQLite's source
434             (L<http://www.sqlite.org/src>).>
435              
436             A C<colref> virtual table is designed to facilitate using an
437             array of values as the right-hand side of an IN operator. The
438             usual syntax for IN is to prepare a statement like this:
439              
440             SELECT * FROM table WHERE x IN (?,?,?,...,?);
441              
442             and then bind individual values to each of the ? slots; but this has
443             the disadvantage that the number of values must be known in
444             advance. Instead, we can store values in a Perl array, bind that array
445             to a virtual table, and then write a statement like this
446              
447             SELECT * FROM table WHERE x IN perl_array;
448              
449             Here is how such a program would look like :
450              
451             # connect to the database
452             my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", '', '',
453             {RaiseError => 1, AutoCommit => 1});
454            
455             # Declare a global arrayref containing the values. Here we assume
456             # they are taken from @ARGV, but any other datasource would do.
457             # Note the use of "our" instead of "my".
458             our $values = \@ARGV;
459            
460             # register the module and declare the virtual table
461             $dbh->sqlite_create_module(perl => "DBD::SQLite::VirtualTable::PerlData");
462             $dbh->do('CREATE VIRTUAL TABLE temp.intarray'
463             .' USING perl(i INT, colref="main::values');
464            
465             # now we can SELECT from another table, using the intarray as a constraint
466             my $sql = "SELECT * FROM some_table WHERE some_col IN intarray";
467             my $result = $dbh->selectall_arrayref($sql);
468              
469              
470             Beware that the virtual table is read-write, so the statement below
471             would push 99 into @ARGV !
472              
473             INSERT INTO intarray VALUES (99);
474              
475              
476              
477             =head1 AUTHOR
478              
479             Laurent Dami E<lt>dami@cpan.orgE<gt>
480              
481             =head1 COPYRIGHT AND LICENSE
482              
483             Copyright Laurent Dami, 2014.
484              
485             This library is free software; you can redistribute it and/or modify
486             it under the same terms as Perl itself.
487              
488             =cut