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   7568 use strict;
  5         14  
  5         168  
5 5     5   27 use warnings;
  5         12  
  5         171  
6 5     5   27 use base 'DBD::SQLite::VirtualTable';
  5         11  
  5         2997  
7 5     5   38 use DBD::SQLite;
  5         11  
  5         178  
8 5 50   5   29 use constant SQLITE_3010000 => $DBD::SQLite::sqlite_version_number >= 3010000 ? 1 : 0;
  5         10  
  5         483  
9 5 50   5   33 use constant SQLITE_3021000 => $DBD::SQLite::sqlite_version_number >= 3021000 ? 1 : 0;
  5         10  
  5         1291  
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         98 my $self = $class->_PREPARE_SELF(@_);
43              
44             # verifications
45 11         20 my $n_cols = @{$self->{columns}};
  11         26  
46 11 50       41 $n_cols > 0
47             or die "$class: no declared columns";
48 11 50 66     53 !$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     112 or die "$class: missing option 'arrayrefs' or 'hashrefs' or 'colref'";
54              
55             # bind to the Perl variable
56 5     5   44 no strict "refs";
  5         11  
  5         6156  
57 11 50       21 defined ${$symbolic_ref}
  11         59  
58             or die "$class: can't find global variable \$$symbolic_ref";
59 11         21 $self->{rows} = \ ${$symbolic_ref};
  11         55  
60              
61 11         77 bless $self, $class;
62             }
63              
64             sub _build_headers_optypes {
65 11     11   23 my $self = shift;
66              
67 11         59 my $cols = $self->sqlite_table_info;
68              
69             # headers : names of columns, without type information
70 11         1930 $self->{headers} = [ map {$_->{name}} @$cols ];
  29         106  
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       27 = [ map {$_->{type} =~ /INT|REAL|FLOA|DOUB/i ? $NUM : $TXT} @$cols ];
  29         177  
76             }
77              
78             #----------------------------------------------------------------------
79             # method for initiating a search
80             #----------------------------------------------------------------------
81              
82             sub BEST_INDEX {
83 67     67 1 283 my ($self, $constraints, $order_by) = @_;
84              
85 67 100       243 $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         124 my @conditions;
90 67         109 my $ix = 0;
91 67 100       149 foreach my $constraint (grep {$_->{usable} and exists $SQLOP2PERLOP{ $_->{op} } } @$constraints) {
  62         397  
92 54         116 my $col = $constraint->{col};
93 54         93 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       131 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         89 my $opts = $self->{options};
105             $member = $opts->{arrayrefs} ? "\$row->[$col]"
106             : $opts->{hashrefs} ? "\$row->{$self->{headers}[$col]}"
107 54 50       204 : $opts->{colref} ? "\$row"
    100          
    100          
108             : die "corrupted data in ->{options}";
109 54         120 $optype = $self->{optypes}[$col];
110             }
111 54         128 my $op = $SQLOP2PERLOP{$constraint->{op}}[$optype];
112 54 100       164 if (SQLITE_3021000 && $op =~ /defined/) {
    100          
113 8 100       26 if ($constraint->{op} =~ /NULL/) {
114 6         19 push @conditions,
115             "($op($member))";
116             } else {
117 2         9 push @conditions,
118             "($op($member) && !defined(\$vals[$ix]))";
119             }
120             } elsif (SQLITE_3010000 && $op =~ /str/) {
121 1         6 push @conditions,
122             "(defined($member) && defined(\$vals[$ix]) && !$op(\$vals[$ix], $member))";
123             } else {
124 45         165 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         140 $constraint->{argvIndex} = $ix++;
132 54         139 $constraint->{omit} = 1;
133             }
134              
135             # further info for the SQLite core
136 67   100     420 my $outputs = {
137             idxNum => 1,
138             idxStr => (join(" && ", @conditions) || "1"),
139             orderByConsumed => 0,
140             estimatedCost => 1.0,
141             estimatedRows => undef,
142             };
143              
144 67         1939 return $outputs;
145             }
146              
147              
148             #----------------------------------------------------------------------
149             # methods for data update
150             #----------------------------------------------------------------------
151              
152             sub _build_new_row {
153 5     5   10 my ($self, $values) = @_;
154              
155 5         8 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       21 : $opts->{colref} ? $values->[0]
    50          
    100          
160             : die "corrupted data in ->{options}";
161             }
162              
163             sub INSERT {
164 5     5 1 11 my ($self, $new_rowid, @values) = @_;
165              
166 5         11 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         6 return $#${$self->{rows}};
  5         37  
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   54 use strict;
  5         12  
  5         162  
204 5     5   59 use warnings;
  5         11  
  5         159  
205 5     5   27 use base "DBD::SQLite::VirtualTable::Cursor";
  5         10  
  5         2142  
206              
207              
208             sub row {
209 1815     1815   2692 my ($self, $i) = @_;
210 1815         2443 return ${$self->{vtable}{rows}}->[$i];
  1815         23002  
211             }
212              
213             sub FILTER {
214 69     69   220 my ($self, $idxNum, $idxStr, @vals) = @_;
215              
216             # build a method coderef to fetch matching rows
217 69         178 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   38 $self->{is_wanted_row} = do { no warnings; eval $perl_code }
  5         11  
  5         1826  
  69         121  
  69         7902  
224             or die "couldn't eval q{$perl_code} : $@";
225              
226             # position the cursor to the first matching row (or to eof)
227 69         165 $self->{row_ix} = -1;
228 69         193 $self->NEXT;
229             }
230              
231              
232             sub EOF {
233 1346     1346   2362 my ($self) = @_;
234              
235 1346         1840 return $self->{row_ix} > $#${$self->{vtable}{rows}};
  1346         5750  
236             }
237              
238             sub NEXT {
239 211     211   1898 my ($self) = @_;
240              
241             do {
242 1135         2760 $self->{row_ix} += 1
243             } until $self->EOF
244 211   100     317 || eval {$self->{is_wanted_row}->($self, $self->{row_ix})};
  1071         22523  
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   1435 my ($self, $idxCol) = @_;
256              
257 744         1287 my $row = $self->row($self->{row_ix});
258              
259 744         1200 my $opts = $self->{vtable}{options};
260             return $opts->{arrayrefs} ? $row->[$idxCol]
261             : $opts->{hashrefs} ? $row->{$self->{vtable}{headers}[$idxCol]}
262 744 50       3815 : $opts->{colref} ? $row
    100          
    100          
263             : die "corrupted data in ->{options}";
264             }
265              
266             sub ROWID {
267 8     8   16 my ($self) = @_;
268              
269 8         35 return $self->{row_ix} + 1; # rowids start at 1 in SQLite
270             }
271              
272              
273             1;
274              
275             __END__