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   1762824 use 5.014;
  26         112  
4 26     26   272 use warnings;
  26         121  
  26         1840  
5 26     26   225 use strict;
  26         88  
  26         944  
6 26     26   143 use Carp;
  26         107  
  26         2776  
7              
8 26     26   159 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS $SQL @BIND_VALUES);
  26         53  
  26         2865  
9             require Exporter;
10 26     26   160 use base 'Exporter';
  26         43  
  26         3065  
11 26     26   13947 use Keyword::Pluggable;
  26         48429  
  26         2016  
12              
13             $VERSION = '1.08';
14             @EXPORT = qw(sql);
15             @EXPORT_OK = qw(union intersect except subselect);
16             %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
17              
18 26     26   18775 use DBIx::Perlish::Parse;
  26         119  
  26         12744  
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 377234 return 1 if $^V lt 5.22.0;
31 3         15 return 2;
32             }
33              
34             sub lexify
35             {
36 32     32 0 88 my ( $text, $insert ) = @_;
37 32 100       269 $insert .= 'sub ' if $$text =~ /^\s*\{/;
38 32         6731 substr($$text, 0, 0, $insert);
39             }
40              
41             sub import
42             {
43 27     27   178369 my $pkg = caller;
44 27         98 local @EXPORT_OK = @EXPORT_OK;
45 27         106 local %EXPORT_TAGS = %EXPORT_TAGS;
46 27 50 33     567 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         63 my @shift;
52 27 100       108 @shift = (shift()) if @_ % 2;
53 27         89 my %p = @_;
54 27 100 66     170 if ($p{prefix} && $p{prefix} =~ /^[a-zA-Z_]\w*$/) {
55 26     26   246 no strict 'refs';
  26         47  
  26         16876  
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         3 my $dbhref = $p{dbh};
58 2         8 *{$pkg."::$p{prefix}_fetch"} =
59 2         9 *{$pkg."::$p{prefix}_select"} =
60 2     0   9 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->fetch(@_) };
  0         0  
  0         0  
61 2         5 *{$pkg."::$p{prefix}_update"} =
62 2     0   18 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->update(@_) };
  0         0  
  0         0  
63 2         5 *{$pkg."::$p{prefix}_delete"} =
64 2     0   5 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->delete(@_) };
  0         0  
  0         0  
65 2         9 *{$pkg."::$p{prefix}_insert"} =
66 2     0   4 sub { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->insert(@_) };
  0         0  
  0         0  
67 2         145 return;
68             }
69             }
70              
71 25   50     167 my $prefix = delete($p{prefix}) // 'db';
72 25   100     166 my $dbh = delete($p{dbh}) // '$dbh';
73 25         66 my $iprefix = '__' . $dbh . '_execute_perlish';
74 25         187 $iprefix =~ s/\W//g;
75              
76 25         172 for (
77             [fetch => " $dbh, q(fetch), "],
78             [select => " $dbh, q(fetch), "],
79             [update => " $dbh, q(update), "],
80             [delete => " $dbh, q(delete), "],
81             ) {
82 100         2834 my ($name, $code) = @$_;
83             Keyword::Pluggable::define
84             keyword => $prefix . '_' . $name,
85 32     32   4095 code => sub { lexify( $_[0], $iprefix.$code ) },
86 100         539 expression => 1,
87             package => $pkg
88             ;
89             }
90             Keyword::Pluggable::define
91 25         770 keyword => $prefix . '_insert',
92             code => $iprefix . "_insert $dbh, ",
93             expression => 1,
94             package => $pkg
95             ;
96              
97             {
98 26     26   2359 no strict 'refs';
  26         48  
  26         114811  
  25         707  
99 25         183 *{$pkg."::${iprefix}"} = sub ($$&) {
100 3     3   22 my ( $dbh, $method, $sub ) = @_;
101 3         12 my $o = DBIx::Perlish->new(dbh => $dbh);
102 3         11 $o->$method($sub);
103 25         116 };
104 25         130 *{$pkg."::${iprefix}_insert"} = sub {
105 0     0   0 my $o = DBIx::Perlish->new(dbh => shift);
106 0         0 $o->insert(@_)
107 25         119 };
108             }
109 25         5199 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 265425 my ($class, %p) = @_;
117 5 100       55 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     13 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         13 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     13 my $dbh = tied(%$real_dbh) || $real_dbh;
156 3         9 return lc $dbh->{Driver}{Name};
157             }
158              
159             sub gen_sql_select
160             {
161 3     3 0 7 my ($moi, $sub) = @_;
162 3 50       5 my $me = ref $moi ? $moi : {};
163              
164 3         5 my $dbh = $me->{dbh};
165 3         5 my @kf;
166 3         9 my $flavor = _get_flavor($dbh);
167 3     0   10 my $kf_convert = sub { return $_[0] };
  0         0  
168 3 0 33     9 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     15 quirks => $me->{quirks} || $non_object_quirks,
179             key_fields => \@kf,
180             kf_convert => $kf_convert,
181             );
182 3 50       9 $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 6 my ($moi, $sub) = @_;
197 3 50       7 my $me = ref $moi ? $moi : {};
198              
199 3         4 my $nret;
200 3         6 my $dbh = $me->{dbh};
201 3         4 my %flags;
202              
203 3         8 ($me->{sql}, $me->{bind_values}, $nret, %flags) = $me->gen_sql_select($sub);
204 3         6 $SQL = $me->{sql}; @BIND_VALUES = @{$me->{bind_values}};
  3         4  
  3         6  
