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   727263 use 5.014;
  26         266  
4 26     26   130 use warnings;
  26         53  
  26         707  
5 26     26   135 use strict;
  26         73  
  26         724  
6 26     26   166 use Carp;
  26         75  
  26         2540  
7              
8 26     26   176 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS $SQL @BIND_VALUES);
  26         58  
  26         2983  
9             require Exporter;
10 26     26   171 use base 'Exporter';
  26         48  
  26         3733  
11 26     26   12023 use Keyword::Pluggable;
  26         632463  
  26         1738  
12              
13             $VERSION = '1.05';
14             @EXPORT = qw(sql);
15             @EXPORT_OK = qw(union intersect except subselect);
16             %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
17              
18 26     26   15295 use DBIx::Perlish::Parse;
  26         82  
  26         7881  
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 3609 return 1 if $^V lt 5.22.0;
31 3         14 return 2;
32             }
33              
34             sub lexify
35             {
36 56     56 0 134 my ( $text, $insert ) = @_;
37 56 100       291 $insert .= 'sub ' if $$text =~ /^\s*\{/;
38 56         6633 substr($$text, 0, 0, $insert);
39             }
40              
41             sub import
42             {
43 28     28   2162 my $pkg = caller;
44 28         86 local @EXPORT_OK = @EXPORT_OK;
45 28         114 local %EXPORT_TAGS = %EXPORT_TAGS;
46 28 50 33     499 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         66 my @shift;
52 28 100       126 @shift = (shift()) if @_ % 2;
53 28         93 my %p = @_;
54 28 100 66     156 if ($p{prefix} && $p{prefix} =~ /^[a-zA-Z_]\w*$/) {
55 26     26   218 no strict 'refs';
  26         63  
  26         12641  
56 2 50 33     23 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         40 *{$pkg."::$p{prefix}_fetch"} =
59 2         14 *{$pkg."::$p{prefix}_select"} =
60 2     0   10 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         7 *{$pkg."::$p{prefix}_delete"} =
64 2     0   10 sub (&) { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->delete(@_) };
  0         0  
  0         0  
65 2         9 *{$pkg."::$p{prefix}_insert"} =
66 2     0   8 sub { my $o = DBIx::Perlish->new(dbh => $$dbhref); $o->insert(@_) };
  0         0  
  0         0  
67 2         175 return;
68             }
69             }
70              
71 26   50     173 my $prefix = delete($p{prefix}) // 'db';
72 26   100     124 my $dbh = delete($p{dbh}) // '$dbh';
73 26         85 my $iprefix = '__' . $dbh . '_execute_perlish';
74 26         185 $iprefix =~ s/\W//g;
75              
76 26         206 for (
77             [fetch => " $dbh, q(fetch), "],
78             [select => " $dbh, q(fetch), "],
79             [update => " $dbh, q(update), "],
80             [delete => " $dbh, q(delete), "],
81             ) {
82 104         2199 my ($name, $code) = @$_;
83             Keyword::Pluggable::define
84             keyword => $prefix . '_' . $name,
85 56     56   4672 code => sub { lexify( $_[0], $iprefix.$code ) },
86 104         524 expression => 1,
87             package => $pkg
88             ;
89             }
90             Keyword::Pluggable::define
91 26         656 keyword => $prefix . '_insert',
92             code => $iprefix . "_insert $dbh, ",
93             expression => 1,
94             package => $pkg
95             ;
96              
97             {
98 26     26   253 no strict 'refs';
  26         53  
  26         82814  
  26         605  
99 26         181 *{$pkg."::${iprefix}"} = sub ($$&) {
100 3     3   25 my ( $dbh, $method, $sub ) = @_;
101 3         14 my $o = DBIx::Perlish->new(dbh => $dbh);
102 3         10 $o->$method($sub);
103 26         113 };
104 26         136 *{$pkg."::${iprefix}_insert"} = sub {
105 0     0   0 my $o = DBIx::Perlish->new(dbh => shift);
106 0         0 $o->insert(@_)
107 26         97 };
108             }
109 26         5266 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 946 my ($class, %p) = @_;
117 5 100       27 unless (UNIVERSAL::isa($p{dbh}, "DBI::db")) { # XXX maybe relax for other things?
118 1         10 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     45 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         22 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     16 my $dbh = tied(%$real_dbh) || $real_dbh;
156 3         11 return lc $dbh->{Driver}{Name};
157             }
158              
159             sub gen_sql_select
160             {
161 3     3 0 5 my ($moi, $sub) = @_;
162 3 50       8 my $me = ref $moi ? $moi : {};
163              
164 3         5 my $dbh = $me->{dbh};
165 3         4 my @kf;
166 3         6 my $flavor = _get_flavor($dbh);
167 3     0   9 my $kf_convert = sub { return $_[0] };
  0         0  
168 3 0 33     11 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     13 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         20 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       9 my $me = ref $moi ? $moi : {};
198              
199 3         5 my $nret;
200 3         8 my $dbh = $me->{dbh};
201 3         5 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         5  
  3         6  
205              
206 3 50       7 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     7 my $r = $dbh->selectcol_arrayref($me->{sql}, {}, @{$me->{bind_values}}) || [];
245 3 50       81 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 344     344 0 433648 my ($sub, $operation, %args) = @_;
330              
331 344 100       1240 $args{quirks} = $non_object_quirks unless $args{quirks};
332 344   100     1638 $args{inline} //= 1;
333              
334 344         1406 my $S = DBIx::Perlish::Parse::init(%args, operation => $operation);
335 344         1227 DBIx::Perlish::Parse::parse_sub($S, $sub);
336 287         815 my $sql = "";
337 287         471 my $next_bit = "";
338 287         392 my $nret = 9999;
339 287         671 my $no_aliases;
340             my $dangerous;
341 287         0 my %flags;
342 287 100       641 if ($operation eq "select") {
    100          
    50          
343 259         363 my $nkf = 0;
344 259 100       632 if ($S->{key_fields}) {
345 7         10 $nkf = @{$S->{key_fields}};
  7         13  
346 7 100       17 push @{$args{key_fields}}, @{$S->{key_fields}} if $args{key_fields};
  5         10  
  5         11  
347             }
348 259         422 $sql = "select ";
349 259 100       602 $sql .= "distinct " if $S->{distinct};
350 259 100       543 if ($S->{returns}) {
351 83         142 $sql .= join ", ", @{$S->{returns}};
  83         286  
352 83         190 $nret = @{$S->{returns}};
  83         151  
353 83         161 for my $ret (@{$S->{returns}}) {
  83         187  
354 121 100       408 $nret = 9999 if $ret =~ /\*/;
355             }
356             $flags{returns_dont_care} = 1 if
357 83         505 1 == @{$S->{returns}} &&
358             $S->{returns}->[0] =~ /^(.*)\.\*/ &&
359 83 100 100     178 $S->{returns_dont_care}->{$1}
      100        
360             ;
361             } else {
362 176         405 $sql .= "*";
363             }
364 259         411 $next_bit = " from ";
365 259 100       679 die "all returns are key fields, this is nonsensical\n" if $nkf == $nret;
366             } elsif ($operation eq "delete") {
367 2         2 $no_aliases = 1;
368 2         3 $dangerous = 1;
369 2         3 $next_bit = "delete from ";
370             } elsif ($operation eq "update") {
371 26         38 $no_aliases = 1;
372 26         36 $dangerous = 1;
373 26         41 $next_bit = "update ";
374             } else {
375 0         0 die "unsupported operation: $operation\n";
376             }
377 285         435 my %tabs;
378 285         437 for my $var (keys %{$S->{vars}}) {
  285         1077  
379 189 100       954 $tabs{$S->{var_alias}->{$var}} =
380             $no_aliases ?
381             "$S->{vars}->{$var}" :
382             "$S->{vars}->{$var} $S->{var_alias}->{$var}";
383             }
384 285         500 for my $tab (keys %{$S->{tabs}}) {
  285         835  
385 130 100       553 $tabs{$S->{tab_alias}->{$tab}} =
386             $no_aliases ?
387             "$tab" :
388             "$tab $S->{tab_alias}->{$tab}";
389             }
390 285 100       822 unless (keys %tabs) {
391 15 100 100     69 if ($operation eq "select" && $S->{returns}) {
392 12 100 66     59 if ($args{flavor} && $args{flavor} eq "oracle") {
393 3         8 $tabs{dual} = "dual";
394             } else {
395 9         18 $next_bit = " ";
396             }
397             } else {
398 3         36 die "no tables specified in $operation\n";
399             }
400             }
401 282         493 $sql .= $next_bit;
402 282         368 my %seentab;
403 282         433 my $joins = "";
404 282         397 for my $j ( @{$S->{joins}} ) {
  282         573  
405 25         63 my ($join, $tab1, $tab2, $condition) = @$j;
406 25 100       79 $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     99 if $seentab{$tab1} && $seentab{$tab2};
409 24 100       49 if ($seentab{$tab2}) {
410 2         8 ($tab1, $tab2) = ($tab2, $tab1);
411 2 100       10 if ($join eq "left outer") {
    50          
412 1         5 $join = "right outer";
413             } elsif ($join eq "right outer") {
414 0         0 $join = "left outer";
415             }
416             }
417 24 100       47 if ($seentab{$tab1}) {
418 5 50       15 $joins .= " " if $joins;
419 5         18 $joins .= "$join join $tabs{$tab2}$condition";
420             } else {
421 19 100       36 $joins .= ", " if $joins;
422 19         64 $joins .= "$tabs{$tab1} $join join $tabs{$tab2}$condition";
423             }
424 24         45 $seentab{$tab1}++;
425 24         48 $seentab{$tab2}++;
426             }
427 281 100       720 my @joins = $joins ? ($joins) : ();
428 281         934 $sql .= join ", ", @joins, map { $tabs{$_} } grep { !$seentab{$_} } sort keys %tabs;
  279         887  
  319         979  
429              
430 281         538 my @sets = grep { $_ ne "" } @{$S->{sets}};
  30         76  
  281         635  
431 281         422 my @where = grep { $_ ne "" } @{$S->{where}};
  186         490  
  281         494  
432 281         471 my @having = grep { $_ ne "" } @{$S->{having}};
  1         4  
  281         670  
433 281         398 my @group_by = grep { $_ ne "" } @{$S->{group_by}};
  3         10  
  281         503  
434 281         398 my @order_by = grep { $_ ne "" } @{$S->{order_by}};
  10         26  
  281         503  
435              
436 281 100 100     803 if ($S->{autogroup_needed} && !$S->{no_autogroup} &&
      100        
      100        
437 3         10 !@group_by && @{$S->{autogroup_by}})
438             {
439 2         3 @group_by = grep { $_ ne "" } @{$S->{autogroup_by}};
  3         9  
  2         16  
440             }
441 281 100 100     783 die "nothing to update\n" if $operation eq "update" && !@sets;
442              
443 280 100       649 $sql .= " set " . join ", ", @sets if @sets;
444 280 100       886 $sql .= " where " . join " and ", @where if @where;
445 280 100       595 $sql .= " group by " . join ", ", @group_by if @group_by;
446 280 100       573 $sql .= " having " . join " and ", @having if @having;
447 280 100       538 $sql .= " order by " . join ", ", @order_by if @order_by;
448              
449 280 100 100     702 if ($dangerous && !@where && !$S->{seen_exec}) {
      100        
450 2         28 die "unfiltered $operation is dangerous: use exec if you want it\n";
451             }
452              
453 278   66     1106 my $use_rownum = $args{flavor} && $args{flavor} eq "oracle";
454              
455 278 100       641 unless ($use_rownum) {
456 266 100       583 if ($S->{limit}) {
457 6         18 $sql .= " limit $S->{limit}";
458             }
459 266 100       558 if ($S->{offset}) {
460 4         10 $sql .= " offset $S->{offset}";
461             }
462             }
463 278         433 my $v = $S->{set_values};
464 278         447 push @$v, @{$S->{ret_values}};
  278         520  
465 278         427 push @$v, @{$S->{join_values}};
  278         468  
466 278         419 push @$v, @{$S->{values}};
  278         476  
467              
468 278         379 for my $add (@{$S->{additions}}) {
  278         578  
469 8         27 $sql .= " $add->{type} $add->{sql}";
470 8         12 push @$v, @{$add->{vals}};
  8         17  
471             }
472 278         1559 $sql =~ s/\s+$//;
473              
474 278 100 66     704 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     13 push @p, "ROWNUM <= " . ($S->{limit} + ($S->{offset} // 0)) if $S->{limit};
478 2         8 $sql = "select * from ($sql) where " . join(' and ', @p);
479             }
480              
481 278         4309 return ($sql, $v, $nret, %flags);
482             }
483              
484              
485             1;
486             __END__