File Coverage

blib/lib/DBD/PgLite.pm
Criterion Covered Total %
statement 379 454 83.4
branch 125 228 54.8
condition 27 64 42.1
subroutine 59 82 71.9
pod 1 17 5.8
total 591 845 69.9


line stmt bran cond sub pod time code
1             package DBD::PgLite;
2              
3             ### DBI related package globals:
4             our $drh; # Driver handle
5             our $err = 0; # Holds error code for $DBI::err.
6             our $errstr = ''; # Holds error string for $DBI::errstr.
7             our $sqlstate = ''; # Holds SQL state for $DBI::state.
8             our $imp_data_size = 0; # required by DBI
9             our $VERSION = '0.11';
10              
11             ### Modules
12 1     1   1378 use strict;
  1         2  
  1         53  
13 1     1   3626 use DBD::SQLite;
  1         13616  
  1         72  
14              
15 1     1   1050 use Time::HiRes ();
  1         1925  
  1         33  
16 1     1   4158 use Time::Local;
  1         2636  
  1         179  
17 1     1   1292 use POSIX qw( LC_CTYPE LC_COLLATE );
  1         7136  
  1         22  
18             my $locale = $ENV{LC_COLLATE} || $ENV{LC_ALL} || $ENV{LANG} || $ENV{LC_CTYPE} || 'C';
19             POSIX::setlocale( LC_CTYPE, $locale );
20             POSIX::setlocale( LC_COLLATE, $locale );
21 1     1   1544 use locale;
  1         1  
  1         10  
22 1     1   2328 use Math::Trig ();
  1         31832  
  1         36  
23 1     1   1309 use Text::Iconv;
  1         4304  
  1         61  
24 1     1   895 use MIME::Base64 ();
  1         748  
  1         24  
25 1     1   7 use Digest::MD5 ();
  1         1  
  1         14503  