205              
206 3 50       5 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       6 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     7 my $r = $dbh->selectcol_arrayref($me->{sql}, {}, @{$me->{bind_values}}) || [];
245 3 50       53 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 348     348 0 5353041 my ($sub, $operation, %args) = @_;
330              
331 348 100       2039 $args{quirks} = $non_object_quirks unless $args{quirks};
332 348   100     2286 $args{inline} //= 1;
333              
334 348         2130 my $S = DBIx::Perlish::Parse::init(%args, operation => $operation);
335 348         1691 DBIx::Perlish::Parse::parse_sub($S, $sub);
336 291         1154 my $sql = "";
337 291         560 my $next_bit = "";
338 291         536 my $nret = 9999;
339 291         1009 my $no_aliases;
340             my $dangerous;
341 291         0 my %flags;
342 291 100       1038 if ($operation eq "select") {
    100          
    50          
343 263         501 my $nkf = 0;
344 263 100       983 if ($S->{key_fields}) {
345 7         10 $nkf = @{$S->{key_fields}};
  7         14  
346 7 100       20 push @{$args{key_fields}}, @{$S->{key_fields}} if $args{key_fields};
  5         10  
  5         14  
347             }
348 263         545 $sql = "select ";
349 263 100       797 $sql .= "distinct " if $S->{distinct};
350 263 100       721 if ($S->{returns}) {
351 83         157 $sql .= join ", ", @{$S->{returns}};
  83         384  
352 83         171 $nret = @{$S->{returns}};
  83         186  
353 83         141 for my $ret (@{$S->{returns}}) {
  83         207  
354 121 100       412 $nret = 9999 if $ret =~ /\*/;
355             }
356             $flags{returns_dont_care} = 1 if
357 83         578 1 == @{$S->{returns}} &&
358             $S->{returns}->[0] =~ /^(.*)\.\*/ &&
359 83 100 100     138 $S->{returns_dont_care}->{$1}
      100        
360             ;
361             } else {
362 180         444 $sql .= "*";
363             }
364 263         532 $next_bit = " from ";
365 263 100       811 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         6 $next_bit = "delete from ";
370             } elsif ($operation eq "update") {
371 26         54 $no_aliases = 1;
372 26         43 $dangerous = 1;
373 26         55 $next_bit = "update ";
374             } else {
375 0         0 die "unsupported operation: $operation\n";
376             }
377 289         577 my %tabs;
378 289         549 for my $var (keys %{$S->{vars}}) {
  289         1345  
379 193 100       1303 $tabs{$S->{var_alias}->{$var}} =
380             $no_aliases ?
381             "$S->{vars}->{$var}" :
382             "$S->{vars}->{$var} $S->{var_alias}->{$var}";
383             }
384 289         604 for my $tab (keys %{$S->{tabs}}) {
  289         1109  
385 130 100       716 $tabs{$S->{tab_alias}->{$tab}} =
386             $no_aliases ?
387             "$tab" :
388             "$tab $S->{tab_alias}->{$tab}";
389             }
390 289 100       872 unless (keys %tabs) {
391 15 100 100     77 if ($operation eq "select" && $S->{returns}) {
392 12 100 66     182 if ($args{flavor} && $args{flavor} eq "oracle") {
393 3         10 $tabs{dual} = "dual";
394             } else {
395 9         18 $next_bit = " ";
396             }
397             } else {
398 3         105 die "no tables specified in $operation\n";
399             }
400             }
401 286         582 $sql .= $next_bit;
402 286         541 my %seentab;
403 286         640 my $joins = "";
404 286         479 for my $j ( @{$S->{joins}} ) {
  286         762  
405 25         116 my ($join, $tab1, $tab2, $condition) = @$j;
406 25 100       187 $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     136 if $seentab{$tab1} && $seentab{$tab2};
409 24 100       67 if ($seentab{$tab2}) {
410 2         8 ($tab1, $tab2) = ($tab2, $tab1);
411 2 100       11 if ($join eq "left outer") {
    50          
412 1         3 $join = "right outer";
413             } elsif ($join eq "right outer") {
414 0         0 $join = "left outer";
415             }
416             }
417 24 100       83 if ($seentab{$tab1}) {
418 5 50       21 $joins .= " " if $joins;
419 5         13 $joins .= "$join join $tabs{$tab2}$condition";
420             } else {
421 19 100       54 $joins .= ", " if $joins;
422 19         70 $joins .= "$tabs{$tab1} $join join $tabs{$tab2}$condition";
423             }
424 24         81 $seentab{$tab1}++;
425 24         70 $seentab{$tab2}++;
426             }
427 285 100       972 my @joins = $joins ? ($joins) : ();
428 285         1223 $sql .= join ", ", @joins, map { $tabs{$_} } grep { !$seentab{$_} } sort keys %tabs;
  283         1107  
  323         1181  
