File Coverage

blib/lib/DBIx/SecureCGI.pm
Criterion Covered Total %
statement 28 335 8.3
branch 8 202 3.9
condition 5 111 4.5
subroutine 8 40 20.0
pod 1 1 100.0
total 50 689 7.2


line stmt bran cond sub pod time code
1             package DBIx::SecureCGI;
2 20     20   5562429 use 5.010001;
  20         166  
3 20     20   84 use warnings;
  20         28  
  20         407  
4 20     20   76 use strict;
  20         26  
  20         398  
5 20     20   1043 use utf8;
  20         64  
  20         133  
6 20     20   464 use Carp;
  20         45  
  20         1261  
7              
8             our $VERSION = 'v3.0.1';
9              
10 20     20   2517 use DBI;
  20         27795  
  20         639  
11 20     20   97 use List::Util qw( any );
  20         35  
  20         95193  
12              
13              
14             ## no critic (ProhibitPostfixControls Capitalization ProhibitEnumeratedClasses)
15              
16             my $PRIVATE = 'private_' . __PACKAGE__;
17             my $INT = qr/\A-?\d+\s+(?:SECOND|MINUTE|HOUR|DAY|MONTH|YEAR)\z/msi;
18             my $IDENT = qr/((?!__)\w[a-zA-Z0-9]*(?:_(?!_)[a-zA-Z0-9]*)*)/ms;
19             my %Func = ();
20              
21              
22             DefineFunc(eq => sub {
23             my ($dbh, $f, $v) = @_;
24             my (@val, $null, @expr);
25             @val = ref $v ? @{$v} : $v;
26             $null = grep {!defined} @val;
27             @val = grep {defined} @val;
28             push @expr, sprintf '%s IS NULL', $f if $null;
29             push @expr, sprintf '%s = %s', $f, $dbh->quote($val[0]) if @val==1;
30             push @expr, sprintf '%s IN (%s)',
31             $f, join q{,}, map { $dbh->quote($_) } @val if @val>1;
32             push @expr, 'NOT 1' if !@expr;
33             return @expr==1 ? $expr[0] : '('.join(' OR ', @expr).')';
34             });
35             DefineFunc(ne => sub {
36             my ($dbh, $f, $v) = @_;
37             my (@val, $null, @expr);
38             @val = ref $v ? @{$v} : $v;
39             $null = grep {!defined} @val;
40             @val = grep {defined} @val;
41             push @expr, sprintf '%s IS NOT NULL', $f if $null && !@val;
42             push @expr, sprintf '%s IS NULL', $f if !$null && @val;
43             push @expr, sprintf '%s != %s', $f,$dbh->quote($val[0]) if @val==1;
44             push @expr, sprintf '%s NOT IN (%s)', $f,
45             join q{,}, map { $dbh->quote($_) } @val if @val>1;
46             push @expr, 'NOT 0' if !@expr;
47             return @expr==1 ? $expr[0] : '('.join(' OR ', @expr).')';
48             });
49             DefineFunc(lt => '%s < %s');
50             DefineFunc(gt => '%s > %s');
51             DefineFunc(le => '%s <= %s');
52             DefineFunc(ge => '%s >= %s');
53             DefineFunc(like => '%s LIKE %s');
54             DefineFunc(not_like => '%s NOT LIKE %s');
55             DefineFunc(date_eq => [$INT, '%s = DATE_ADD(NOW(), INTERVAL %s)']);
56             DefineFunc(date_ne => [$INT, '%s != DATE_ADD(NOW(), INTERVAL %s)']);
57             DefineFunc(date_lt => [$INT, '%s < DATE_ADD(NOW(), INTERVAL %s)']);
58             DefineFunc(date_gt => [$INT, '%s > DATE_ADD(NOW(), INTERVAL %s)']);
59             DefineFunc(date_le => [$INT, '%s <= DATE_ADD(NOW(), INTERVAL %s)']);
60             DefineFunc(date_ge => [$INT, '%s >= DATE_ADD(NOW(), INTERVAL %s)']);
61             DefineFunc(set_date => sub {
62             my ($dbh, $f, $v) = @_;
63             if (uc $v eq 'NOW') {
64             return sprintf '%s = NOW()', $f;
65             } elsif ($v =~ /$INT/mso) {
66             return sprintf '%s = DATE_ADD(NOW(), INTERVAL %s)', $f, $dbh->quote($v),
67             }
68             return;
69             });
70             DefineFunc(set_add => sub {
71             my ($dbh, $f, $v) = @_;
72             return sprintf '%s = %s + %s', $f, $f, $dbh->quote($v);
73             });
74              
75              
76             sub DefineFunc {
77 320     320 1 453 my ($func, $cmd) = @_;
78 320 50 33     1413 if (!$func || ref $func || $func !~ /\A[A-Za-z]\w*\z/ms) {
      33        
79 0         0 croak "bad function name: $func";
80             }
81 320 100       624 if (!ref $cmd) {
    100          
    50          
82 120 50       299 if (2 != (() = $cmd =~ /%s/msg)) {
83 0         0 croak "bad function: $cmd";
84             }
85             } elsif (ref $cmd eq 'ARRAY') {
86 120 50 33     126 if (2 != @{$cmd}
  120   33     746  
      33        
87             || ref $cmd->[0] ne 'Regexp'
88             || (ref $cmd->[1] || 2 != (() = $cmd->[1] =~ /%s/msg))) {
89 0         0 croak "bad function: [@$cmd]";
90             }
91             } elsif (ref $cmd ne 'CODE') {
92 0         0 croak 'bad function';
93             }
94 320         966 $Func{$func} = $cmd;
95 320         392 return;
96             }
97              
98             sub _ret {
99 0     0     my $cb = shift;
100 0 0         if ($cb) {
101 0           return $cb->(@_);
102             } else {
103 0 0         return wantarray ? @_ : $_[0];
104             }
105             }
106              
107             sub _ret1 {
108 0     0     my ($cb, $ret, $h) = @_;
109 0 0         if ($cb) {
110 0           return $cb->($ret, $h);
111             } else {
112 0           return $ret;
113             }
114             }
115              
116             sub _retdo {
117 0     0     my ($dbh, $sql, $cb) = @_;
118 0 0         if (!$cb) {
119 0           return $dbh->do($sql);
120             }
121 0           return $dbh->do($sql, undef, $cb);
122             }
123              
124             # Set cache to given HASHREF, if any.
125             # Initialize cache, if needed.
126             # Return current cache.
127             sub DBI::db::SecureCGICache {
128 0     0     my ($dbh, $cache) = @_;
129 0 0 0       if ($cache && ref $cache eq 'HASH') {
130 0           $dbh->{$PRIVATE} = $cache;
131             } else {
132 0   0       $dbh->{$PRIVATE} //= {};
133             }
134 0           return $dbh->{$PRIVATE};
135             }
136              
137             # Ensure $dbh->All("DESC $table") is cached.
138             # Return cached $dbh->All("DESC $table").
139             # On error set $dbh->err and return nothing.
140             sub DBI::db::ColumnInfo {
141 0     0     my ($dbh, $table, $cb) = @_;
142 0           my $cache = $dbh->SecureCGICache();
143 0 0         if ($cache->{$table}) {
144 0           return _ret($cb, $cache->{$table});
145             }
146              
147 0 0         if (!$cb) {
148 0           my @desc = $dbh->All('DESC '.$dbh->quote_identifier($table));
149 0           return _set_column_info($dbh, $cache, $table, undef, @desc);
150             }
151             return $dbh->All('DESC '.$dbh->quote_identifier($table), sub {
152 0     0     my @desc = @_;
153 0           return _set_column_info($dbh, $cache, $table, $cb, @desc);
154 0           });
155             }
156              
157             sub _set_column_info {
158 0     0     my ($dbh, $cache, $table, $cb, @desc) = @_;
159 0 0         if (@desc) {
160 0           my @pk = grep {$desc[$_]{Key} eq 'PRI'} 0 .. $#desc;
  0            
161 0 0 0       if (1 != @pk || $pk[0] != 0) {
162 0           return _ret($cb, $dbh->set_err($DBI::stderr, "first field must be primary key: $table\n", undef, 'ColumnInfo'));
163             }
164 0           $cache->{$table} = \@desc;
165             }
166 0           return _ret($cb, $cache->{$table});
167             }
168              
169             # Ensure DESC for all $tables cached.
170             # Return $dbh->SecureCGICache().
171             # On error set $dbh->err and return nothing.
172             sub DBI::db::TableInfo {
173 0     0     my ($dbh, $tables, $cb) = @_;
174 0 0         my @tables = ref $tables eq 'ARRAY' ? @{$tables} : ($tables);
  0            
175 0 0 0 0     if (!@tables || any {/\A\z|\s/ms} @tables) {
  0            
176 0           return _ret($cb, $dbh->set_err($DBI::stderr, "bad tables: [@tables]\n", undef, 'TableInfo'));
177             }
178              
179 0 0         if (!$cb) {
180 0           while (@tables) {
181 0           my $desc = $dbh->ColumnInfo(shift @tables);
182 0 0         if (!$desc) {
183 0           return;
184             }
185             }
186 0           return $dbh->SecureCGICache();
187             }
188 0           my $code; $code = sub {
189 0     0     my ($desc) = @_;
190 0 0         if (!$desc) {
191 0           undef $code;
192 0           return $cb->();
193             }
194 0 0         if (@tables) {
195 0           return $dbh->ColumnInfo(shift @tables, $code);
196             }
197 0           undef $code;
198 0           return $cb->( $dbh->SecureCGICache() );
199 0           };
200 0           return $dbh->ColumnInfo(shift @tables, $code);
201             }
202              
203             sub DBI::db::GetSQL {
204 0     0     my ($dbh, $tables, $P, $cb) = @_;
205             # remove possible JOIN info from table names for TableInfo()
206 0 0         my @tables = map {my $s=$_;$s=~s/\s.*//ms;$s} ref $tables ? @{$tables} : $tables; ## no critic (ProhibitComplexMappings)
  0            
  0            
  0            
  0            
207 0 0         if (!$cb) {
208 0           my $cache = $dbh->TableInfo(\@tables);
209 0           return _get_sql($dbh, $cache, $tables, $P);
210             }
211             return $dbh->TableInfo(\@tables, sub {
212 0     0     my $cache = shift;
213 0           return _get_sql($dbh, $cache, $tables, $P, $cb);
214 0           });
215             }
216              
217             sub _get_sql { ## no critic (ProhibitExcessComplexity)
218 0     0     my ($dbh, $cache, $tables, $P, $cb) = @_;
219 0 0         if (!$cache) {
220 0           return _ret($cb);
221             }
222              
223             # Extract JOIN type info from table names
224 0           my (@tables, @jointype);
225 0 0         for (ref $tables eq 'ARRAY' ? @{$tables} : $tables) {
  0            
226 0 0         if (/\A(\S+)(?:\s+(LEFT|INNER))?\s*\z/msi) {
227 0           push @tables, $1;
228 0   0       push @jointype, $2 // 'INNER';
229             }
230             else {
231 0           return _ret($cb, $dbh->set_err($DBI::stderr, "unknown join type: $_\n", undef, 'GetSQL'));
232             }
233             }
234              
235             my %SQL = (
236             Table => $tables[0],
237             ID => $cache->{ $tables[0] }[0]{Field},
238 0           Select => q{},
239             From => q{},
240             Set => q{},
241             Where => q{},
242             UpdateWhere => q{},
243             Order => q{},
244             Group => q{},
245             Limit => q{},
246             SelectLimit => q{},
247             );
248              
249             # Detect keys which should be used for JOINing tables
250 0           $SQL{From} = $dbh->quote_identifier($tables[0]);
251 0           my @field = map {{ map {$_->{Field}=>1} @{ $cache->{$_} } }} @tables; ## no critic (ProhibitComplexMappings,ProhibitVoidMap)
  0            
  0            
  0            
252             TABLE:
253 0           for my $right (1..$#tables) {
254             ## no critic (ProhibitAmbiguousNames)
255 0           my $rkey = $cache->{ $tables[$right] }[0]{Field};
256 0           for my $left (0..$right-1) {
257 0           my $lkey = $cache->{ $tables[$left] }[0]{Field};
258             my $key = $field[$left]{$rkey} ? $rkey :
259 0 0         $field[$right]{$lkey} ? $lkey : next;
    0          
260             $SQL{From} .= sprintf ' %s JOIN %s ON (%s.%s = %s.%s)',
261             $jointype[$right],
262 0           map { $dbh->quote_identifier($_) }
  0            
263             $tables[$right], $tables[$right], $key, $tables[$left], $key;
264 0           next TABLE;
265             }
266 0           return _ret($cb, $dbh->set_err($DBI::stderr, "can't join table: $tables[$right]\n", undef, 'GetSQL'));
267             }
268              
269             # Set $SQL{Select} using qualified field names and without duplicates
270 0           my %qualify;
271 0           for my $t (@tables) {
272 0           for my $f (map {$_->{Field}} @{ $cache->{$t} }) {
  0            
  0            
273 0 0         next if $qualify{$f};
274             $qualify{$f} = sprintf '%s.%s',
275 0           map { $dbh->quote_identifier($_) } $t, $f;
  0            
276 0           $SQL{Select} .= ', '.$qualify{$f};
277             }
278             }
279 0           $SQL{Select} =~ s/\A, //ms;
280              
281             # Set $SQL{Set}, $SQL{Where}, $SQL{UpdateWhere}
282 0           for my $k (keys %{$P}) {
  0            
283 0 0         $k =~ /\A$IDENT(?:__(?!_)$IDENT)?\z/ms or next; # ignore non-field keys
284 0 0         my $f = $qualify{$1} or next; # ignore non-field keys
285 0   0       my $func= $2 // q{};
286 0   0       my $cmd = $Func{$func || 'eq'};
287 0 0         if (!$cmd) {
288 0           return _ret($cb, $dbh->set_err($DBI::stderr, "unknown function: $k\n", undef, 'GetSQL'));
289             }
290 0 0 0       if (!$func && ref $P->{$k}) {
291 0           return _ret($cb, $dbh->set_err($DBI::stderr, "ARRAYREF without function: $k\n", undef, 'GetSQL'));
292             }
293             # WARNING functions `eq' and `ne' must process value array themselves:
294 0   0       my $is_list = ref $P->{$k} && $func ne 'eq' && $func ne 'ne';
295 0 0         for my $v ($is_list ? @{$P->{$k}} : $P->{$k}) {
  0            
296 0 0 0       my $expr
    0          
297             = ref $cmd eq 'CODE' ? $cmd->($dbh, $f, $v)
298             : ref $cmd eq 'ARRAY' ? ($v =~ /$cmd->[0]/ms && sprintf $cmd->[1], $f, $v)
299             : sprintf $cmd, $f, $dbh->quote($v);
300 0 0         if (!$expr) {
301 0           return _ret($cb, $dbh->set_err($DBI::stderr, "bad value for $k: $v\n", undef, 'GetSQL'));
302             }
303 0 0 0       $SQL{Set} .= ", $expr" if !$func || $func =~ /\Aset_/ms;
304 0 0         $SQL{Where} .= " AND $expr" if $func !~ /\Aset_/ms;
305 0 0 0       $SQL{UpdateWhere} .= " AND $expr" if $func && $func !~ /\Aset_/ms;
306 0 0         $SQL{UpdateWhere} .= " AND $expr" if $k eq $SQL{ID};
307             }
308             }
309 0           $SQL{Set} =~ s/\A, //ms;
310 0           $SQL{Where} =~ s/\A AND //ms;
311 0           $SQL{UpdateWhere} =~ s/\A AND //ms;
312 0           $SQL{Set} =~ s/\s+IS\s+NULL/ = NULL/msg;
313 0   0       $SQL{Where} ||= '1';
314 0   0       $SQL{UpdateWhere} ||= '1';
315              
316             # Set $SQL{Order} and $SQL{Group}
317 0 0         for my $order (ref $P->{__order} ? @{$P->{__order}} : $P->{__order}) {
  0            
318 0 0         next if !defined $order;
319 0 0 0       if ($order !~ /\A(\w+)\s*( ASC| DESC|)\z/ms || !$qualify{$1}) {
320 0           return _ret($cb, $dbh->set_err($DBI::stderr, "bad __order value: $order\n", undef, 'GetSQL'));
321             }
322 0           $SQL{Order} .= ", $qualify{$1}$2";
323             }
324 0 0         for my $group (ref $P->{__group} ? @{$P->{__group}} : $P->{__group}) {
  0            
325 0 0         next if !defined $group;
326 0 0 0       if ($group !~ /\A(\w+)\s*( ASC| DESC|)\z/ms || !$qualify{$1}) {
327 0           return _ret($cb, $dbh->set_err($DBI::stderr, "bad __group value: $group\n", undef, 'GetSQL'));
328             }
329 0           $SQL{Group} .= ", $qualify{$1}$2";
330             }
331 0           $SQL{Order} =~ s/\A, //ms;
332 0           $SQL{Group} =~ s/\A, //ms;
333              
334             # Set $SQL{Limit}, $SQL{SelectLimit}
335 0 0 0       my @limit = ref $P->{__limit} ? @{$P->{__limit}} : $P->{__limit} // ();
  0            
336 0           for (grep {!m/\A\d+\z/ms} @limit) {
  0            
337 0           return _ret($cb, $dbh->set_err($DBI::stderr, "bad __limit value: $_\n", undef, 'GetSQL'));
338             }
339 0 0         if (@limit == 1) {
    0          
    0          
340 0           $SQL{Limit} = " $limit[0]"; # make __limit=>0 true value
341 0           $SQL{SelectLimit} = " $limit[0]"; # make __limit=>0 true value
342             }
343             elsif (@limit == 2) {
344 0           $SQL{SelectLimit} = join q{,}, @limit;
345             }
346             elsif (@limit > 2) {
347 0           return _ret($cb, $dbh->set_err($DBI::stderr, "too many __limit values: [@limit]\n", undef, 'GetSQL'));
348             }
349              
350 0           return _ret($cb, \%SQL);
351             }
352              
353             sub DBI::db::Insert {
354 0     0     my ($dbh, $table, $P, $cb) = @_;
355 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
356              
357             my $sql = sprintf 'INSERT INTO %s SET %s',
358 0           $dbh->quote_identifier($SQL->{Table}), $SQL->{Set};
359              
360 0 0         if (!$cb) {
361 0 0         return $dbh->do($sql) ? $dbh->{mysql_insertid} : undef;
362             }
363             return $dbh->do($sql, undef, sub {
364 0     0     my ($rv, $dbh) = @_; ## no critic (ProhibitReusedNames)
365 0 0         return $cb->(($rv ? $dbh->{mysql_insertid} : undef), $dbh);
366 0           });
367             }
368              
369             sub DBI::db::InsertIgnore {
370 0     0     my ($dbh, $table, $P, $cb) = @_;
371 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
372              
373             my $sql = sprintf 'INSERT IGNORE INTO %s SET %s',
374 0           $dbh->quote_identifier($SQL->{Table}), $SQL->{Set};
375 0           return _retdo($dbh, $sql, $cb);
376             }
377              
378             sub DBI::db::Update {
379 0     0     my ($dbh, $table, $P, $cb) = @_;
380 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
381 0 0 0       if ($SQL->{UpdateWhere} eq '1' && !$P->{__force}) {
382 0           return _ret1($cb, $dbh->set_err($DBI::stderr, "empty WHERE require {__force=>1}\n", undef, 'Update'), $dbh);
383             }
384              
385             my $sql = sprintf 'UPDATE %s SET %s WHERE %s' . ($SQL->{Limit} ? ' LIMIT %s' : q{}),
386             $dbh->quote_identifier($SQL->{Table}), $SQL->{Set}, $SQL->{UpdateWhere},
387 0 0 0       $SQL->{Limit} || ();
388 0           return _retdo($dbh, $sql, $cb);
389             }
390              
391             sub DBI::db::Replace {
392 0     0     my ($dbh, $table, $P, $cb) = @_;
393 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
394              
395             my $sql = sprintf 'REPLACE INTO %s SET %s',
396 0           $dbh->quote_identifier($SQL->{Table}), $SQL->{Set};
397 0           return _retdo($dbh, $sql, $cb);
398             }
399              
400             sub _find_tables_for_delete {
401 0     0     my ($dbh, $fields, $tables, $P, $cb) = @_;
402 0 0         if (!@{$tables}) {
  0            
403 0           return _ret1($cb, undef, $dbh);
404             }
405              
406 0           my $found = [];
407 0 0         if (!$cb) {
408 0           for my $t (@{$tables}) {
  0            
409 0           my $desc = $dbh->ColumnInfo($t);
410 0 0         if ($desc) {
411 0           my @columns = map {$_->{Field}} @{$desc};
  0            
  0            
412 0           my %seen;
413 0 0         if (@{$fields} == grep {++$seen{$_}==2} @{$fields}, @columns) {
  0            
  0            
  0            
414 0           push @{$found}, $t;
  0            
415             }
416             }
417             }
418 0           return $dbh->Delete($found, $P);
419             }
420 0           my $code; $code = sub {
421 0     0     my ($desc) = @_;
422 0           my $t = shift @{$tables};
  0            
423 0 0         if ($desc) {
424 0           my @columns = map {$_->{Field}} @{$desc};
  0            
  0            
425 0           my %seen;
426 0 0         if (@{$fields} == grep {++$seen{$_}==2} @{$fields}, @columns) {
  0            
  0            
  0            
427 0           push @{$found}, $t;
  0            
428             }
429             }
430 0 0         if (@{$tables}) {
  0            
431 0           return $dbh->ColumnInfo($tables->[0], $code);
432             }
433 0           undef $code;
434 0           return $dbh->Delete($found, $P, $cb);
435 0           };
436 0           return $dbh->ColumnInfo($tables->[0], $code);
437             }
438              
439             sub DBI::db::Delete { ## no critic (ProhibitExcessComplexity)
440 0     0     my ($dbh, $table, $P, $cb) = @_;
441              
442 0 0         if (!defined $table) {
443 0 0         my %fields = map {/\A$IDENT(?:__(?!_)$IDENT)?\z/ms ? ($1=>1) : ()} keys %{$P};
  0            
  0            
444 0           my @fields = keys %fields;
445 0 0         if (!@fields) {
446 0           return _ret1($cb, $dbh->set_err($DBI::stderr, "table undefined, require params\n", undef, 'Delete'), $dbh);
447             }
448 0 0         if (!$cb) {
449 0           return _find_tables_for_delete($dbh, \@fields, [$dbh->Col('SHOW TABLES')], $P);
450             }
451             return $dbh->Col('SHOW TABLES', sub {
452 0     0     my (@tables) = @_;
453 0           return _find_tables_for_delete($dbh, \@fields, \@tables, $P, $cb);
454 0           });
455             }
456              
457 0 0         my @tables = ref $table ? @{$table} : $table;
  0            
458 0 0         if (!$cb) {
459 0           my $res;
460 0           for my $t (@tables) {
461 0 0         my $SQL = $dbh->GetSQL($t, $P) or return;
462 0 0 0       if ($SQL->{Where} eq '1' && !$P->{__force}) {
463 0           return $dbh->set_err($DBI::stderr, "empty WHERE require {__force=>1}\n", undef, 'Delete');
464             }
465             my $sql = sprintf 'DELETE FROM %s WHERE %s' . ($SQL->{Limit} ? ' LIMIT %s' : q{}),
466 0 0 0       $dbh->quote_identifier($SQL->{Table}), $SQL->{Where}, $SQL->{Limit} || ();
467 0 0         $res = $dbh->do($sql) or return;
468             }
469 0           return $res;
470             }
471 0           my $code; $code = sub {
472 0     0     my ($SQL) = @_;
473 0           my $t = shift @tables;
474 0 0         if (!$SQL) {
475 0           undef $code;
476 0           return $cb->(undef, $dbh);
477             }
478 0 0 0       if ($SQL->{Where} eq '1' && !$P->{__force}) {
479 0           undef $code;
480 0           return $cb->($dbh->set_err($DBI::stderr, "empty WHERE require {__force=>1}\n", undef, 'Delete'), $dbh);
481             }
482             my $sql = sprintf 'DELETE FROM %s WHERE %s' . ($SQL->{Limit} ? ' LIMIT %s' : q{}),
483 0 0 0       $dbh->quote_identifier($SQL->{Table}), $SQL->{Where}, $SQL->{Limit} || ();
484             $dbh->do($sql, sub {
485 0           my ($res, $dbh) = @_; ## no critic (ProhibitReusedNames)
486 0 0 0       if ($res && @tables) {
487 0           return $dbh->GetSQL($tables[0], $P, $code);
488             }
489 0           undef $code;
490 0           return $cb->($res, $dbh);
491 0           });
492 0           };
493 0           return $dbh->GetSQL($tables[0], $P, $code);
494             }
495              
496             sub DBI::db::ID {
497 0     0     my ($dbh, $table, $P, $cb) = @_;
498 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
499              
500             my $sql = sprintf 'SELECT %s.%s FROM %s WHERE %s'
501             . ($SQL->{Order} ? ' ORDER BY %s' : q{})
502             . ($SQL->{SelectLimit} ? ' LIMIT %s' : q{}),
503 0           (map { $dbh->quote_identifier($_) } $SQL->{Table}, $SQL->{ID}),
504 0 0 0       $SQL->{From}, $SQL->{Where}, $SQL->{Order} || (), $SQL->{SelectLimit} || ();
    0 0        
505 0   0       return $dbh->Col($sql, $cb // ());
506             }
507              
508             sub DBI::db::Count {
509 0     0     my ($dbh, $table, $P, $cb) = @_;
510 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
511              
512             my $sql = sprintf 'SELECT count(*) __count FROM %s WHERE %s',
513 0           $SQL->{From}, $SQL->{Where};
514 0   0       return $dbh->Col($sql, $cb // ());
515             }
516              
517             sub DBI::db::Select {
518 0     0     my ($dbh, $table, $P, $cb) = @_;
519 0 0         my $SQL = $dbh->GetSQL($table, $P) or return _ret1($cb, undef, $dbh);
520              
521             my $sql = sprintf 'SELECT %s'
522             . ($SQL->{Group} ? ', count(*) __count' : q{})
523             . ' FROM %s WHERE %s'
524             . ($SQL->{Group} ? ' GROUP BY %s' : q{})
525             . ($SQL->{Order} ? ' ORDER BY %s' : q{})
526             . ($SQL->{SelectLimit} ? ' LIMIT %s' : q{}),
527             $SQL->{Select}, $SQL->{From}, $SQL->{Where},
528 0 0 0       $SQL->{Group} || (), $SQL->{Order} || (), $SQL->{SelectLimit} || ();
    0 0        
    0 0        
    0          
529 0 0         if (!$cb) {
530 0 0         return wantarray ? $dbh->All($sql) : $dbh->Row($sql);
531             }
532 0           return $dbh->All($sql, $cb);
533             }
534              
535             sub _is_cb {
536 0     0     my $cb = shift;
537 0           my $ref = ref $cb;
538 0   0       return $ref eq 'CODE' || $ref eq 'AnyEvent::CondVar';
539             }
540              
541             sub DBI::db::All {
542 0     0     my ($dbh, $sql, @bind) = @_;
543 0 0 0       my $cb = @bind && _is_cb($bind[-1]) ? pop @bind : undef;
544 0 0         if (!$cb) {
545 0 0         (my $sth = $dbh->prepare($sql, {async=>0}))->execute(@bind) or return;
546 0           return @{ $sth->fetchall_arrayref({}) };
  0            
547             }
548             return $dbh->prepare($sql)->execute(@bind, sub {
549 0     0     my ($rv, $sth) = @_;
550 0 0         return $cb->(!$rv ? () : @{ $sth->fetchall_arrayref({}) });
  0            
551 0           });
552             }
553              
554             sub DBI::db::Row {
555 0     0     my ($dbh, $sql, @bind) = @_;
556 0           return $dbh->selectrow_hashref($sql, undef, @bind);
557             }
558              
559             sub DBI::db::Col {
560 0     0     my ($dbh, $sql, @bind) = @_;
561 0 0 0       my $cb = @bind && _is_cb($bind[-1]) ? pop @bind : undef;
562 0 0         if (!$cb) {
563 0 0         my @res = @{ $dbh->selectcol_arrayref($sql, undef, @bind) || [] };
  0            
564 0 0         return wantarray ? @res : $res[0];
565             }
566             return $dbh->selectcol_arrayref($sql, undef, @bind, sub {
567 0     0     my ($ary_ref) = @_;
568 0 0         return $cb->($ary_ref ? @{ $ary_ref } : ());
  0            
569 0           });
570             }
571              
572              
573             1; # Magic true value required at end of module
574             __END__