26              
27             # Instance variables, accessible through
28             # setTime, getTime, setTransaction, getTransaction,
29             {
30             my $Time;
31             my $Transaction;
32             my $Dbh;
33             my $Currval = {};
34             my $LastvalSeq;
35             sub Time {
36 162 100   162 1 462 $Time = shift if @_;
37 162   33     545 $Time ||= Time::HiRes::time;
38 162         393 return $Time;
39             }
40             sub Transaction {
41 161 50   161 0 436 $Transaction = shift if @_;
42 161 100       657 $Transaction = 0 unless defined $Transaction;
43 161         804 return $Transaction;
44             }
45             sub Dbh {
46 8 100   8 0 32 $Dbh = shift if @_;
47 8         17 return $Dbh;
48             }
49             sub Currval {
50 8     8 0 20 my ($sn,$set) = @_;
51 8         23 $sn = lc($sn);
52 8 100       37 $Currval->{$sn} = int($set) if $set;
53 8         23 return $Currval->{$sn};
54             }
55             sub Lastval {
56 7 100   7 0 57 $LastvalSeq = lc(shift) if @_;
57 7         135 return $Currval->{$LastvalSeq};
58             }
59             }
60 161     161 0 1048 sub setTime { Time(Time::HiRes::time); }
61 0     0 0 0 sub getTime { Time(); }
62 0     0 0 0 sub setTransaction { Transaction(shift); }
63 161     161 0 1116 sub getTransaction { Transaction(); }
64 1     1 0 4 sub setDbh { Dbh(shift); }
65 7     7 0 33 sub getDbh { Dbh(); }
66 3     3 0 11 sub getCurrval { Currval(shift); }
67 5     5 0 24 sub setCurrval { Currval(@_); }
68 2     2 0 8 sub getLastval { Lastval(); }
69 5     5 0 32 sub setLastval { Lastval(shift); }
70              
71             ### Main package methods/subs ######
72              
73             sub driver {
74 1 50   1 0 201 return $drh if ($drh);
75 1         21 my ($class, $attr) = @_;
76 1         3 $class .= "::dr";
77 1         8 ($drh) = DBI::_new_drh ($class, {
78             'Name' => 'PgLite',
79             'Version' => $VERSION,
80             'Attribution' => 'DBD::PgLite by Baldur Kristinsson',
81             });
82 1         52 return $drh;
83             }
84              
85 0     0 0 0 sub disconnect_all { } # required by DBI
86             sub DESTROY {
87 0     0   0 my $dbh = getDbh();
88 0 0       0 $dbh->disconnect if $dbh;
89             }
90              
91              
92             # Localeorder function legwork
93              
94             my (@chars,%chars);
95             for (1..254) {
96             push @chars, chr($_);
97             }
98             @chars = sort { lc($a) cmp lc($b) } @chars;
99             %chars = map { ($chars[$_] => sprintf("%x",$_)) } 0..$#chars;
100             my $localeorder_func = sub {
101             my $str = shift;
102             return join('', map { $chars{$_} } split //, $str);
103             };
104              
105             # Make sure sequence environment is sane and yield a
106             # database handle to sequence functions
107             sub _seq_init {
108 7     7   22 my $sn = lc(shift);
109 7         29 my $dbh = getDbh();
110             # Create sequence table if it does not exist
111 7         16 my $check_tbl = "select name from sqlite_master where name = ? and type = 'table'";
112 7 100       46 unless ($dbh->selectrow_array($check_tbl, {}, 'pglite_seq')) {
113 1         212 $dbh->do("create table pglite_seq (sequence_name text primary key, last_value int, is_locked int, is_called int)");
114             }
115 7         1918 my $check_seq = "select sequence_name from pglite_seq where sequence_name = ?";
116             # Autocreate sequence if it does not exist
117 7 100       58 unless ($dbh->selectrow_array($check_seq,{},$sn)) {
118 2         698 $dbh->do("insert into pglite_seq (sequence_name, last_value, is_locked, is_called) values (?,?,?,?)",
119             {}, $sn, 1, 1, 0);
120             # Find a matching table, if possible, and set last_value based on that
121 2         14 my $tn = $sn;
122 2         22 $tn =~ s/_seq$//;
123 2         9 my ($val,$col) = (0,'');
124 2   100     38 while (!$val && $tn=~/_+[a-z]*$/) {
125 4 100       1736 $col = ($col ? "${1}_$col" : $1) if $tn =~ s/_+([a-z]*)$//;
    50          
126 4 100       40 if ($dbh->selectrow_array($check_tbl, {}, $tn)) {
127 1         419 eval {
128 1         12 $val = $dbh->selectrow_array("select max($col) from $tn");
129             };
130             }
131             }
132 2 100       445 if (int($val) > 0) {
133 1         9 $dbh->do("update pglite_seq set last_value = ?, is_called = 1 where sequence_name = ?",
134             {}, int($val), $sn);
135             }
136             # unlock sequence before we continue
137 2         23 $dbh->do("update pglite_seq set is_locked = 0 where sequence_name = ?",{},$sn);
138             }
139 7         957 return $dbh;
140             }
141              
142              
143             # Advance the sequence object to its next value and return that
144             # value.
145             sub _nextval {
146 5     5   21 my $sn = lc(shift);
147 5         17 my $dbh = _seq_init($sn);
148 5         13 my $tries;
149 5         11 while (1) {
150 5         53 my $rc = $dbh->do("update pglite_seq set last_value = last_value + 1, is_locked = 1 where sequence_name = ? and is_locked = 0 and is_called = 1",{},$sn);
151 5 100 66     223 last if $rc && $rc > 0;
152 1         8 $rc = $dbh->do("update pglite_seq set is_locked = 1 where sequence_name = ? and is_locked = 0 and is_called = 0",{},$sn);
153 1 50 33     20 last if $rc && $rc > 0;
154 0         0 Time::HiRes::sleep(0.05);
155 0 0       0 die "Too many tries trying to update sequence '$sn' - need manual fix?" if ++$tries > 20;
156             }
157 5         85 my $sval = $dbh->selectrow_array("select last_value from pglite_seq where sequence_name = ?",{},$sn);
158 5         2402 $dbh->do("update pglite_seq set is_locked = 0, is_called = 1 where sequence_name = ? and is_locked = 1",{},$sn);
159 5         45 setLastval($sn);
160 5         27 setCurrval($sn,$sval);
161 5         134 return $sval;
162             }
163              
164             # Return the value most recently obtained by nextval for this sequence
165             # in the current session.
166             sub _currval {
167 3     3   11 my $sn = lc(shift);
168 3         9 my $val = getCurrval($sn);
169 3 50       11 die qq[ERROR: currval of sequence "$sn" is not yet defined in this session] unless $val;
170 3         188 return $val;
171             }
172              
173              
174             # Return the value most recently returned by nextval in the current
175             # session.
176             sub _lastval {
177 2     2   8 my $val = getLastval();
178 2 50       9 die qq[ERROR: lastval is not yet defined in this session] unless $val;
179 2         889 return $val;
180             }
181              
182              
183             # Reset the sequence object's counter value.
184             sub _setval {
185 2     2   5 my ($sn,$val,$called) = @_;
186 2         9 $sn = lc($sn);
187 2         5 $val = int($val);
188 2 50       7 die "ERROR: Value of sequence '$sn' must be a positive integer" unless $val;
189 2 50       7 $called = 1 unless defined($called);
190 2 50       5 $called = $called ? 1 : 0;
191 2         10 my $dbh = _seq_init($sn);
192 2         4 my $tries;
193 2         5 while (1) {
194 2         17 my $rc = $dbh->do("update pglite_seq set last_value = ?, is_called = ? where sequence_name = ? and is_locked = 0",
195             {}, $val, $called, $sn);
196 2 50 33     37 last if $rc && $rc > 0;
197 0         0 Time::HiRes::sleep(0.05);
198 0 0       0 die "Too many tries trying to update sequence '$sn' - need manual fix?" if ++$tries > 20;
199             }
200 2         54 return $val;
201             }
202              
203              
204             # Utility functions for succinct expression below
205              
206             sub _trim {
207 4     4   26 my ($mode,$str,$chars) = @_;
208 4   50     14 $mode ||= 'both';
209 4   100     15 $chars ||= " \n\t\r";
210 4         9 my ($left,$right);
211 4 100       24 $left = $mode =~ /both|leading|left/i ? 1 : 0;
212 4 100       21 $right = $mode =~ /both|trailing|right/i ? 1 : 0;
213 4         17 $chars = "[".quotemeta($chars)."]+";
214 4 100       84 $str =~ s/^$chars// if $left;
215 4 100       67 $str =~ s/$chars$// if $right;
216 4         61 return $str;
217             }
218              
219             my %_encode = ( 'base64' => sub { my $x = MIME::Base64::encode_base64(shift); chomp $x; return $x; },
220             'hex' => sub { unpack("H*",shift) },
221             'escape' => sub { $_[0]=~s/\0/\\000/g; return $_[0]; }, );
222             my %_decode = ( 'base64' => sub { MIME::Base64::decode_base64(shift) },
223             'hex' => sub { pack("H*",shift) },
224             'escape' => sub { $_[0]=~s/\\000/\0/g; return $_[0]; }, );
225              
226             sub _convert {
227 2     2   6 my ($txt,$from,$to) = @_;
228 2 50       7 return $txt unless $txt;
229 2 100       19 return $txt if $from eq $to;
230 1 50       302 my $c = Text::Iconv->new($from,$to) or die "No conversion possible: $from -> $to\n";
231 1 50       20 $txt = $c->convert($txt) or die "Could not convert $from -> $to";
232 1         23 return $txt;
233             }
234              
235             # Guess what Latin-1 is called in the iconv() implementation of this OS
236             sub _latin1_symbol {
237 2     2   584 my ($kernel) = POSIX::uname();
238 2 50       14 return '8859-1' if $kernel =~ /SunOS|Solaris/i;
239 2         14 return 'ISO-8859-1';
240             }
241              
242             sub _pad {
243 2     2   9 my ($mode,$str,$len,$fill) = @_;
244 2   50     13 $fill ||= ' ';
245 2 50       9 return substr($str,0,$len) if length($str)>=$len;
246 2 100       9 if ($mode eq 'left') {
247 1         3 my $addlen = $len - length($str);
248 1         4 $fill = $fill x $addlen;
249 1         4 $fill = substr($fill,0,$addlen);
250 1         2 $str = "$fill$str";
251             }
252             else {
253 1         5 while (length($str) < $len) {
254 2         6 $str .= $fill;
255             }
256             }
257 2         7 $str = substr($str,0,$len);
258 2         30 return $str;
259             }
260              
261             sub _to_ascii {
262 1     1   2 my ($str,$encoding) = @_;
263 1 50       5 $str = _convert($str,$encoding,_latin1_symbol()) if $encoding;
264 1         4 $str =~ tr[àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ]
265             [aaaaaaaceeeeiiiidnoooooouuuuytAAAAAAACEEEEIIIIDNOOOOOOUUUUYT];
266 1         12 return $str;
267             }
268              
269             sub _pgtime_to_time {
270 8     8   19 my $pgt = shift;
271 8 50       36 return $pgt if $pgt !~ /-/;
272 8         19 $pgt =~ s/\+\d+$//; # ignore timezone
273 8         23 my ($yr,$mon,$day, $hr,$min,$sec, $fraction) = (0,0,0, 0,0,0, 0);
274 8 100       84 if ($pgt=~/^(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)\.(\d+)$/) {
    50          
    0          
    0          
275 1         10 ($yr,$mon,$day,$hr,$min,$sec,$fraction) = ($1,$2,$3, $4,$5,$6, $7);
276             } elsif ($pgt=~/^(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)$/) {
277 7         40 ($yr,$mon,$day,$hr,$min,$sec) = ($1,$2,$3, $4,$5,$6);
278             } elsif ($pgt=~/^(\d+)-(\d+)-(\d+) (\d+):(\d+)$/) {
279 0         0 ($yr,$mon,$day,$hr,$min) = ($1,$2,$3, $4,$5);
280             } elsif ($pgt=~/^(\d+)-(\d+)-(\d+)$/) {
281 0         0 ($yr,$mon,$day) = ($1,$2,$3);
282             }
283 8 50       19 die "Invalid date/time format in '$pgt'" unless $yr;
284 8         50 my $t = timelocal($sec,$min,$hr,$day,$mon-1,$yr);
285 8 100       704 if ($fraction) {
286 1         3 $fraction = '0.'.$fraction;
287 1         4 $t += $fraction;
288             }
289 8         33 return $t;
290             }
291              
292             sub _to_char {
293 5     5   13 my ($time,$format) = @_;
294 5         16 my %h = _time_to_hash($time,'to_char');
295 5 50       79 my @hk = sort { length($b)<=>length($a) || $b cmp $a } keys %h;
  1000         1822  
296 5         25 for (@hk) {
297 235         2516 $format =~ s{$_}{$h{$_}}g;
298             }
299 5         13 $format =~ s/da[Ýý]/day/g;
300 5         9 $format =~ s/DAÝ/DAY/g;
301 5         182 return $format;
302             }
303              
304             sub _extract {
305 3     3   6 my ($field,$time) = @_;
306 3         9 my %h = _time_to_hash($time,'extract');
307 3         77 return $h{lc($field)};
308             }
309              
310             sub _date_trunc {
311 0     0   0 my ($field,$time) = @_;
312 0 0       0 $time = _pg_current('timestamp',0,$time) if $time =~ /^\d+$/;
313 0         0 $time =~ s/\+00$//;
314 0 0       0 if ($field eq 'second') {
    0          
    0          
    0          
    0          
    0          
315 0         0 $time =~ s/\.\d+$//;
316             } elsif ($field eq 'minute') {
317 0         0 $time =~ s/:\d\d\.\d+$/:00/;
318             } elsif ($field eq 'hour') {
319 0         0 $time =~ s/:\d\d:\d\d\.\d+$/:00:00/;
320             } elsif ($field eq 'day') {
321 0         0 $time =~ s/ \d\d:\d\d:\d\d\.\d+$/ 00:00:00/;
322             } elsif ($field eq 'month') {
323 0         0 $time =~ s/-\d\d \d\d:\d\d:\d\d\.\d+$/-01 00:00:00/;
324             } elsif ($field eq 'year') {
325 0         0 $time =~ s/-\d\d-\d\d \d\d:\d\d:\d\d\.\d+$/-01-01 00:00:00/;
326             } else {
327 0         0 die "Unknown or unimplemented field name: $field";
328             }
329             }
330              
331             my @month = qw(January February March April May June July August September October November December);
332             my @wday = qw(SundaÝ MondaÝ TuesdaÝ WednesdaÝ ThursdaÝ FridaÝ SaturdaÝ); # prevent recursion
333             my @roman = qw(i ii iii iv v vi vii viii ix x xi xii);
334             sub _time_to_hash {
335 8     8   14 my $t = shift;
336 8         11 my $type = shift;
337 8         213 my ($sec,$min,$hr,$day,$mon,$yr,$wday,$yday,$isdst) = localtime($t);
338 8 50       31 my $leap = $yr % 4 ? 0 : 1; # need only approximation because of restricted range (1970-2038)
339 8         145 my $ystart_dow = (localtime $t-$yday*60*60*24)[6];
340 8         33 my @iwn_offset = qw(1 7 6 5 4 3 2); # iso week number offset for dow
341 8         32 my @mdays = qw(31 28 31 30 31 30 31 31 30 31 30 31);
342 8         15 $mdays[1] += $leap;
343 8         10 my $ylen = 365+1;
344 8         14 my $fraction = $t;
345 8 100       43 $fraction =~ s/^\d+\./0\./; $fraction=0 if $fraction>=1;
  8         26  
346 8 50       17 my $ampm = $hr > 11 ? 'p.m.' : 'a.m.';
347 8 50       20 my $ampm_short = $hr > 11 ? 'pm' : 'am';
348 8         13 my %h;
349 8 100       19 if ($type eq 'extract') {
350             # extract: missing timezone*
351 3 50       61 %h = (
352             day => $day,
353             dow => $wday,
354             doy => $yday+1,
355             epoch => $t,
356             hour => $hr,
357             minute => $min,
358             month => $mon+1,
359             second => $sec + $fraction,
360             year => $yr+1900,
361             century=> substr($yr+1900,0,2),
362             decade => substr($yr+1900,0,3),
363             week => int(($yday+$iwn_offset[$ystart_dow])/7+.9),
364             quarter=> int(((($mon+1)/12)*4)+0.8),
365             microseconds=> ($fraction+$sec)*1_000_000,
366             milliseconds => ($fraction+$sec)*1_000,
367             millennium=> $yr>100 ? 3 : 2, # restricted range (1970-2038)
368             );
369             } else {
370             # to_char: missing: 'Y,YYY', IYYY,, IYY, IY, I, J, TZ, tz
371             # FM prefix supported for HH*, MM and DD but not otherwise.
372             # Other formatting prefixes not supported.
373 5 50       505 %h = (
    50          
    50          
    50          
374             HH => sprintf("%02d", $hr==12 ? $hr : $hr%12),
375             HH12 => sprintf("%02d", $hr==12 ? $hr : $hr%12),
376             HH24 => sprintf("%02d", $hr),
377             FMHH => sprintf("%d", $hr==12 ? $hr : $hr%12),
378             FMHH12 => sprintf("%d", $hr==12 ? $hr : $hr%12),
379             FMHH24 => sprintf("%d", $hr),
380             MI => sprintf("%02d", $min),
381             SS => sprintf("%02d", $sec),
382             MS => substr(sprintf("%.3f",$fraction),2,3),
383             US => substr(sprintf("%.6f",$fraction),2,6),
384             DD => sprintf("%02d", $day),
385             D => $wday+1,
386             DDD => $yday+1,
387             FMDD => sprintf("%d", $day),
388             MM => sprintf("%02d", $mon+1),
389             FMMM => sprintf("%d", $mon+1),
390             YYYY => sprintf("%d", $yr+1900),
391             YYY => sprintf("%03d", ($yr+1900)%1000),
392             YY => sprintf("%02d", ($yr+1900)%100),
393             Y => sprintf("%d", $yr%10),
394             am => $ampm_short,
395             'a.m.' => $ampm,
396             pm => $ampm_short,
397             'p.m.' => $ampm,
398             AM => uc($ampm_short),
399             'A.M.' => uc($ampm),
400             PM => uc($ampm_short),
401             'P.M.' => uc($ampm),
402             SSSS => $sec + 60*$min + 3600*$hr,
403             MONTH => sprintf("%-9s",uc($month[$mon])),
404             MON => uc(substr($month[$mon],0,3)),
405             month => sprintf("%-9s",lc($month[$mon])),
406             mon => lc(substr($month[$mon],0,3)),
407             Month => sprintf("%-9s",$month[$mon]),
408             Mon => substr($month[$mon],0,3),
409             DAY => sprintf("%-9s",uc($wday[$wday])),
410             DY => uc(substr($wday[$wday],0,3)),
411             day => sprintf("%-9s",lc($wday[$wday])),
412             dy => lc(substr($wday[$wday],0,3)),
413             Day => sprintf("%-9s",$wday[$wday]),
414             Dy => substr($wday[$wday],0,3),
415             RM => uc($roman[$mon]),
416             rm => $roman[$mon],
417             Q => int(((($mon+1)/12)*4)+0.8),
418             CC => substr($yr+2000,0,2),
419             WW => sprintf("%02d", int(($yday+1)/7+.9)),
420             IW => sprintf("%02d", int(($yday+$iwn_offset[$ystart_dow])/7+.9)),
421             );
422             }
423 8         222 return %h;
424             }
425              
426             sub _pg_current {
427 1     1   4 my ($mode,$with_tz,$now) = @_;
428 1         8 my %formats = ( date => "%04d-%02d-%02d",
429             timestamp => "%04d-%02d-%02d %02d:%02d:%02d.%06d",
430             time => "%02d:%02d:%02d.%06d" );
431 1 50       6 die "Unknown format '$mode'" unless $formats{$mode};
432 1   33     11 $now ||= Time();
433 1         35 my ($sec,$min,$hr,$day,$mon,$yr) = localtime($now);
434 1         272 my $fraction = $now;
435 1         26 $fraction =~ s/^\d+\.//;
436 1         10 $fraction .= '0' while length($fraction)<6;
437 1         2 my $cur;
438 1 50       5 if ($mode eq 'timestamp') {
    0          
    0          
439 1         11 $cur = sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", $yr+1900,$mon+1,$day, $hr,$min,$sec,$fraction);
440 1 50       4 $cur .= '+00' if $with_tz;
441             } elsif ($mode eq 'date') {
442 0         0 $cur = sprintf("%04d-%02d-%02d", $yr+1900,$mon+1,$day);
443             } elsif ($mode eq 'time') {
444 0         0 $cur = sprintf("%02d:%02d:%02d.%06d", $hr,$min,$sec,$fraction);
445 0 0       0 $cur .= '+00' if $with_tz;
446             }
447 1         13992 return $cur;
448             }
449              
450              
451             my %_interv_units = ( 'd' => 24*60*60,
452             'day' => 24*60*60,
453             'days' => 24*60*60,
454             'min' => 60,
455             'mins' => 60,
456             'minutes' => 60,
457             'm' => 60,
458             's' => 1,
459             'seconds' => 1,
460             'sec' => 1,
461             'secs' => 1,
462             'hours' => 60*60,
463             'hour' => 60*60,
464             'h' => 60*60,
465             'week' => 7*24*60*60,
466             'weeks' => 7*24*60*60,
467             'w' => 7*24*60*60,
468             'mon' => 30*24*60*60,
469             'month' => 30*24*60*60,
470             'months' => 30*24*60*60,
471             'y' => 365*24*60*60,
472             'yr' => 365*24*60*60,
473             'year' => 365*24*60*60,
474             'years' => 365*24*60*60,
475             );
476             sub _interval_to_seconds {
477 0     0   0 my $str = lc(shift);
478 0         0 my $sec = 0;
479 0 0       0 my ($hr,$min) = ($1,$2) if $str=~ s/(\d\d):(\d\d)//;
480 0 0       0 $sec += $hr * 60 * 60 if $hr;
481 0 0       0 $sec += $min * 60 if $min;
482 0 0       0 $sec += $1 if $str =~ s/:(\d\d)//;
483 0         0 for my $u (keys %_interv_units) {
484 0 0       0 if ($str =~ s{([\d\.]+)\s*$u}{}) {
485 0         0 $sec += $1 * $_interv_units{$u};
486             }
487             }
488 0         0 return $sec;
489             }
490              
491             # Strangely, Perl tries to interpolate @_ if you say '$x = sub { atan2(@_) }', etc.
492 1     1   83 sub _atan2 { my ($x,$y) = @_; return atan2($x,$y); }
  1         13  
493 1     1   105 sub _cos { my $x = shift; return cos($x); }
  1         36  
494 1     1   75 sub _sin { my $x = shift; return sin($x); }
  1         12  
495              
496             my @functions =
497             (
498              
499             # Additions because of regex operator filtering: matches() and friends
500             {
501             name => 'imatches_safe',
502             argnum => 2,
503             func => sub {
504             my ($col,$exp) = @_;
505             $exp =~ s/[\?\+\*]//g; # remove quantifiers
506             my $re;
507             eval { $re = qr/$exp/i; };
508             if ($@) {
509             eval { $re = qr/\Q$exp\E/i; };
510             }
511             return 1 if $col =~ $re;
512             }
513             },
514              
515             {
516             name => 'matches_safe',
517             argnum => 2,
518             func => sub {
519             my ($col,$exp) = @_;
520             $exp =~ s/[\?\+\*]//g; # remove quantifiers
521             my $re;
522             eval { $re = qr/$exp/; };
523             if ($@) {
524             eval { $re = qr/\Q$exp\E/; };
525             }
526             return 1 if $col =~ $re;
527             }
528             },
529              
530             {
531             name => 'matches',
532             argnum => 2,
533             func => sub {
534             my ($col,$exp) = @_;
535             my $re;
536             eval { $re = qr/$exp/; };
537             return 1 if $col =~ $re;
538             }
539             },
540              
541             {
542             name => 'imatches',
543             argnum => 2,
544             func => sub {
545             my ($col,$exp) = @_;
546             my $re;
547             eval { $re = qr/$exp/i; };
548             return 1 if $col =~ $re;
549             }
550             },
551            
552             # Interval calculation functions
553             {
554             name => 'add_interval',
555             argnum => 2,
556             func => sub { my $f = length($_[0])<=10 ? 'date' : 'timestamp'; _pg_current($f,0, _pgtime_to_time($_[0])+_interval_to_seconds($_[1])); }
557             },
558             {
559             name => 'subtract_interval',
560             argnum => 2,
561             func => sub { my $f = length($_[0])<=10 ? 'date' : 'timestamp'; _pg_current($f,0, _pgtime_to_time($_[0])-_interval_to_seconds($_[1])); }
562             },
563              
564             # Misc. utility functions, not based on Pg
565             {
566             name => 'lower_latin1',
567             argnum => 1,
568             func => sub {
569             my $str = shift;
570             $str =~ tr/A-ZÀ-Þ/a-zà-þ/;
571             return $str;
572             }
573             },
574              
575             {
576             name => 'localeorder',
577             argnum => 1,
578             func => $localeorder_func
579             },
580              
581             {
582             name => 'locale',
583             argnum => 0,
584             func => sub { return $locale },
585             },
586              
587              
588             # Mathemathical functions.
589             # http://www.postgresql.org/docs/current/static/functions-math.html
590             {
591             name => 'abs',
592             argnum => 1,
593             func => sub { abs(shift) }
594             },
595              
596             {
597             name => 'cbrt',
598             argnum => 1,
599             func => sub { (shift)**(1/3) }
600             },
601              
602             {
603             name => 'ceil',
604             argnum => 1,
605             func => sub { POSIX::ceil(shift) }
606             },
607              
608             {
609             name => 'degrees',
610             argnum => 1,
611             func => sub { Math::Trig::rad2deg(@_) }
612             },
613              
614             {
615             name => 'exp',
616             argnum => 1,
617             func => sub { exp(shift) }
618             },
619              
620             {
621             name => 'floor',
622             argnum => 1,
623             func => sub { POSIX::floor(shift) }
624             },
625              
626             {
627             name => 'ln',
628             argnum => 1,
629             func => sub { log(shift) }
630             },
631              
632             {
633             name => 'log',
634             argnum => 1,
635             func => sub { POSIX::log10(shift) }
636             },
637              
638             {
639             name => 'log',
640             argnum => 2,
641             func => sub { my ($base,$x)=@_; return log($x)/log($base); }
642             },
643              
644             {
645             name => 'mod',
646             argnum => 2,
647             func => sub { (shift)%(shift) }
648             },
649              
650             {
651             name => 'pi',
652             argnum => 0,
653             func => sub { Math::Trig::pi }
654             },
655              
656             {
657             name => 'exp',
658             argnum => 1,
659             func => sub { exp(shift) }
660             },
661              
662             {
663             name => 'pow',
664             argnum => 2,
665             func => sub { (shift)**(shift) }
666             },
667              
668             {
669             name => 'radians',
670             argnum => 1,
671             func => sub { Math::Trig::deg2rad(@_) }
672             },
673              
674             {
675             name => 'random', # NB! Overrides the builtin
676             argnum => 0,
677             func => sub { rand() }
678             },
679              
680             {
681             name => 'setseed',
682             argnum => 1,
683             func => sub { my $seed=shift; $seed*=2**31 if $seed<1 && $seed>-1; srand($seed); return int($seed); }
684             },
685              
686             {
687             name => 'sign',
688             argnum => 1,
689             func => sub { $_[0] < 0 ? -1 : $_[0] == 0 ? 0 : +1; }
690             },
691              
692             {
693             name => 'sqrt',
694             argnum => 1,
695             func => sub { sqrt(shift) }
696             },
697              
698             {
699             name => 'trunc',
700             argnum => 1,
701             func => sub { int(shift) }
702             },
703              
704             {
705             name => 'trunc',
706             argnum => 2,
707             func => sub { my ($n,$l)=@_; return int($n) if $l<=0; my $f="%.".($l+1)."f"; $n=sprintf($f,$n); $n=~s/\d$//; return $n; }
708             },
709              
710             {
711             name => 'acos',
712             argnum => 1,
713             func => sub { Math::Trig::acos(@_) }
714             },
715              
716             {
717             name => 'asin',
718             argnum => 1,
719             func => sub { Math::Trig::asin(@_) }
720             },
721              
722             {
723             name => 'atan',
724             argnum => 1,
725             func => sub { Math::Trig::atan(@_) }
726             },
727              
728             {
729             name => 'atan2',
730             argnum => 2,
731             func => \&_atan2,
732             },
733              
734             {
735             name => 'cos',
736             argnum => 1,
737             func => \&_cos,
738             },
739              
740             {
741             name => 'cot',
742             argnum => 1,
743             func => sub { Math::Trig::cot(@_) }
744             },
745              
746             {
747             name => 'sin',
748             argnum => 1,
749             func => \&_sin,
750             },
751              
752             {
753             name => 'tan',
754             argnum => 1,
755             func => sub { Math::Trig::tan(@_) }
756             },
757              
758             # String Functions
759             # http://www.postgresql.org/docs/current/static/functions-string.html
760             {
761             name => 'ascii',
762             argnum => 1,
763             func => sub { ord(substr(shift,0,1)) }
764             },
765            
766             {
767             name => 'bit_length',
768             argnum => 1,
769             func => sub { length(shift)*8 }
770             },
771            
772             {
773             name => 'btrim',
774             argnum => 2,
775             func => sub { _trim('both',@_) }
776             },
777            
778             {
779             name => 'char_length',
780             argnum => 1,
781             func => sub { length(shift) }
782             },
783            
784             {
785             name => 'character_length',
786             argnum => 1,
787             func => sub { length(shift) }
788             },
789            
790             {
791             name => 'chr',
792             argnum => 1,
793             func => sub { chr(shift) }
794             },
795            
796             {
797             name => 'convert',
798             argnum => 2,
799             func => sub { _convert(shift,_latin1_symbol(),uc(shift)) }
800             },
801              
802             {
803             name => 'convert',
804             argnum => 3,
805             func => sub { _convert(shift,uc(shift),uc(shift)) }
806             },
807            
808             {
809             name => 'decode',
810             argnum => 2,
811             func => sub { my($txt,$typ)=@_; return $_decode{lc($typ)}->($txt); }
812             },
813            
814             {
815             name => 'encode',
816             argnum => 2,
817             func => sub { my($txt,$typ)=@_; return $_encode{lc($typ)}->($txt); }
818             },
819            
820             {
821             name => 'initcap',
822             argnum => 1,
823             func => sub {
824             my $str=ucfirst(shift);
825             $str=~s[(\s\w)]{uc $1}gie; #}ge;
826             return $str;
827             }
828             },
829            
830             {
831             name => 'length',
832             argnum => 1,
833             func => sub { return length(shift) }
834             },
835            
836             {
837             name => 'lpad',
838             argnum => 2,
839             func => sub { _pad('left',@_,' ') }
840             },
841              
842             {
843             name => 'lpad',
844             argnum => 3,
845             func => sub { _pad('left',@_) }
846             },
847            
848             {
849             name => 'ltrim',
850             argnum => 2,
851             func => sub { _trim('left',@_) }
852             },
853            
854             {
855             name => 'md5', # new in Pg 7.4
856             argnum => 1,
857             func => sub { Digest::MD5::md5_hex(shift); } #))
858             },
859            
860             {
861             name => 'octet_length',
862             argnum => 1,
863             func => sub { length(shift) }
864             },
865            
866             {
867             name => 'position',
868             argnum => 2,
869             func => sub { my($part,$whole)=@_; return index($whole,$part)+1; }
870             },
871            
872             {
873             name => 'pg_client_encoding',
874             argnum => 0,
875             func => sub { return 'SQL_ASCII' }
876             },
877            
878             {
879             name => 'quote_ident',
880             argnum => 1,
881             func => sub { local($_)=shift; s/\"/\\\"/g; return qq!"$_"!; } #"
882             },
883            
884             {
885             name => 'quote_literal',
886             argnum => 1,
887             func => sub { local($_)=shift; s/\'/\'\'/g; s/\\/\\\\/g; return qq!'$_'!; }
888             },
889            
890             {
891             name => 'repeat',
892             argnum => 2,
893             func => sub { $_[0] x $_[1] }
894             },
895            
896             {
897             name => 'replace',
898             argnum => 3,
899             func => sub { $_[0] =~ s!\Q$_[1]\E!$_[2]!g; $_[0]; }
900             },
901              
902             {
903             name => 'rpad',
904             argnum => 2,
905             func => sub { _pad('right',@_,' ') }
906             },
907              
908             {
909             name => 'rpad',
910             argnum => 3,
911             func => sub { _pad('right',@_) }
912             },
913            
914             {
915             name => 'rtrim',
916             argnum => 2,
917             func => sub { _trim('right',@_) }
918             },
919            
920             {
921             name => 'split_part',
922             argnum => 3,
923             func => sub { my ($str,$delim,$i) = @_; $i||=1; return (split(/\Q$delim\E/,$str))[$i-1]; }
924             },
925            
926             {
927             name => 'strpos',
928             argnum => 2,
929             func => sub { index(shift,shift)+1 }
930             },
931            
932             {
933             name => 'substring',
934             argnum => 2,
935             func => sub { my ($str,$pat)=@_; return $1 if $str=~m{($pat)}; }
936             },
937             # NB: substring(string from pattern for escape) is NOT SUPPORTED
938             {
939             name => 'substring',
940             argnum => 3,
941             func => sub { substr($_[0],$_[1]-1,$_[2]); }
942             },
943            
944             {
945             name => 'to_ascii', # assumes latin1 input
946             argnum => 1,
947             func => sub { _to_ascii(@_) }
948             },
949              
950             {
951             name => 'to_ascii',
952             argnum => 2,
953             func => sub { _to_ascii(@_) }
954             },
955            
956             {
957             name => 'to_hex',
958             argnum => 1,
959             func => sub { sprintf("%x",shift) }
960             },
961            
962             {
963             name => 'translate',
964             argnum => 3,
965             func => sub { my ($str,$from,$to) = @_; s/\//\\\//g for ($from,$to); eval '$str =~ '."tr/$from/$to/"; return $str; }
966             },
967            
968             {
969             name => 'trim',
970             argnum => 1,
971             func => sub { _trim('both',shift) }
972             },
973              
974             {
975             name => 'trim',
976             argnum => 2,
977             func => sub { _trim('both',@_) },
978             },
979            
980             {
981             name => 'trim',
982             argnum => 3,
983             func => sub { _trim('both',@_) }
984             },
985              
986             # Data Type Formatting
987             # http://www.postgresql.org/docs/current/static/functions-formatting.html
988             {
989             name => 'to_char', # Limited support because of datatype issues
990             argnum => 2,
991             func => sub { _to_char(_pgtime_to_time(shift),shift) }
992             },
993            
994             {
995             name => 'to_date',
996             argnum => 2,
997             func => sub { die "TODO: to_date" }
998             },
999              
1000             {
1001             name => 'to_timestamp',
1002             argnum => 2,
1003             func => sub { die "TODO: to_timestamp" }
1004             },
1005              
1006             {
1007             name => 'to_number',
1008             argnum => 2,
1009             func => sub { die "TODO: to_number" }
1010             },
1011              
1012             # Date/Time Functions
1013             # http://www.postgresql.org/docs/current/static/functions-datetime.html
1014             # NB! need to handle datetime-calculations and datetime/date casting operators
1015             # NB! need filter for (t1,t2) OVERLAPS (t3,t4)
1016             {
1017             name => 'age',
1018             argnum => 1,
1019             func => sub { die "TODO: age" }
1020             },
1021            
1022             {
1023             name => 'age',
1024             argnum => 2,
1025             func => sub { die "TODO: age 2" }
1026             },
1027              
1028             {
1029             name => 'current_date',
1030             argnum => 0,
1031             func => sub { _pg_current('date',0) }
1032             },
1033              
1034             {
1035             name => 'current_time',
1036             argnum => 0,
1037             func => sub { _pg_current('time',0) }
1038             },
1039              
1040             {
1041             name => 'current_timestamp',
1042             argnum => 0,
1043             func => sub { _pg_current('timestamp',0) }
1044             },
1045              
1046             {
1047             name => 'date_part', # NB! works only for timestamp, not interval
1048             argnum => 2,
1049             func => sub { _extract($_[0],_pgtime_to_time($_[1])) }
1050             },
1051            
1052             {
1053             name => 'date_trunc', # NB! works only for timestamp, not interval
1054             argnum => 2,
1055             func => sub { _date_trunc(@_) }
1056             },
1057            
1058             {
1059             name => 'extract', # NB! works only for timestamp, not interval
1060             argnum => 2,
1061             func => sub { _extract($_[0],_pgtime_to_time($_[1])) }
1062             },
1063              
1064             {
1065             name => 'isfinite', # timestamp/interval
1066             argnum => 1,
1067             func => sub { die "TODO: isfinite" }
1068             },
1069            
1070             {
1071             name => 'localtime',
1072             argnum => 0,
1073             func => sub { _pg_current('time',0) }
1074             },
1075            
1076             {
1077             name => 'localtimestamp',
1078             argnum => 0,
1079             func => sub { _pg_current('timestamp',0) }
1080             },
1081              
1082             {
1083             name => 'now',
1084             argnum => 0,
1085             func => sub { _pg_current('timestamp',0) }
1086             },
1087            
1088             {
1089             name => 'timeofday',
1090             argnum => 0,
1091             func => sub { scalar localtime; }
1092             },
1093              
1094             # Sequence Manipulation Functions
1095             # http://www.postgresql.org/docs/current/static/functions-sequence.html
1096             {
1097             name => 'nextval',
1098             argnum => 1,
1099             func => sub { _nextval(@_) }
1100             },
1101             {
1102             name => 'currval',
1103             argnum => 1,
1104             func => sub { _currval(@_) }
1105             },
1106             {
1107             name => 'lastval',
1108             argnum => 0,
1109             func => sub { _lastval() }
1110             },
1111             {
1112             name => 'setval',
1113             argnum => 2,
1114             func => sub { _setval(@_) }
1115             },
1116             {
1117             name => 'setval',
1118             argnum => 3,
1119             func => sub { _setval(@_) }
1120             },
1121              
1122             # Misc Functions
1123             # http://www.postgresql.org/docs/current/static/functions-misc.html
1124             # Most of these are omitted.
1125             {
1126             name => 'current_user',
1127             argnum => 0,
1128             func => sub { (getpwuid $>)[0] }
1129             },
1130             {
1131             name => 'session_user',
1132             argnum => 0,
1133             func => sub { (getpwuid $>)[0] }
1134             },
1135             {
1136             name => 'user',
1137             argnum => 0,
1138             func => sub { (getpwuid $>)[0] }
1139             },
1140            
1141             );
1142              
1143             # Transforms a stored procedure into a coderef
1144             sub _sp_func {
1145 1     1   4 my $dbh = shift;
1146 1         4 my $name = shift;
1147 1         2 my $sql = shift;
1148             my $ret = sub {
1149 2     2   241 my @args = @_;
1150 2 50       8 die "No more than at most 9 arguments supported" if @args > 9;
1151 2 50       15 die "Non-SELECT statements not supported" unless $sql =~ /^\s*select\b/i;
1152 2         7 for (@args) {
1153 2 50       7 unless (defined $_) {
1154 0         0 $_ = 'NULL';
1155 0         0 next;
1156             }
1157 2 50       10 next if /^[\-\+]?\d+(?:\.\d+)$/;
1158 2         6 s/\'/\'\'/g;
1159 2         8 $_ = "'".$_."'";
1160             }
1161 2 100 66     22 if (@args && $sql =~ /\$\d/) {
1162 1         5 for my $i (1..9) { # supports only up to 9 args
1163 9         86 $sql =~ s/\$${i}/$args[$i-1]/g;
1164             }
1165             }
1166 2         14 my $res = $dbh->selectall_arrayref($sql);
1167 2 50 33     244 return undef unless $res && @$res;
1168 2 50       9 die "User-defined SQL function '$name' returns more than 1 row for values [ @_ ]" if @$res > 1;
1169 2         6 my $row = $res->[0];
1170 2 50       6 die "User-defined SQL function '$name' returns more than 1 column for values [ @_ ]" if @$row > 1;
1171 2         43 return $row->[0];
1172 1         10 };
1173 1         17 return $ret;
1174             }
1175              
1176             sub _register_builtin_functions {
1177 1     1   2 my $dbh = shift; # real sqlite handle
1178 1         3 for (@functions) {
1179 99         387 $dbh->func( $_->{name}, $_->{argnum}, $_->{func}, "create_function" );
1180             }
1181 1         9 $dbh->func( "avg", 1, 'DBD::PgLite::Aggregate::avg', "create_aggregate" );
1182             }
1183              
1184             sub _register_stored_functions {
1185 2     2   27 my $pglite_dbh = shift;
1186 2         25 my $real_dbh = $pglite_dbh->{D};
1187 2         32 my $check = $real_dbh->selectrow_array("select name from sqlite_master where type = 'table' and name = 'pglite_functions'");
1188 2 100       704 if ($check) {
1189 1         25 my $sproc = $real_dbh->selectall_arrayref("select name, argnum, type, sql from pglite_functions",{Columns=>{}});
1190 1         310 for my $sp (@$sproc) {
1191 1 50       7 if ($sp->{type} eq 'perl') {
1192 0         0 my $func = eval $sp->{sql};
1193 0 0       0 if ($@) {
1194 0         0 warn "WARNING: invalid stored perl function '$sp->{name}' - skipping ($@)\n";
1195             } else {
1196 0         0 $real_dbh->func( $sp->{name}, $sp->{argnum}, $func, "create_function" );
1197             }
1198             } else {
1199 1         7 $real_dbh->func( $sp->{name}, $sp->{argnum},
1200             _sp_func($pglite_dbh,$sp->{name},$sp->{sql}),
1201             "create_function" );
1202             }
1203             }
1204             }
1205             }
1206              
1207             ### driver methods ######
1208              
1209             package DBD::PgLite::dr;
1210             our $imp_data_size = 0; # strongly suggested by DBI
1211             sub connect {
1212 1     1   68 my ($drh, $dsn, $user, $auth, $attr) = @_;
1213 1         5 my %attr = (RaiseError=>1,PrintError=>0,AutoCommit=>1,FilterSQL=>1);
1214 1 50       4 if (ref $attr) {
1215 1         6 $attr{$_} = $attr->{$_} for keys %$attr;
1216             }
1217 1         3 my $use_filter = $attr{FilterSQL};
1218 1         2 delete $attr{FilterSQL};
1219 1 50       12 my $real_dbh = DBI->connect("dbi:SQLite:$dsn",$user,$auth,\%attr)
1220             or die "Could not connect with dbi::SQLite:$dsn\n";
1221 1         1786 DBD::PgLite::_register_builtin_functions($real_dbh);
1222 1         10 my $handle = DBI::_new_dbh ($drh, {
1223             'Name' => $attr{mbl_dsn},
1224             'User' => $user,
1225             'D' => $real_dbh,
1226             'Seq' => undef, # for sequence support
1227             'FilterSQL' => $use_filter,
1228             %$attr,
1229             });
1230 1         30 DBD::PgLite::_register_stored_functions($handle);
1231 1         4 DBD::PgLite::setDbh($handle);
1232 1         4 return $handle;
1233             }
1234 1 50 33 1   2215 sub disconnect_all { my $dbh = shift; $dbh->{D}->disconnect_all(@_) if $dbh && $dbh->{D}; } # required by DBI
  1         17  
1235 0 0 0 0   0 sub DESTROY { my $x=shift; $x->{D}->DESTROY(@_) if $x && $x->{D}; } # required by DBI ()
  0         0  
1236              
1237             ### database handle methods ######
1238              
1239             package DBD::PgLite::db;
1240             our $imp_data_size = 0; # strongly suggested by DBI
1241              
1242 4     4   57 sub STORE { my ($h,$k,$v) = @_; return $h->{D}->STORE($k,$v); }
  4         48  
1243 0 0   0   0 sub FETCH { my ($h,$k) = @_; return $h->{D} if $k eq 'D'; return $h->{D}->FETCH($k); }
  0         0  
  0         0  
1244              
1245             sub do {
1246 47     47   3361 my ($dbh,$sql,$attr,@bind) = @_;
1247 47   100     392 $attr ||= {};
1248 47         640 my $sth = $dbh->prepare($sql,$attr);
1249 47         2885172 return $sth->execute(@bind);
1250             }
1251 0     0   0 sub table_info { shift->{D}->table_info(@_); }
1252 0     0   0 sub column_info { shift->{D}->column_info(@_); }
1253 0     0   0 sub rows { shift->{D}->rows(@_); }
1254 0     0   0 sub quote { shift->{D}->quote(@_); }
1255 0     0   0 sub primary_key_info { shift->{D}->primary_key_info(@_); }
1256 0     0   0 sub primary_key { shift->{D}->primary_key(@_); }
1257 0     0   0 sub foreign_key_info { shift->{D}->foreign_key_info(@_); }
1258 0     0   0 sub get_info { shift->{D}->get_info(@_); }
1259 0     0   0 sub ping { shift->{D}->ping(@_); }
1260 0     0   0 sub begin_work { DBD::PgLite::setTransaction(1); DBD::PgLite::setTime(); shift->{D}->begin_work(@_); }
  0         0  
  0         0  
1261 0     0   0 sub commit { DBD::PgLite::setTransaction(0); DBD::PgLite::setTime(); shift->{D}->commit(@_); }
  0         0  
  0         0  
1262 0     0   0 sub rollback { DBD::PgLite::setTransaction(0); DBD::PgLite::setTime(); shift->{D}->rollback(@_); }
  0         0  
  0         0  
1263              
1264              
1265             sub prepare {
1266 47     47   173 my ($dbh,$statement,$attr) = @_;
1267 47         337 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1268 47         611 return $dbh->{D}->prepare($filtered,$attr);
1269             }
1270             sub selectrow_array {
1271 110     110   79829 my ($dbh,$statement,$attr,@bind) = @_;
1272 110         526 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1273 110         1367 return $dbh->{D}->selectrow_array($filtered,$attr,@bind);
1274             }
1275             sub selectrow_arrayref {
1276 0     0   0 my ($dbh,$statement,$attr,@bind) = @_;
1277 0         0 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1278 0         0 return $dbh->{D}->selectrow_arrayref($filtered,$attr,@bind);
1279             }
1280             sub selectrow_hashref {
1281 0     0   0 my ($dbh,$statement,$attr,@bind) = @_;
1282 0         0 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1283 0         0 return $dbh->{D}->selectrow_hashref($filtered,$attr,@bind);
1284             }
1285             sub selectall_arrayref {
1286 3     3   193 my ($dbh,$statement,$attr,@bind) = @_;
1287 3         10 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1288 3         34 return $dbh->{D}->selectall_arrayref($filtered,$attr,@bind);
1289             }
1290             sub selectall_hashref {
1291 1     1   5 my ($dbh,$statement,$kf,$attr,@bind) = @_;
1292 1         5 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1293 1         21 return $dbh->{D}->selectall_hashref($filtered,$kf,$attr,@bind);
1294             }
1295             sub selectcol_arrayref {
1296 0     0   0 my ($dbh,$statement,$attr,@bind) = @_;
1297 0         0 my $filtered = DBD::PgLite::Filter::filter_sql($dbh,$statement,$attr);
1298 0         0 return $dbh->{D}->selectcol_arrayref($filtered,$attr,@bind); #}
1299             }
1300              
1301             ### statement handle methods ######
1302              
1303             package DBD::PgLite::st;
1304             our $imp_data_size = 0; # strongly suggested by DBI
1305              
1306             # We should not need any methods in this package, as any statement
1307             # handles will already be blessed into DBD::SQLite::st.
1308              
1309             ### dbh method additions/overrides ######
1310              
1311             package DBD::PgLite::Filter;
1312              
1313             # Regexes used in filter_sql()
1314             my $end_re = qr/(?=[\s\,\)\:\|])|$/;
1315             my $col_re = qr/\b[\w+\.]+$end_re/; # column name, keyword or number
1316             my $qs_re = qr/(?:''|'(?:[^\']|'')+')$end_re/; # quoted string
1317             my $func_simple_re = qr/\b\w+\s*\(\s*(?:$col_re|$qs_re)?(?:\s*,\s*(?:$col_re|$qs_re))*\s*\)/; # simple function call
1318             my $func_complex_re = qr/\b\w+\s*\(\s*(?:$col_re|$qs_re|$func_simple_re)(?:\s*,\s*(?:$col_re|$qs_re|$func_simple_re))*\s*\)/; #complex function call
1319             my $chunk_re = qr/(?:$col_re|$qs_re|$func_simple_re|$func_complex_re)/;
1320             my $join_re = qr/\s+NATURAL\s+(?:LEFT\s+|RIGHT\s+|FULL\s+)?(?:OUTER\s+|INNER\s+|CROSS\s+)?JOIN\s+/i;
1321              
1322             #######################)!}}]];;;;!!!///'''''''''''""""""""""
1323              
1324             sub filter_sql {
1325 161     161   474 my ($dbh,$sql,$attr) = @_;
1326             # warn "[ UNFILTERED SQL:\n$sql\n]\n" if $ENV{PGLITEDEBUG}>1;
1327             # Prefilter SQL
1328 161 50 0     1118 $sql = ($dbh->{prefilter}->($sql) || $sql) if ref $dbh->{prefilter} eq 'CODE';
1329 161 100 33     593 $sql = ($attr->{prefilter}->($sql) || $sql) if ref $attr->{prefilter} eq 'CODE';
1330             # Fix time for transaction
1331 161 50       619 DBD::PgLite::setTime() unless DBD::PgLite::getTransaction();
1332             # Strip out all trailing ";" and make sure statement ends in space (don't ask!)
1333 161         1163 while ($sql =~ s/\s*\;\s*$//s) { next; }
  0         0  
1334 161         684 $sql .= " ";
1335             # NB! may not be healthy for non-SELECTs...
1336             # First determine whether filtering has been turned off
1337 161   50     496 $attr ||= {};
1338 161 50       428 if (exists $attr->{FilterSQL}) {
1339 0 0       0 return $sql unless $attr->{FilterSQL};
1340             }
1341 161 50       782 if ($dbh->{D}) {
1342 161 50 33     626 return $sql unless $dbh->{FilterSQL} || $attr->{FilterSQL};
1343 161         498 $dbh = $dbh->{D};
1344             }
1345 161         788 my %interval_func = (
1346             '+' => 'add_interval',
1347             '-' => 'subtract_interval'
1348             );
1349             # Protect quoted strings from the unsafe transformations below
1350 161 50       24088 $sql =~ s{($qs_re)}{$1 eq "''" ? "''" : "'".unpack("H*",$1)."'"}gie;
  105         1572  
1351 161         402 for ($sql) {
1352             # Booleans ('t' = 74 hex, 'f' = 66 hex, "'" = 27 hex)
1353             # (a) In conjunction with operators
1354 161         8563 $sql =~ s{($chunk_re)\s*=\s*(?:false|False|FALSE|\'276627\')}{NOT $1}g;
1355 161         23365 $sql =~ s{($chunk_re)\s*(?:\!=|<>)\s*(?:false|False|FALSE|\'276627\')}{$1}g;
1356 161         5284 $sql =~ s{($chunk_re)\s*=\s*(?:true|True|TRUE|\'277427\')}{$1}g;
1357 161         15452 $sql =~ s{($chunk_re)\s*(?:\!=|<>)\s*(?:true|True|TRUE|\'277427\')}{NOT $1}g;
1358             # (b) freestanding
1359 161         627 $sql =~ s{\'277427\'::bool(?:ean)?}{1}gi;
1360 161         383 $sql =~ s{\'276627\'::bool(?:ean)?}{0}gi;
1361 161         835 $sql =~ s{\bTRUE\b}{1}gi;
1362 161         2129 $sql =~ s{\bFALSE\b}{0}gi;
1363             # Time zone not supported
1364 161         588 s{\swith(?:out)?\s+time\s+zone}{}gi;
1365             # Casting to date supported as an alias for to_char...
1366 161         23795 s{($chunk_re)::date\b}{to_char($1,'YYYY-MM-DD')}gi;
1367             # Casting to integer supported as an alias for round
1368 161         34548 s{($chunk_re)::int(?:eger)?\b}{round($1)}gi;
1369             # ... but casting in general not supported
1370 161         613 s{\:\:\w+(?:\([\d\,]+\))?}{}gi;
1371             # Non-paren functions -- add parentheses
1372 161         454 for (qw[CURRENT_DATE CURRENT_TIMESTAMP CURRENT_TIME LOCALTIMESTAMP LOCALTIME
1373             CURRENT_USER SESSION_USER USER]) {
1374 1288         81723 $sql =~ s/([\s\,\(])($_)([\s\,])/$1$2()$3/gi;
1375             }
1376             # ILIKE => LIKE
1377 161         1017 s{\bI(LIKE)\b}{$1}gi;
1378             # extract(field from dtvalue)
1379 161         667 s{\b(EXTRACT\s*\(\s*)\'?(\w+)\'?\s+FROM\s+}{$1'$2',}gi;
1380             # trim(both 'x' from 'xAx'): reverse arguments
1381 161         2964 s{\bTRIM\s*\(\s*BOTH\s+($chunk_re)\s+FROM\s+($chunk_re)\s*\)}{BTRIM($2,$1)}gi;
1382 161         2980 s{\bTRIM\s*\(\s*LEADING\s+($chunk_re)\s+FROM\s+($chunk_re)\s*\)}{LTRIM($2,$1)}gi;
1383 161         4453 s{\bTRIM\s*\(\s*TRAILING\s+($chunk_re)\s+FROM\s+($chunk_re)\s*\)}{RTRIM($2,$1)}gi;
1384             # substring(string FROM int FOR int)
1385 161         3196 s{\b(SUBSTRING\s*\()\s*($chunk_re)\s+FROM\s+($chunk_re)\s+FOR\s+}{$1$2,$3,}gi;
1386             # substring(string FROM pattern)
1387 161         2237 s{\b(SUBSTRING\s*\()\s*($chunk_re)\s+FROM\s+}{$1$2,}gi;
1388             # position('x' IN 'y')
1389 161         1665 s{(POSITION\s*\()\s*($chunk_re)\s+IN\s+}{$1$2,}gi;
1390             # convert(x USING conversion_name)
1391 161         1563 s{(CONVERT\s*\()\s*($chunk_re)\s+USING\s+}{$1$2,}gi;
1392             # Regex operator filters
1393 161         3380 s{($chunk_re\s+)~(\s+$chunk_re)}{MATCHES($1,$2)}g;
1394 161         2306 s{($chunk_re\s+)~\*(\s+$chunk_re)}{IMATCHES($1,$2)}g;
1395 161         2653 s{($chunk_re\s+)\!~(\s+$chunk_re)}{NOT MATCHES($1,$2)}g; #]]]}}''
1396 161         3149 s{($chunk_re\s+)\!~\*(\s+$chunk_re)}{NOT IMATCHES($1,$2)}g; #]]]}}''
1397             # Interval/datetime calculation - VERY limited support
1398 161         26520 s{($chunk_re\s+)(\+|\-)\s*INTERVAL(\s+$chunk_re)}{$interval_func{$2}($1,$3)}i;
1399             }
1400             # Solve table aliases problem.
1401             # ("select x.a, y.b from table t1 as x t2 as y" does not work)
1402 161 100       2069 my $from_clause = $1 if $sql =~ /\s+FROM\s+(.*?)(?:\sWHERE|\sON|\sUSING|\sGROUP\s+BY|\sHAVING|\sORDER\s+BY|\sLIMIT|\;|$)/si;
1403 161 100       922 if ($from_clause) {
1404 43         1458 my @ftables = split /\s*(?:,|(?:NATURAL\s+|LEFT\s+|RIGHT\s+|FULL\s+|OUTER\s+|INNER\s+|CROSS\s+)*JOIN)\s*/i, $from_clause;
1405 43         144 foreach my $tb (@ftables) {
1406 71         183 $tb =~ s/[\(\)]/ /g;
1407 71         491 $tb =~ s/^\s+//;
1408 71         277 $tb =~ s/\s+$//;
1409 71 100       397 if ($tb =~ /\s/) {
1410 12         59 my ($real,$alias) = split /\s+(?:AS\s+)?/i, $tb;
1411 12 50 33     65 next unless $real && $alias;
1412 12         173 $sql =~ s/\Q$tb\E/$real/g;
1413 12         174 $sql =~ s/\b$alias\.(\w+)/$real.$1/g;
1414 12         210 $sql =~ s/\b$alias\.\*([,\s])/$real.*$1/g;
1415             }
1416             }
1417             }
1418             # Solve ambiguous column problem in natural join
1419             # ("select cat_id, sc_id, cat_name, sc_name from cat natural join subcat" does not work)
1420 161 100       1622 if ($sql =~ $join_re) {
1421 14         915 my @tables = ($sql =~ /(\w+)$join_re/gi);
1422 14         407 push @tables, ($sql =~ /$join_re(\w+)/gi);
1423 14         25 my (%seen,%col);
1424 14         31 for my $tab (@tables) {
1425 55 100       532 next if $seen{$tab}++;
1426 42         844 my $res = $dbh->selectall_arrayref("pragma table_info($tab)",{Columns=>{}});
1427 42 50 33     15185 next unless $res && ref $res eq 'ARRAY';
1428 42         91 for my $row (@$res) {
1429 126 100       292 if ($col{ $row->{name} }) {
1430 28         70 $col{ $row->{name} }->[0]++;
1431             }
1432             else {
1433 98         691 $col{ $row->{name} } = [1, $tab];
1434             }
1435             }
1436             }
1437 14         56 for my $c (keys %col) {
1438 98 100       273 next unless $col{$c}->[0] > 1;
1439 28 100 66     736 if ($from_clause && $from_clause =~ /\([^\)]*\b$col{$c}->[1]\b[^\)]*\)/) {
1440             # Table grouping in joins addles SQLite's brains.
1441             # It messes aliases up (and indeed table referencing in colnames generally).
1442 2         37 $sql =~ s/\b$col{$c}->[1]\.(\w+)/$1/g;
1443             }
1444             else {
1445 26         1307 $sql =~ s/([^\w\.])$c([^\w\.])/$1$col{$c}->[1].$c$2/g;
1446             }
1447             }
1448             }
1449             # Unprotect quoted strings
1450 161         828 $sql =~ s{\'([a-fA-F0-9]+)\'}{pack("H*",$1)}gie; #};\';
  102         828  
1451             # Catch implicit NEXTVAL calls
1452 161         851 $sql = catch_nextval($sql,$dbh);
1453             # Postfilter SQL
1454 161 50 0     4101 $sql = ($dbh->{postfilter}->($sql) || $sql) if ref $dbh->{postfilter} eq 'CODE';
1455 161 100 33     1122 $sql = ($attr->{postfilter}->($sql) || $sql) if ref $attr->{postfilter} eq 'CODE';
1456             # warn "[ FILTERED SQL:\n$sql\n]\n" if $ENV{PGLITEDEBUG};
1457 161         853 return $sql;
1458             }
1459              
1460             sub catch_nextval {
1461 161     161   901 my ($sql,$dbh) = @_;
1462 161 100       2009 return $sql unless $sql =~ /^\s*INSERT\s+INTO\s+([\w\.]+)\s+\(([^\)]+)\)\s+VALUES\s+\(/si;
1463 3         16 my $table = lc($1);
1464 3         12 my $colstr = lc($2);
1465 3         54 my @pk = $dbh->primary_key(undef,undef,$table);
1466 3 50       6685 return $sql unless @pk==1;
1467 3         14 $colstr =~ s/^\s+//;
1468 3         15 $colstr =~ s/\s+$//;
1469 3         42 my %cols = map { (lc($_)=>1) } split /\s*,\s*/, $colstr;
  11         37  
1470 3 100       21 return $sql if $cols{lc($pk[0])};
1471 1         4 my $seqname = $table . '_' . lc($pk[0]) . '_seq';
1472 1         3 my $val = 0;
1473 1         3 eval { $val = $dbh->selectrow_array("SELECT NEXTVAL('$seqname')") };
  1         12  
1474 1 50       21 if ($val) {
1475 1         25 $sql =~ s/(INTO\s+[\w\.]+\s+\()/$1$pk[0], /i;
1476 1         10 $sql =~ s/(VALUES\s+\()/$1$val, /i;
1477             }
1478 1         8 return $sql;
1479             }
1480              
1481              
1482             ### Aggregate function: avg ####
1483              
1484             package DBD::PgLite::Aggregate::avg;
1485              
1486 1     1   248 sub new { bless {sum=>0,count=>0}, shift; }
1487             sub step {
1488 8     8   11 my ($self,$val) = @_;
1489 8 50       17 return unless defined $val; # don't count nulls as zero
1490 8         18 $self->{count}++;
1491 8         53 $self->{sum}+=$val;
1492             }
1493             sub finalize {
1494 1     1   3 my $self = shift;
1495 1 50       5 return undef unless $self->{count};
1496 1         32 return $self->{sum}/$self->{count};
1497             }
1498              
1499             1;
1500             __END__