429              
430 285         593 my @sets = grep { $_ ne "" } @{$S->{sets}};
  30         89  
  285         801  
431 285         567 my @where = grep { $_ ne "" } @{$S->{where}};
  190         591  
  285         638  
432 285         581 my @having = grep { $_ ne "" } @{$S->{having}};
  1         4  
  285         981  
433 285         516 my @group_by = grep { $_ ne "" } @{$S->{group_by}};
  3         7  
  285         703  
434 285         457 my @order_by = grep { $_ ne "" } @{$S->{order_by}};
  10         35  
  285         743  
435              
436 285 100 100     1066 if ($S->{autogroup_needed} && !$S->{no_autogroup} &&
      100        
      100        
437 3         11 !@group_by && @{$S->{autogroup_by}})
438             {
439 2         3 @group_by = grep { $_ ne "" } @{$S->{autogroup_by}};
  3         8  
  2         7  
440             }
441 285 100 100     1131 die "nothing to update\n" if $operation eq "update" && !@sets;
442              
443 284 100       782 $sql .= " set " . join ", ", @sets if @sets;
444 284 100       1128 $sql .= " where " . join " and ", @where if @where;
445 284 100       642 $sql .= " group by " . join ", ", @group_by if @group_by;
446 284 100       666 $sql .= " having " . join " and ", @having if @having;
447 284 100       681 $sql .= " order by " . join ", ", @order_by if @order_by;
448              
449 284 100 100     1068 if ($dangerous && !@where && !$S->{seen_exec}) {
      100        
450 2         181 die "unfiltered $operation is dangerous: use exec if you want it\n";
451             }
452              
453 282   66     1465 my $use_rownum = $args{flavor} && $args{flavor} eq "oracle";
454              
455 282 100       850 unless ($use_rownum) {
456 270 100       789 if ($S->{limit}) {
457 6         19 $sql .= " limit $S->{limit}";
458             }
459 270 100       719 if ($S->{offset}) {
460 4         13 $sql .= " offset $S->{offset}";
461             }
462             }
463 282         593 my $v = $S->{set_values};
464 282         502 push @$v, @{$S->{ret_values}};
  282         638  
465 282         474 push @$v, @{$S->{join_values}};
  282         566  
466 282         515 push @$v, @{$S->{values}};
  282         617  
467              
468 282         430 for my $add (@{$S->{additions}}) {
  282         800  
469 8         22 $sql .= " $add->{type} $add->{sql}";
470 8         13 push @$v, @{$add->{vals}};
  8         17  
471             }
472 282         2639 $sql =~ s/\s+$//;
473              
474 282 100 66     818 if ( $use_rownum && ( $S->{limit} || $S->{offset} )) {
      66        
475 2         4 my @p;
476 2 100       11 push @p, "ROWNUM > " . $S->{offset} if $S->{offset};
477 2 50 100     21 push @p, "ROWNUM <= " . ($S->{limit} + ($S->{offset} // 0)) if $S->{limit};
478 2         42 $sql = "select * from ($sql) where " . join(' and ', @p);
479             }
480              
481 282         7606 return ($sql, $v, $nret, %flags);
482             }
483              
484              
485             1;
486             __END__