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 26     26   807902 use 5.014;
  26         271  
4 26     26   152 use warnings;
  26         75  
  26         926  
5 26     26   156 use strict;
  26         52  
  26         792  
6 26     26   137 use Carp;
  26         53  
  26         2567  
7              
8 26     26   193 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS $SQL @BIND_VALUES);
  26         51  
  26         2996  
9             require Exporter;
10 26     26   180 use base 'Exporter';
  26         61  
  26         2343  
11 26     26   12145 use Keyword::Pluggable;
  26         654335  
  26         1887  
12              
13             $VERSION = '1.06';
14             @EXPORT = qw(sql);
15             @EXPORT_OK = qw(union intersect except subselect);
16             %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
17              
18 26     26   16136 use DBIx::Perlish::Parse;
  26         96  
  26         8436  
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 3566 return 1 if $^V lt 5.22.0;
31 3         14 return 2;
32             }
33              
34             sub lexify
35             {
36 56     56 0 126 my ( $text, $insert ) = @_;
37 56 100       331 $insert .= 'sub ' if $$text =~ /^\s*\{/;
38 56         7822 substr($$text, 0, 0, $insert);
39             }
40              
41             sub import
42             {
43 28     28   2108 my $pkg = caller;
44 28         94 local @EXPORT_OK = @EXPORT_OK;
45 28         115 local %EXPORT_TAGS = %EXPORT_TAGS;
46 28 50 33     488 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 28         75 my @shift;
52 28 100       124 @shift = (shift()) if @_ % 2;
53 28         99 my %p = @_;
54 28 100 66     131 if ($p{prefix} && $p{prefix} =~ /^[a-zA-Z_]\w*$/) {
55 26     26   223 no strict 'refs';
  26         69  
  26         12595  
56 2 50 33     18 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         78 *{$pkg."::$p{prefix}_fetch"} =
59 2         11 *{$pkg."::$p{prefix}_select"} =
60 2     0   9 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->fetch(@_) };
  0         0  
  0         0  
61 2         8 *{$pkg."::$p{prefix}_update"} =
62 2     0   11 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->update(@_) };
  0         0  
  0         0  
63 2         8 *{$pkg."::$p{prefix}_delete"} =
64 2     0   6 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->delete(@_) };
  0         0  
  0         0  
65 2         6 *{$pkg."::$p{prefix}_insert"} =
66 2     0   6 sub { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->insert(@_) };
  0         0  
  0         0  
67 2         166 return;
68             }
69             }
70              
71 26   50     167 my $prefix = delete($p{prefix}) // 'db';
72 26   100     119 my $dbh = delete($p{dbh}) // '$dbh';
73 26         87 my $iprefix = '__' . $dbh . '_execute_perlish';
74 26         180 $iprefix =~ s/\W//g;
75              
76 26         174 for (
77             [fetch => " $dbh, q(fetch), "],
78             [select => " $dbh, q(fetch), "],
79             [update => " $dbh, q(update), "],
80             [delete => " $dbh, q(delete), "],
81             ) {
82 104         2262 my ($name, $code) = @$_;
83             Keyword::Pluggable::define
84             keyword => $prefix . '_' . $name,
85 56     56   4710 code => sub { lexify( $_[0], $iprefix.$code ) },
86 104         523 expression => 1,
87             package => $pkg
88             ;
89             }
90             Keyword::Pluggable::define
91 26         663 keyword => $prefix . '_insert',
92             code => $iprefix . "_insert $dbh, ",
93             expression => 1,
94             package => $pkg
95             ;
96              
97             {
98 26     26   212 no strict 'refs';
  26         54  
  26         85869  
  26         606  
99 26         170 *{$pkg."::${iprefix}"} = sub ($$&) {
100 3     3   26 my ( $dbh, $method, $sub ) = @_;
101 3         16 my $o = DBIx::Perlish->new(dbh => $dbh);
102 3         12 $o->$method($sub);
103 26         105 };
104 26         144 *{$pkg."::${iprefix}_insert"} = sub {
105 0     0   0 my $o = DBIx::Perlish->new(dbh => shift);
106 0         0 $o->insert(@_)
107 26         158 };
108             }
109 26         5267 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 902 my ($class, %p) = @_;
117 5 100       31 unless (UNIVERSAL::isa($p{dbh}, "DBI::db")) { # XXX maybe relax for other things?
118 1         7 die "Invalid database handle supplied in the \"dbh\" parameter.\n";
119             }
120 4         17 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         18 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   5 my ($real_dbh) = @_;
155 3   33     15 my $dbh = tied(%$real_dbh) || $real_dbh;
156 3         14 return lc $dbh->{Driver}{Name};
157             }
158              
159             sub gen_sql_select
160             {
161 3     3 0 7 my ($moi, $sub) = @_;
162 3 50       7 my $me = ref $moi ? $moi : {};
163              
164 3         5 my $dbh = $me->{dbh};
165 3         5 my @kf;
166 3         8 my $flavor = _get_flavor($dbh);
167 3     0   11 my $kf_convert = sub { return $_[0] };
  0         0  
168 3 0 33     10 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     14 quirks => $me->{quirks} || $non_object_quirks,
179             key_fields => \@kf,
180             kf_convert => $kf_convert,
181             );
182 3 50       10 $flags{key_fields} = \@kf if @kf;
183 3         21 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 7 my ($moi, $sub) = @_;
197 3 50       8 my $me = ref $moi ? $moi : {};
198              
199 3         5 my $nret;
200 3         8 my $dbh = $me->{dbh};
201 3         6 my %flags;
202              
203 3         8 ($me->{sql}, $me->{bind_values}, $nret, %flags) = $me->gen_sql_select($sub);
204 3         8 $SQL = $me->{sql}; @BIND_VALUES = @{$me->{bind_values}};
  3         4  
  3         7  
