File Coverage

blib/lib/DBIx/Perlish.pm
Criterion Covered Total %
statement 232 347 66.8
branch 92 152 60.5
condition 57 97 58.7
subroutine 20 43 46.5
pod 14 18 77.7
total 415 657 63.1


line stmt bran cond sub pod time code
1             package DBIx::Perlish;
2              
3 25     25   742620 use 5.014;
  25         246  
4 25     25   166 use warnings;
  25         49  
  25         708  
5 25     25   136 use strict;
  25         63  
  25         658  
6 25     25   121 use Carp;
  25         57  
  25         2453  
7              
8 25     25   161 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS $SQL @BIND_VALUES);
  25         61  
  25         2941  
9             require Exporter;
10 25     25   156 use base 'Exporter';
  25         87  
  25         2192  
11 25     25   11331 use Keyword::Pluggable;
  25         631695  
  25         1694  
12              
13             $VERSION = '1.07';
14             @EXPORT = qw(sql);
15             @EXPORT_OK = qw(union intersect except subselect);
16             %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
17              
18 25     25   16286 use DBIx::Perlish::Parse;
  25         113  
  25         7510  
19              
20       0 1   sub union (&;$) {}
21       0 1   sub intersect (&;$) {}
22       0 1   sub except (&;$) {}
23       0 1   sub subselect (&) {}
24              
25             my $default_object;
26             my $non_object_quirks = {};
27              
28             sub optree_version
29             {
30 3 50   3 1 3440 return 1 if $^V lt 5.22.0;
31 3         13 return 2;
32             }
33              
34             sub lexify
35             {
36 32     32 0 133 my ( $text, $insert ) = @_;
37 32 100       184 $insert .= 'sub ' if $$text =~ /^\s*\{/;
38 32         5033 substr($$text, 0, 0, $insert);
39             }
40              
41             sub import
42             {
43 27     27   2199 my $pkg = caller;
44 27         84 local @EXPORT_OK = @EXPORT_OK;
45 27         130 local %EXPORT_TAGS = %EXPORT_TAGS;
46 27 50 33     441 if ($pkg && $pkg->can("except")) {
47             # XXX maybe check prototype here
48 0         0 pop @EXPORT_OK;
49 0         0 %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
50             }
51 27         55 my @shift;
52 27 100       147 @shift = (shift()) if @_ % 2;
53 27         88 my %p = @_;
54 27 100 66     119 if ($p{prefix} && $p{prefix} =~ /^[a-zA-Z_]\w*$/) {
55 25     25   190 no strict 'refs';
  25         92  
  25         11972  
56 2 50 33     17 if ( $p{dbh} && ref $p{dbh} && (ref $p{dbh} eq "SCALAR" || ref $p{dbh} eq "REF")) {
      66        
      33        
57 2         4 my $dbhref = $p{dbh};
58 2         53 *{$pkg."::$p{prefix}_fetch"} =
59 2         12 *{$pkg."::$p{prefix}_select"} =
60 2     0   7 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->fetch(@_) };
  0         0  
  0         0  
61 2         7 *{$pkg."::$p{prefix}_update"} =
62 2     0   14 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->update(@_) };
  0         0  
  0         0  
63 2         7 *{$pkg."::$p{prefix}_delete"} =
64 2     0   18 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->delete(@_) };
  0         0  
  0         0  
65 2         8 *{$pkg."::$p{prefix}_insert"} =
66 2     0   4 sub { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->insert(@_) };
  0         0  
  0         0  
67 2         207 return;
68             }
69             }
70              
71 25   50     139 my $prefix = delete($p{prefix}) // 'db';
72 25   100     133 my $dbh = delete($p{dbh}) // '$dbh';
73 25         78 my $iprefix = '__' . $dbh . '_execute_perlish';
74 25         183 $iprefix =~ s/\W//g;
75              
76 25         157 for (
77             [fetch => " $dbh, q(fetch), "],
78             [select => " $dbh, q(fetch), "],
79             [update => " $dbh, q(update), "],
80             [delete => " $dbh, q(delete), "],
81             ) {
82 100         2112 my ($name, $code) = @$_;
83             Keyword::Pluggable::define
84             keyword => $prefix . '_' . $name,
85 32     32   3138 code => sub { lexify( $_[0], $iprefix.$code ) },
86 100         512 expression => 1,
87             package => $pkg
88             ;
89             }
90             Keyword::Pluggable::define
91 25         634 keyword => $prefix . '_insert',
92             code => $iprefix . "_insert $dbh, ",
93             expression => 1,
94             package => $pkg
95             ;
96              
97             {
98 25     25   190 no strict 'refs';
  25         49  
  25         81702  
  25         579  
99 25         228 *{$pkg."::${iprefix}"} = sub ($$&) {
100 3     3   25 my ( $dbh, $method, $sub ) = @_;
101 3         14 my $o = DBIx::Perlish->new(dbh => $dbh);
102 3         9 $o->$method($sub);
103 25         100 };
104 25         134 *{$pkg."::${iprefix}_insert"} = sub {
105 0     0   0 my $o = DBIx::Perlish->new(dbh => shift);
106 0         0 $o->insert(@_)
107 25         96 };
108             }
109 25         5105 DBIx::Perlish->export_to_level(1, @shift, %p);
110             }
111              
112 0     0 0 0 sub init { warn "DBIx::Perlish::init is deprecated" }
113              
114             sub new
115             {
116 5     5 1 1510 my ($class, %p) = @_;
117 5 100       26 unless (UNIVERSAL::isa($p{dbh}, "DBI::db")) { # XXX maybe relax for other things?
118 1         13 die "Invalid database handle supplied in the \"dbh\" parameter.\n";
119             }
120 4         21 my $me = bless { dbh => $p{dbh}, quirks => {} }, $class;
121 4 50 33     16 if ($p{quirks} && ref $p{quirks} eq "ARRAY") {
122 0         0 for my $q (@{$p{quirks}}) {
  0         0  
123 0         0 $me->quirk(@$q);
124             }
125             }
126 4         17 return $me;
127             }
128              
129             sub quirk
130             {
131 0     0 1 0 my $flavor = shift;
132 0         0 my $quirks = $non_object_quirks;
133 0 0       0 if (ref $flavor) {
134 0         0 $quirks = $flavor->{quirks};
135 0         0 $flavor = shift;
136             }
137 0         0 $flavor = lc $flavor;
138 0 0       0 if ($flavor eq "oracle") {
139 0         0 my $qtype = shift;
140 0 0       0 if ($qtype eq "table_func_cast") {
141 0         0 my ($func, $cast) = @_;
142 0 0       0 die "table_func_cast requires a function name and a type name" unless $cast;
143 0         0 $quirks->{oracle_table_func_cast}{$func} = $cast;
144             } else {
145 0         0 die "unknown quirk $qtype for $flavor";
146             }
147             } else {
148 0         0 die "there are currently no quirks for $flavor";
149             }
150             }
151              
152             sub _get_flavor
153             {
154 3     3   7 my ($real_dbh) = @_;
155 3   33     12 my $dbh = tied(%$real_dbh) || $real_dbh;
156 3         12 return lc $dbh->{Driver}{Name};
157             }
158              
159             sub gen_sql_select
160             {
161 3     3 0 4 my ($moi, $sub) = @_;
162 3 50       7 my $me = ref $moi ? $moi : {};
163              
164 3         4 my $dbh = $me->{dbh};
165 3         5 my @kf;
166 3         5 my $flavor = _get_flavor($dbh);
167 3     0   10 my $kf_convert = sub { return $_[0] };
  0         0  
168 3 0 33     8 if ($flavor eq "pg" && $dbh->{FetchHashKeyName}) {
169 0 0       0 if ($dbh->{FetchHashKeyName} eq "NAME_uc") {
    0          
170 0     0   0 $kf_convert = sub { return uc $_[0] };
  0         0  
171             } elsif ($dbh->{FetchHashKeyName} eq "NAME_lc") {
172 0     0   0 $kf_convert = sub { return lc $_[0] };
  0         0  
173             }
174             }
175             my ($sql, $bind_values, $nret, %flags) = gen_sql($sub, "select",
176             flavor => $flavor,
177             dbh => $dbh,
178 3   33     11 quirks => $me->{quirks} || $non_object_quirks,
179             key_fields => \@kf,
180             kf_convert => $kf_convert,
181             );
182 3 50       8 $flags{key_fields} = \@kf if @kf;
183 3         18 return $sql, $bind_values, $nret, %flags;
184             }
185              
186             sub query
187             {
188 0     0 1 0 my ($moi, $sub) = @_;
189 0 0       0 my $me = ref $moi ? $moi : {};
190 0         0 my ( $sql ) = $moi->gen_sql_select($sub);
191 0         0 return $sql;
192             }
193              
194             sub fetch
195             {
196 3     3 1 6 my ($moi, $sub) = @_;
197 3 50       8 my $me = ref $moi ? $moi : {};
198              
199 3         3 my $nret;
200 3         9 my $dbh = $me->{dbh};
201 3         4 my %flags;
202              
203 3         9 ($me->{sql}, $me->{bind_values}, $nret, %flags) = $me->gen_sql_select($sub);
204 3         6 $SQL = $me->{sql}; @BIND_VALUES = @{$me->{bind_values}};
  3         5  
  3         5  
205              
206 3 50       6 if ($flags{key_fields}) {
207 0   0     0 my @kf = @{ $flags{key_fields} // [] };
  0         0  
208 0 0       0 my $kf = @kf == 1 ? $kf[0] : [@kf];
209 0   0     0 my $r = $dbh->selectall_hashref($me->{sql}, $kf, {}, @{$me->{bind_values}}) || {};
210 0         0 my $postprocess;
211 0 0       0 if ($nret - @kf == 1) {
212             # Only one field returned apart from the key field,
213             # change hash reference to simple values.
214             $postprocess = sub {
215 0     0   0 my ($h, $level) = @_;
216 0 0       0 if ($level <= 1) {
217 0         0 delete @$_{@kf} for values %$h;
218 0         0 $_ = (values %$_)[0] for values %$h;
219             } else {
220 0         0 for my $nh (values %$h) {
221 0         0 $postprocess->($nh, $level-1);
222             }
223             }
224 0         0 };
225             } else {
226             $postprocess = sub {
227 0     0   0 my ($h, $level) = @_;
228 0 0       0 if ($level <= 1) {
229 0         0 delete @$_{@kf} for values %$h;
230             } else {
231 0         0 for my $nh (values %$h) {
232 0         0 $postprocess->($nh, $level-1);
233             }
234             }
235 0         0 };
236             }
237 0         0 $postprocess->($r, scalar @kf);
238 0 0       0 return wantarray ? %$r : $r;
239             } else {
240 3 50       8 if ($nret > 1) {
241 0   0     0 my $r = $dbh->selectall_arrayref($me->{sql}, {Slice=>{}}, @{$me->{bind_values}}) || [];
242 0 0       0 return wantarray ? @$r : $r->[0];
243             } else {
244 3   50     5 my $r = $dbh->selectcol_arrayref($me->{sql}, {}, @{$me->{bind_values}}) || [];
245 3 50       49 return wantarray ? @$r : $r->[0];
246             }
247             }
248             }
249              
250             # XXX refactor update/delete into a single implemention if possible?
251             sub update
252             {
253 0     0 1 0 my ($moi, $sub) = @_;
254 0 0       0 my $me = ref $moi ? $moi : {};
255              
256 0         0 my $dbh = $me->{dbh};
257             ($me->{sql}, $me->{bind_values}) = gen_sql($sub, "update",
258             flavor => _get_flavor($dbh),
259             dbh => $dbh,
260 0   0     0 quirks => $me->{quirks} || $non_object_quirks,
261             );
262 0         0 $SQL = $me->{sql}; @BIND_VALUES = @{$me->{bind_values}};
  0         0  
  0         0  
263 0         0 $dbh->do($me->{sql}, {}, @{$me->{bind_values}});
  0         0  
264             }
265              
266             sub delete
267             {
268 0     0 1 0 my ($moi, $sub) = @_;
269 0 0       0 my $me = ref $moi ? $moi : {};
270              
271 0         0 my $dbh = $me->{dbh};
272             ($me->{sql}, $me->{bind_values}) = gen_sql($sub, "delete",
273             flavor => _get_flavor($dbh),
274             dbh => $dbh,
275 0   0     0 quirks => $me->{quirks} || $non_object_quirks,
276             );
277 0         0 $SQL = $me->{sql}; @BIND_VALUES = @{$me->{bind_values}};
  0         0  
  0         0  
278 0         0 $dbh->do($me->{sql}, {}, @{$me->{bind_values}});
  0         0  
279             }
280              
281             sub insert
282             {
283 0     0 1 0 my ($moi, $table, @rows) = @_;
284 0 0       0 my $me = ref $moi ? $moi : {};
285              
286 0         0 my $dbh = $me->{dbh};
287 0         0 my %sth;
288 0         0 for my $row (@rows) {
289 0         0 my @keys = sort keys %$row;
290 0         0 my $sql = "insert into $table (";
291 0         0 $sql .= join ",", @keys;
292 0         0 $sql .= ") values (";
293 0         0 my (@v, @b);
294 0         0 my $skip_prepare;
295 0         0 for my $v (@$row{@keys}) {
296 0 0       0 if (ref $v eq 'CODE') {
297 0         0 push @v, scalar $v->();
298 0         0 $skip_prepare = 1;
299             } else {
300 0         0 push @v, "?";
301 0         0 push @b, $v;
302             }
303             }
304 0         0 $sql .= join ",", @v;
305 0         0 $sql .= ")";
306 0 0       0 if ($skip_prepare) {
307 0 0       0 return undef unless defined $dbh->do($sql, {}, @b);
308             } else {
309 0         0 my $k = join ";", @keys;
310 0   0     0 $sth{$k} ||= $dbh->prepare($sql);
311 0 0       0 return undef unless defined $sth{$k}->execute(@b);
312             }
313             }
314 0         0 return scalar @rows;
315             }
316              
317             sub sql ($) {
318 0     0 1 0 my $self = shift;
319 0 0 0     0 if (ref $self && $self->isa("DBIx::Perlish")) {
320 0         0 $self->{sql};
321             } else {
322 0     0   0 sub { $self }
323 0         0 }
324             }
325 0 0   0 1 0 sub bind_values { $_[0]->{bind_values} ? @{$_[0]->{bind_values}} : () }
  0         0  
326              
327             sub gen_sql
328             {
329 346     346 0 431827 my ($sub, $operation, %args) = @_;
330              
331 346 100       1579 $args{quirks} = $non_object_quirks unless $args{quirks};
332 346   100     1664 $args{inline} //= 1;
333              
334 346         1383 my $S = DBIx::Perlish::Parse::init(%args, operation => $operation);
335 346         1167 DBIx::Perlish::Parse::parse_sub($S, $sub);
336 289         812 my $sql = "";
337 289         452 my $next_bit = "";
338 289         419 my $nret = 9999;
339 289         656 my $no_aliases;
340             my $dangerous;
341 289         0 my %flags;
342 289 100       691 if ($operation eq "select") {
    100          
    50          
343 261         359 my $nkf = 0;
344 261 100       606 if ($S->{key_fields}) {
345 7         10 $nkf = @{$S->{key_fields}};
  7         15  
346 7 100       16 push @{$args{key_fields}}, @{$S->{key_fields}} if $args{key_fields};
  5         10  
  5         11  
347             }
348 261         451 $sql = "select ";
349 261 100       575 $sql .= "distinct " if $S->{distinct};
350 261 100       519 if ($S->{returns}) {
351 83         143 $sql .= join ", ", @{$S->{returns}};
  83         269  
352 83         133 $nret = @{$S->{returns}};
  83         170  
353 83         182 for my $ret (@{$S->{returns}}) {
  83         171  
354 121 100       411 $nret = 9999 if $ret =~ /\*/;
355             }
356             $flags{returns_dont_care} = 1 if
357 83         490 1 == @{$S->{returns}} &&
358             $S->{returns}->[0] =~ /^(.*)\.\*/ &&
359 83 100 100     149 $S->{returns_dont_care}->{$1}
      100        
360             ;
361             } else {
362 178         478 $sql .= "*";
363             }
364 261         433 $next_bit = " from ";
365 261 100       615 die "all returns are key fields, this is nonsensical\n" if $nkf == $nret;
366             } elsif ($operation eq "delete") {
367 2         4 $no_aliases = 1;
368 2         4 $dangerous = 1;
369 2         3 $next_bit = "delete from ";
370             } elsif ($operation eq "update") {
371 26         40 $no_aliases = 1;
372 26         61 $dangerous = 1;
373 26         44 $next_bit = "update ";
374             } else {
375 0         0 die "unsupported operation: $operation\n";
376             }
377 287         412 my %tabs;
378 287         377 for my $var (keys %{$S->{vars}}) {
  287         1077  
379 191 100       905 $tabs{$S->{var_alias}->{$var}} =
380             $no_aliases ?
381             "$S->{vars}->{$var}" :
382             "$S->{vars}->{$var} $S->{var_alias}->{$var}";
383             }
384 287         522 for my $tab (keys %{$S->{tabs}}) {
  287         889  
385 130 100       585 $tabs{$S->{tab_alias}->{$tab}} =
386             $no_aliases ?
387             "$tab" :
388             "$tab $S->{tab_alias}->{$tab}";
389             }
390 287 100       783 unless (keys %tabs) {
391 15 100 100     93 if ($operation eq "select" && $S->{returns}) {
392 12 100 66     52 if ($args{flavor} && $args{flavor} eq "oracle") {
393 3         9 $tabs{dual} = "dual";
394             } else {
395 9         16 $next_bit = " ";
396             }
397             } else {
398 3         42 die "no tables specified in $operation\n";
399             }
400             }
401 284         455 $sql .= $next_bit;
402 284         374 my %seentab;
403 284         447 my $joins = "";
404 284         380 for my $j ( @{$S->{joins}} ) {
  284         604  
405 25         62 my ($join, $tab1, $tab2, $condition) = @$j;
406 25 100       81 $condition = ( defined $condition) ? " on $condition" : '';
407             die "not sure what to do with repeated tables ($tabs{$tab1} and $tabs{$tab2}) in a join\n"
408 25 100 100     134 if $seentab{$tab1} && $seentab{$tab2};
409 24 100       55 if ($seentab{$tab2}) {
410 2         8 ($tab1, $tab2) = ($tab2, $tab1);
411 2 100       22 if ($join eq "left outer") {
    50          
412 1         2 $join = "right outer";
413             } elsif ($join eq "right outer") {
414 0         0 $join = "left outer";
415             }
416             }
417 24 100       56 if ($seentab{$tab1}) {
418 5 50       12 $joins .= " " if $joins;
419 5         42 $joins .= "$join join $tabs{$tab2}$condition";
420             } else {
421 19 100       34 $joins .= ", " if $joins;
422 19         72 $joins .= "$tabs{$tab1} $join join $tabs{$tab2}$condition";
423             }
424 24         46 $seentab{$tab1}++;
425 24         44 $seentab{$tab2}++;
426             }
427 283 100       667 my @joins = $joins ? ($joins) : ();
428 283         906 $sql .= join ", ", @joins, map { $tabs{$_} } grep { !$seentab{$_} } sort keys %tabs;
  281         860  
  321         949  
429              
430 283         500 my @sets = grep { $_ ne "" } @{$S->{sets}};
  30         93  
  283         590  
431 283         416 my @where = grep { $_ ne "" } @{$S->{where}};
  188         485  
  283         488  
432 283         396 my @having = grep { $_ ne "" } @{$S->{having}};
  1         4  
  283         601  
433 283         404 my @group_by = grep { $_ ne "" } @{$S->{group_by}};
  3         18  
  283         515  
434 283         362 my @order_by = grep { $_ ne "" } @{$S->{order_by}};
  10         27  
  283         472  
435              
436 283 100 100     811 if ($S->{autogroup_needed} && !$S->{no_autogroup} &&
      100        
      100        
437 3         22 !@group_by && @{$S->{autogroup_by}})
438             {
439 2         4 @group_by = grep { $_ ne "" } @{$S->{autogroup_by}};
  3         9  
  2         6  
440             }
441 283 100 100     801 die "nothing to update\n" if $operation eq "update" && !@sets;
442              
443 282 100       741 $sql .= " set " . join ", ", @sets if @sets;
444 282 100       857 $sql .= " where " . join " and ", @where if @where;
445 282 100       621 $sql .= " group by " . join ", ", @group_by if @group_by;
446 282 100       524 $sql .= " having " . join " and ", @having if @having;
447 282 100       513 $sql .= " order by " . join ", ", @order_by if @order_by;
448              
449 282 100 100     694 if ($dangerous && !@where && !$S->{seen_exec}) {
      100        
450 2         33 die "unfiltered $operation is dangerous: use exec if you want it\n";
451             }
452              
453 280   66     1092 my $use_rownum = $args{flavor} && $args{flavor} eq "oracle";
454              
455 280 100       607 unless ($use_rownum) {
456 268 100       575 if ($S->{limit}) {
457 6         17 $sql .= " limit $S->{limit}";
458             }
459 268 100       526 if ($S->{offset}) {
460 4         12 $sql .= " offset $S->{offset}";
461             }
462             }
463 280         472 my $v = $S->{set_values};
464 280         492 push @$v, @{$S->{ret_values}};
  280         520  
465 280         405 push @$v, @{$S->{join_values}};
  280         402  
466 280         396 push @$v, @{$S->{values}};
  280         476  
467              
468 280         414 for my $add (@{$S->{additions}}) {
  280         601  
469 8         33 $sql .= " $add->{type} $add->{sql}";
470 8         13 push @$v, @{$add->{vals}};
  8         16  
471             }
472 280         1523 $sql =~ s/\s+$//;
473              
474 280 100 66     663 if ( $use_rownum && ( $S->{limit} || $S->{offset} )) {
      66        
475 2         10 my @p;
476 2 100       14 push @p, "ROWNUM > " . $S->{offset} if $S->{offset};
477 2 50 100     12 push @p, "ROWNUM <= " . ($S->{limit} + ($S->{offset} // 0)) if $S->{limit};
478 2         12 $sql = "select * from ($sql) where " . join(' and ', @p);
479             }
480              
481 280         4245 return ($sql, $v, $nret, %flags);
482             }
483              
484              
485             1;
486             __END__