205              
206 3 50       8 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       10 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     11 my $r = $dbh->selectcol_arrayref($me->{sql}, {}, @{$me->{bind_values}}) || [];
245 3 50       51 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 454201 my ($sub, $operation, %args) = @_;
330              
331 346 100       1273 $args{quirks} = $non_object_quirks unless $args{quirks};
332 346   100     1673 $args{inline} //= 1;
333              
334 346         1487 my $S = DBIx::Perlish::Parse::init(%args, operation => $operation);
335 346         1185 DBIx::Perlish::Parse::parse_sub($S, $sub);
336 289         832 my $sql = "";
337 289         457 my $next_bit = "";
338 289         419 my $nret = 9999;
339 289         691 my $no_aliases;
340             my $dangerous;
341 289         0 my %flags;
342 289 100       692 if ($operation eq "select") {
    100          
    50          
343 261         426 my $nkf = 0;
344 261 100       609 if ($S->{key_fields}) {
345 7         11 $nkf = @{$S->{key_fields}};
  7         12  
346 7 100       18 push @{$args{key_fields}}, @{$S->{key_fields}} if $args{key_fields};
  5         9  
  5         12  
347             }
348 261         423 $sql = "select ";
349 261 100       589 $sql .= "distinct " if $S->{distinct};
350 261 100       565 if ($S->{returns}) {
351 83         151 $sql .= join ", ", @{$S->{returns}};
  83         288  
352 83         146 $nret = @{$S->{returns}};
  83         172  
353 83         153 for my $ret (@{$S->{returns}}) {
  83         182  
354 121 100       356 $nret = 9999 if $ret =~ /\*/;
355             }
356             $flags{returns_dont_care} = 1 if
357 83         523 1 == @{$S->{returns}} &&
358             $S->{returns}->[0] =~ /^(.*)\.\*/ &&
359 83 100 100     217 $S->{returns_dont_care}->{$1}
      100        
360             ;
361             } else {
362 178         408 $sql .= "*";
363             }
364 261         419 $next_bit = " from ";
365 261 100       644 die "all returns are key fields, this is nonsensical\n" if $nkf == $nret;
366             } elsif ($operation eq "delete") {
367 2         3 $no_aliases = 1;
368 2         4 $dangerous = 1;
369 2         3 $next_bit = "delete from ";
370             } elsif ($operation eq "update") {
371 26         42 $no_aliases = 1;
372 26         37 $dangerous = 1;
373 26         42 $next_bit = "update ";
374             } else {
375 0         0 die "unsupported operation: $operation\n";
376             }
377 287         433 my %tabs;
378 287         421 for my $var (keys %{$S->{vars}}) {
  287         1072  
379 191 100       986 $tabs{$S->{var_alias}->{$var}} =
380             $no_aliases ?
381             "$S->{vars}->{$var}" :
382             "$S->{vars}->{$var} $S->{var_alias}->{$var}";
383             }
384 287         520 for my $tab (keys %{$S->{tabs}}) {
  287         884  
385 130 100       580 $tabs{$S->{tab_alias}->{$tab}} =
386             $no_aliases ?
387             "$tab" :
388             "$tab $S->{tab_alias}->{$tab}";
389             }
390 287 100       798 unless (keys %tabs) {
391 15 100 100     81 if ($operation eq "select" && $S->{returns}) {
392 12 100 66     63 if ($args{flavor} && $args{flavor} eq "oracle") {
393 3         9 $tabs{dual} = "dual";
394             } else {
395 9         20 $next_bit = " ";
396             }
397             } else {
398 3         45 die "no tables specified in $operation\n";
399             }
400             }
401 284         483 $sql .= $next_bit;
402 284         389 my %seentab;
403 284         511 my $joins = "";
404 284         427 for my $j ( @{$S->{joins}} ) {
  284         619  
405 25         66 my ($join, $tab1, $tab2, $condition) = @$j;
406 25 100       63 $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     126 if $seentab{$tab1} && $seentab{$tab2};
409 24 100       51 if ($seentab{$tab2}) {
410 2         7 ($tab1, $tab2) = ($tab2, $tab1);
411 2 100       9 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       44 if ($seentab{$tab1}) {
418 5 50       13 $joins .= " " if $joins;
419 5         15 $joins .= "$join join $tabs{$tab2}$condition";
420             } else {
421 19 100       35 $joins .= ", " if $joins;
422 19         65 $joins .= "$tabs{$tab1} $join join $tabs{$tab2}$condition";
423             }
424 24         41 $seentab{$tab1}++;
425 24         48 $seentab{$tab2}++;
426             }
427 283 100       700 my @joins = $joins ? ($joins) : ();
428 283         971 $sql .= join ", ", @joins, map { $tabs{$_} } grep { !$seentab{$_} } sort keys %tabs;
  281         902  
  321         1001  
429              
430 283         584 my @sets = grep { $_ ne "" } @{$S->{sets}};
  30         75  
  283         611  
431 283         423 my @where = grep { $_ ne "" } @{$S->{where}};
  188         488  
  283         497  
432 283         426 my @having = grep { $_ ne "" } @{$S->{having}};
  1         4  
  283         697  
433 283         465 my @group_by = grep { $_ ne "" } @{$S->{group_by}};
  3         10  
  283         508  
434 283         393 my @order_by = grep { $_ ne "" } @{$S->{order_by}};
  10         27  
  283         514  
435              
436 283 100 100     834 if ($S->{autogroup_needed} && !$S->{no_autogroup} &&
      100        
      100        
437 3         12 !@group_by && @{$S->{autogroup_by}})
438             {
439 2         5 @group_by = grep { $_ ne "" } @{$S->{autogroup_by}};
  3         9  
  2         4  
440             }
441 283 100 100     844 die "nothing to update\n" if $operation eq "update" && !@sets;
442              
443 282 100       624 $sql .= " set " . join ", ", @sets if @sets;
444 282 100       913 $sql .= " where " . join " and ", @where if @where;
445 282 100       592 $sql .= " group by " . join ", ", @group_by if @group_by;
446 282 100       566 $sql .= " having " . join " and ", @having if @having;
447 282 100       571 $sql .= " order by " . join ", ", @order_by if @order_by;
448              
449 282 100 100     726 if ($dangerous && !@where && !$S->{seen_exec}) {
      100        
450 2         34 die "unfiltered $operation is dangerous: use exec if you want it\n";
451             }
452              
453 280   66     1260 my $use_rownum = $args{flavor} && $args{flavor} eq "oracle";
454              
455 280 100       586 unless ($use_rownum) {
456 268 100       606 if ($S->{limit}) {
457 6         26 $sql .= " limit $S->{limit}";
458             }
459 268 100       589 if ($S->{offset}) {
460 4         10 $sql .= " offset $S->{offset}";
461             }
462             }
463 280         459 my $v = $S->{set_values};
464 280         464 push @$v, @{$S->{ret_values}};
  280         496  
465 280         424 push @$v, @{$S->{join_values}};
  280         446  
466 280         430 push @$v, @{$S->{values}};
  280         492  
467              
468 280         395 for my $add (@{$S->{additions}}) {
  280         609  
469 8         27 $sql .= " $add->{type} $add->{sql}";
470 8         11 push @$v, @{$add->{vals}};
  8         16  
471             }
472 280         1580 $sql =~ s/\s+$//;
473              
474 280 100 66     746 if ( $use_rownum && ( $S->{limit} || $S->{offset} )) {
      66        
475 2         3 my @p;
476 2 100       8 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         9 $sql = "select * from ($sql) where " . join(' and ', @p);
479             }
480              
481 280         4776 return ($sql, $v, $nret, %flags);
482             }
483              
484              
485             1;
486             __END__