eab3e4ebb3a6c6631f82df811d6c723a24567fe6
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract / Converter.pm
1 package SQL::Abstract::Converter;
2
3 use Carp ();
4 use List::Util ();
5 use Scalar::Util ();
6 use Data::Query::Constants qw(
7   DQ_IDENTIFIER DQ_OPERATOR DQ_VALUE DQ_LITERAL DQ_JOIN DQ_SELECT DQ_ORDER
8   DQ_WHERE DQ_DELETE DQ_UPDATE DQ_INSERT
9 );
10 use Data::Query::ExprHelpers qw(perl_scalar_value);
11 use Moo;
12
13 has renderer_will_quote => (
14   is => 'ro'
15 );
16
17 has lower_case => (
18   is => 'ro' 
19 );
20
21 has default_logic => (
22   is => 'ro', coerce => sub { uc($_[0]) }, default => sub { 'OR' }
23 );
24
25 has bind_meta => (
26   is => 'ro', default => sub { 1 }
27 );
28
29 has cmp => (is => 'ro', default => sub { '=' });
30
31 has sqltrue => (is => 'ro', default => sub { '1=1' });
32 has sqlfalse => (is => 'ro', default => sub { '0=1' });
33
34 has special_ops => (is => 'ro', default => sub { [] });
35
36 # XXX documented but I don't current fail any tests not using it
37 has unary_ops => (is => 'ro', default => sub { [] });
38
39 has injection_guard => (
40   is => 'ro',
41   default => sub {
42     qr/
43       \;
44         |
45       ^ \s* go \s
46     /xmi;
47   }
48 );
49
50 has identifier_sep => (
51   is => 'ro', default => sub { '.' },
52 );
53
54 has always_quote => (is => 'ro', default => sub { 1 });
55
56 has convert => (is => 'ro');
57
58 has array_datatypes => (is => 'ro');
59
60 sub _literal_to_dq {
61   my ($self, $literal) = @_;
62   my @bind;
63   ($literal, @bind) = @$literal if ref($literal) eq 'ARRAY';
64   +{
65     type => DQ_LITERAL,
66     subtype => 'SQL',
67     literal => $literal,
68     (@bind ? (values => [ $self->_bind_to_dq(@bind) ]) : ()),
69   };
70 }
71
72 sub _bind_to_dq {
73   my ($self, @bind) = @_;
74   return unless @bind;
75   $self->bind_meta
76     ? do {
77         $self->_assert_bindval_matches_bindtype(@bind);
78         map perl_scalar_value(reverse @$_), @bind
79       }
80     : map perl_scalar_value($_), @bind
81 }
82
83 sub _value_to_dq {
84   my ($self, $value) = @_;
85   $self->_maybe_convert_dq(perl_scalar_value($value, our $Cur_Col_Meta));
86 }
87
88 sub _ident_to_dq {
89   my ($self, $ident) = @_;
90   $self->_assert_pass_injection_guard($ident)
91     unless $self->renderer_will_quote;
92   $self->_maybe_convert_dq({
93     type => DQ_IDENTIFIER,
94     elements => [ split /\Q${\$self->identifier_sep}/, $ident ],
95   });
96 }
97
98 sub _maybe_convert_dq {
99   my ($self, $dq) = @_;
100   if (my $c = $self->{where_convert}) {
101     +{
102        type => DQ_OPERATOR,
103        operator => { 'SQL.Naive' => 'apply' },
104        args => [
105          { type => DQ_IDENTIFIER, elements => [ $self->_sqlcase($c) ] },
106          $dq
107        ]
108      };
109   } else {
110     $dq;
111   }
112 }
113
114 sub _op_to_dq {
115   my ($self, $op, @args) = @_;
116   $self->_assert_pass_injection_guard($op);
117   +{
118     type => DQ_OPERATOR,
119     operator => { 'SQL.Naive' => $op },
120     args => \@args
121   };
122 }
123
124 sub _assert_pass_injection_guard {
125   if ($_[1] =~ $_[0]->{injection_guard}) {
126     my $class = ref $_[0];
127     die "Possible SQL injection attempt '$_[1]'. If this is indeed a part of the "
128      . "desired SQL use literal SQL ( \'...' or \[ '...' ] ) or supply your own "
129      . "{injection_guard} attribute to ${class}->new()"
130   }
131 }
132
133 sub _insert_to_dq {
134   my ($self, $table, $data, $options) = @_;
135   my (@names, @values);
136   if (ref($data) eq 'HASH') {
137     @names = sort keys %$data;
138     foreach my $k (@names) {
139       local our $Cur_Col_Meta = $k;
140       push @values, $self->_mutation_rhs_to_dq($data->{$k});
141     }
142   } elsif (ref($data) eq 'ARRAY') {
143     local our $Cur_Col_Meta;
144     @values = map $self->_mutation_rhs_to_dq($_), @$data;
145   } else {
146     die "Not handled yet";
147   }
148   my $returning;
149   if (my $r_source = $options->{returning}) {
150     $returning = [
151       map +(ref($_) ? $self->_expr_to_dq($_) : $self->_ident_to_dq($_)),
152         (ref($r_source) eq 'ARRAY' ? @$r_source : $r_source),
153     ];
154   }
155   +{
156     type => DQ_INSERT,
157     target => $self->_table_to_dq($table),
158     (@names ? (names => [ map $self->_ident_to_dq($_), @names ]) : ()),
159     values => [ \@values ],
160     ($returning ? (returning => $returning) : ()),
161   };
162 }
163
164 sub _mutation_rhs_to_dq {
165   my ($self, $v) = @_;
166   if (ref($v) eq 'ARRAY') {
167     if ($self->{array_datatypes}) {
168       return $self->_value_to_dq($v);
169     }
170     $v = \do { my $x = $v };
171   }
172   if (ref($v) eq 'HASH') {
173     my ($op, $arg, @rest) = %$v;
174
175     die 'Operator calls in update/insert must be in the form { -op => $arg }'
176       if (@rest or not $op =~ /^\-(.+)/);
177   }
178   return $self->_expr_to_dq($v);
179 }
180
181 sub _update_to_dq {
182   my ($self, $table, $data, $where) = @_;
183
184   die "Unsupported data type specified to \$sql->update"
185     unless ref $data eq 'HASH';
186
187   my @set;
188
189   foreach my $k (sort keys %$data) {
190     my $v = $data->{$k};
191     local our $Cur_Col_Meta = $k;
192     push @set, [ $self->_ident_to_dq($k), $self->_mutation_rhs_to_dq($v) ];
193   }
194
195   return +{
196     type => DQ_UPDATE,
197     target => $self->_table_to_dq($table),
198     set => \@set,
199     where => $self->_where_to_dq($where),
200   };
201 }
202
203 sub _source_to_dq {
204   my ($self, $table, undef, $where) = @_;
205
206   my $source_dq = $self->_table_to_dq($table);
207
208   if (my $where_dq = $self->_where_to_dq($where)) {
209     $source_dq = {
210       type => DQ_WHERE,
211       from => $source_dq,
212       where => $where_dq,
213     };
214   }
215
216   $source_dq;
217 }
218
219 sub _select_to_dq {
220   my $self = shift;
221   my ($table, $fields, $where, $order) = @_;
222
223   my $source_dq = $self->_source_to_dq(@_);
224
225   my $ordered_dq = do {
226     if ($order) {
227       $self->_order_by_to_dq($order, undef, $source_dq);
228     } else {
229       $source_dq
230     }
231   };
232
233   return $self->_select_select_to_dq($fields, $ordered_dq);
234 }
235
236 sub _select_select_to_dq {
237   my ($self, $fields, $from_dq) = @_;
238
239   $fields ||= '*';
240
241   return +{
242     type => DQ_SELECT,
243     select => $self->_select_field_list_to_dq($fields),
244     from => $from_dq,
245   };
246 }
247
248 sub _select_field_list_to_dq {
249   my ($self, $fields) = @_;
250   [ map $self->_select_field_to_dq($_),
251       ref($fields) eq 'ARRAY' ? @$fields : $fields ];
252 }
253
254 sub _select_field_to_dq {
255   my ($self, $field) = @_;
256   if (my $ref = ref($field)) {
257     if ($ref eq 'REF' and ref($$field) eq 'HASH') {
258       return $$field;
259     } else {
260       return $self->_literal_to_dq($$field);
261     }
262   }
263   return $self->_ident_to_dq($field)
264 }
265
266 sub _delete_to_dq {
267   my ($self, $table, $where) = @_;
268   +{
269     type => DQ_DELETE,
270     target => $self->_table_to_dq($table),
271     where => $self->_where_to_dq($where),
272   }
273 }
274
275 sub _where_to_dq {
276   my ($self, $where, $logic) = @_;
277
278   return undef unless defined($where);
279
280   # if we're given a simple string assume it's a literal
281   return $self->_literal_to_dq($where) if !ref($where);
282
283   # turn the convert misfeature on - only used in WHERE clauses
284   local $self->{where_convert} = $self->convert;
285
286   return $self->_expr_to_dq($where, $logic);
287 }
288
289 sub _expr_to_dq {
290   my ($self, $where, $logic) = @_;
291
292   if (ref($where) eq 'ARRAY') {
293     return $self->_expr_to_dq_ARRAYREF($where, $logic);
294   } elsif (ref($where) eq 'HASH') {
295     return $self->_expr_to_dq_HASHREF($where, $logic);
296   } elsif (
297     ref($where) eq 'SCALAR'
298     or (ref($where) eq 'REF' and ref($$where) eq 'ARRAY')
299   ) {
300     return $self->_literal_to_dq($$where);
301   } elsif (ref($where) eq 'REF' and ref($$where) eq 'HASH') {
302     return $$where;
303   } elsif (!ref($where) or Scalar::Util::blessed($where)) {
304     return $self->_value_to_dq($where);
305   }
306   die "Can't handle $where";
307 }
308
309 sub _expr_to_dq_ARRAYREF {
310   my ($self, $where, $logic) = @_;
311
312   $logic = uc($logic || $self->default_logic || 'OR');
313   $logic eq 'AND' or $logic eq 'OR' or die "unknown logic: $logic";
314
315   return unless @$where;
316
317   my ($first, @rest) = @$where;
318
319   return $self->_expr_to_dq($first) unless @rest;
320
321   my $first_dq = do {
322     if (!ref($first)) {
323       $self->_where_hashpair_to_dq($first => shift(@rest));
324     } else {
325       $self->_expr_to_dq($first);
326     }
327   };
328
329   return $self->_expr_to_dq_ARRAYREF(\@rest, $logic) unless $first_dq;
330
331   $self->_op_to_dq(
332     $logic, $first_dq, $self->_expr_to_dq_ARRAYREF(\@rest, $logic)
333   );
334 }
335
336 sub _expr_to_dq_HASHREF {
337   my ($self, $where, $logic) = @_;
338
339   $logic = uc($logic) if $logic;
340
341   my @dq = map {
342     $self->_where_hashpair_to_dq($_ => $where->{$_}, $logic)
343   } sort keys %$where;
344
345   return $dq[0] unless @dq > 1;
346
347   my $final = pop(@dq);
348
349   foreach my $dq (reverse @dq) {
350     $final = $self->_op_to_dq($logic||'AND', $dq, $final);
351   }
352
353   return $final;
354 }
355
356 sub _where_to_dq_SCALAR {
357   shift->_value_to_dq(@_);
358 }
359
360 sub _apply_to_dq {
361   my ($self, $op, $v) = @_;
362   my @args = map $self->_expr_to_dq($_), (ref($v) eq 'ARRAY' ? @$v : $v);
363
364   # Ok. Welcome to stupid compat code land. An SQLA expr that would in the
365   # absence of this piece of crazy render to:
366   #
367   #   A( B( C( x ) ) )
368   #
369   # such as
370   #
371   #   { -a => { -b => { -c => $x } } }
372   #
373   # actually needs to render to:
374   #
375   #   A( B( C x ) )
376   #
377   # because SQL sucks, and databases are hateful, and SQLA is Just That DWIM.
378   #
379   # However, we don't want to catch 'A(x)' and turn it into 'A x'
380   #
381   # So the way we deal with this is to go through all our arguments, and
382   # then if the argument is -also- an apply, i.e. at least 'B', we check
383   # its arguments - and if there's only one of them, and that isn't an apply,
384   # then we convert to the bareword form. The end result should be:
385   #
386   # A( x )                   -> A( x )
387   # A( B( x ) )              -> A( B x )
388   # A( B( C( x ) ) )         -> A( B( C x ) )
389   # A( B( x + y ) )          -> A( B( x + y ) )
390   # A( B( x, y ) )           -> A( B( x, y ) )
391   #
392   # If this turns out not to be quite right, please add additional tests
393   # to either 01generate.t or 02where.t *and* update this comment.
394
395   foreach my $arg (@args) {
396     if (
397       $arg->{type} eq DQ_OPERATOR and $arg->{operator}{'SQL.Naive'} eq 'apply'
398       and @{$arg->{args}} == 2 and $arg->{args}[1]{type} ne DQ_OPERATOR
399     ) {
400       $arg->{operator}{'SQL.Naive'} = (shift @{$arg->{args}})->{elements}->[0];
401     }
402   }
403   $self->_assert_pass_injection_guard($op);
404   return $self->_op_to_dq(
405     apply => $self->_ident_to_dq($op), @args
406   );
407 }
408
409 sub _where_hashpair_to_dq {
410   my ($self, $k, $v, $logic) = @_;
411
412   if ($k =~ /^-(.*)/s) {
413     my $op = uc($1);
414     if ($op eq 'AND' or $op eq 'OR') {
415       return $self->_expr_to_dq($v, $op);
416     } elsif ($op eq 'NEST') {
417       return $self->_expr_to_dq($v);
418     } elsif ($op eq 'NOT') {
419       return $self->_op_to_dq(NOT => $self->_expr_to_dq($v));
420     } elsif ($op eq 'BOOL') {
421       return ref($v) ? $self->_expr_to_dq($v) : $self->_ident_to_dq($v);
422     } elsif ($op eq 'NOT_BOOL') {
423       return $self->_op_to_dq(
424         NOT => ref($v) ? $self->_expr_to_dq($v) : $self->_ident_to_dq($v)
425       );
426     } elsif ($op eq 'IDENT') {
427       return $self->_ident_to_dq($v);
428     } elsif ($op eq 'VALUE') {
429       return $self->_value_to_dq($v);
430     } elsif ($op =~ /^(?:AND|OR|NEST)_?\d+/) {
431       die "Use of [and|or|nest]_N modifiers is no longer supported";
432     } else {
433       return $self->_apply_to_dq($op, $v);
434     }
435   } else {
436     local our $Cur_Col_Meta = $k;
437     if (ref($v) eq 'ARRAY') {
438       if (!@$v) {
439         return $self->_literal_to_dq($self->{sqlfalse});
440       } elsif (defined($v->[0]) && $v->[0] =~ /-(and|or)/i) {
441         return $self->_expr_to_dq_ARRAYREF([
442           map +{ $k => $_ }, @{$v}[1..$#$v]
443         ], uc($1));
444       }
445       return $self->_expr_to_dq_ARRAYREF([
446         map +{ $k => $_ }, @$v
447       ], $logic);
448     } elsif (ref($v) eq 'SCALAR' or (ref($v) eq 'REF' and ref($$v) eq 'ARRAY')) {
449       return +{
450         type => DQ_LITERAL,
451         subtype => 'SQL',
452         parts => [ $self->_ident_to_dq($k), $self->_literal_to_dq($$v) ]
453       };
454     }
455     my ($op, $rhs) = do {
456       if (ref($v) eq 'HASH') {
457         if (keys %$v > 1) {
458           return $self->_expr_to_dq_ARRAYREF([
459             map +{ $k => { $_ => $v->{$_} } }, sort keys %$v
460           ], $logic||'AND');
461         }
462         my ($op, $value) = %$v;
463         s/^-//, s/_/ /g for $op;
464         if ($op =~ /^(and|or)$/i) {
465           return $self->_expr_to_dq({ $k => $value }, $op);
466         } elsif (
467           my $special_op = List::Util::first {$op =~ $_->{regex}}
468                              @{$self->{special_ops}}
469         ) {
470           return $self->_literal_to_dq(
471             [ $special_op->{handler}->($k, $op, $value) ]
472           );;
473         } elsif ($op =~ /^(?:AND|OR|NEST)_?\d+$/i) {
474           die "Use of [and|or|nest]_N modifiers is no longer supported";
475         }
476         (uc($op), $value);
477       } else {
478         ($self->{cmp}, $v);
479       }
480     };
481     if ($op eq 'BETWEEN' or $op eq 'IN' or $op eq 'NOT IN' or $op eq 'NOT BETWEEN') {
482       if (ref($rhs) ne 'ARRAY') {
483         if ($op =~ /IN$/) {
484           # have to add parens if none present because -in => \"SELECT ..."
485           # got documented. mst hates everything.
486           if (ref($rhs) eq 'SCALAR') {
487             my $x = $$rhs;
488             1 while ($x =~ s/\A\s*\((.*)\)\s*\Z/$1/s);
489             $rhs = \$x;
490           } else {
491             my ($x, @rest) = @{$$rhs};
492             1 while ($x =~ s/\A\s*\((.*)\)\s*\Z/$1/s);
493             $rhs = \[ $x, @rest ];
494           }
495         }
496         return $self->_op_to_dq(
497           $op, $self->_ident_to_dq($k), $self->_literal_to_dq($$rhs)
498         );
499       }
500       return $self->_literal_to_dq($self->{sqlfalse}) unless @$rhs;
501       return $self->_op_to_dq(
502         $op, $self->_ident_to_dq($k), map $self->_expr_to_dq($_), @$rhs
503       )
504     } elsif ($op =~ s/^NOT (?!LIKE)//) {
505       return $self->_where_hashpair_to_dq(-not => { $k => { $op => $rhs } });
506     } elsif ($op eq 'IDENT') {
507       return $self->_op_to_dq(
508         $self->{cmp}, $self->_ident_to_dq($k), $self->_ident_to_dq($rhs)
509       );
510     } elsif ($op eq 'VALUE') {
511       return $self->_op_to_dq(
512         $self->{cmp}, $self->_ident_to_dq($k), $self->_value_to_dq($rhs)
513       );
514     } elsif (!defined($rhs)) {
515       my $null_op = do {
516         if ($op eq '=' or $op eq 'LIKE') {
517           'IS NULL'
518         } elsif ($op eq '!=') {
519           'IS NOT NULL'
520         } else {
521           die "Can't do undef -> NULL transform for operator ${op}";
522         }
523       };
524       return $self->_op_to_dq($null_op, $self->_ident_to_dq($k));
525     }
526     if (ref($rhs) eq 'ARRAY') {
527       if (!@$rhs) {
528         return $self->_literal_to_dq(
529           $op eq '!=' ? $self->{sqltrue} : $self->{sqlfalse}
530         );
531       } elsif (defined($rhs->[0]) and $rhs->[0] =~ /^-(and|or)$/i) {
532         return $self->_expr_to_dq_ARRAYREF([
533           map +{ $k => { $op => $_ } }, @{$rhs}[1..$#$rhs]
534         ], uc($1));
535       } elsif ($op =~ /^-(?:AND|OR|NEST)_?\d+/) {
536         die "Use of [and|or|nest]_N modifiers is no longer supported";
537       }
538       return $self->_expr_to_dq_ARRAYREF([
539         map +{ $k => { $op => $_ } }, @$rhs
540       ]);
541     }
542     return $self->_op_to_dq(
543       $op, $self->_ident_to_dq($k), $self->_expr_to_dq($rhs)
544     );
545   }
546 }
547
548 sub _order_by_to_dq {
549   my ($self, $arg, $dir, $from) = @_;
550
551   return unless $arg;
552
553   my $dq = {
554     type => DQ_ORDER,
555     (defined($dir) ? (reverse => !!($dir =~ /desc/i)) : ()),
556     ($from ? (from => $from) : ()),
557   };
558
559   if (!ref($arg)) {
560     $dq->{by} = $self->_ident_to_dq($arg);
561   } elsif (ref($arg) eq 'ARRAY') {
562     return unless @$arg;
563     local our $Order_Inner unless our $Order_Recursing;
564     local $Order_Recursing = 1;
565     my ($outer, $inner);
566     foreach my $member (@$arg) {
567       local $Order_Inner;
568       my $next = $self->_order_by_to_dq($member, $dir, $from);
569       $outer ||= $next;
570       $inner->{from} = $next if $inner;
571       $inner = $Order_Inner || $next;
572     }
573     $Order_Inner = $inner;
574     return $outer;
575   } elsif (ref($arg) eq 'REF' and ref($$arg) eq 'ARRAY') {
576     $dq->{by} = $self->_literal_to_dq($$arg);
577   } elsif (ref($arg) eq 'SCALAR') {
578     if (my ($ident, $dir) = $$arg =~ /^(\w+)(?:\s+(desc|asc))?$/i) {
579       $dq->{by} = $self->_ident_to_dq($ident);
580       $dq->{reverse} = 1 if $dir and lc($dir) eq 'desc';
581     } else {
582       $dq->{by} = $self->_literal_to_dq($$arg);
583     }
584   } elsif (ref($arg) eq 'HASH') {
585     my ($key, $val, @rest) = %$arg;
586
587     return unless $key;
588
589     if (@rest or not $key =~ /^-(desc|asc)/i) {
590       die "hash passed to _order_by must have exactly one key (-desc or -asc)";
591     }
592     my $dir = uc $1;
593     return $self->_order_by_to_dq($val, $dir, $from);
594   } else {
595     die "Can't handle $arg in _order_by_to_dq";
596   }
597   return $dq;
598 }
599
600 sub _table_to_dq {
601   my ($self, $from) = @_;
602   if (ref($from) eq 'ARRAY') {
603     die "Empty FROM list" unless my @f = @$from;
604     my $dq = $self->_table_to_dq(shift @f);
605     while (my $x = shift @f) {
606       $dq = {
607         type => DQ_JOIN,
608         join => [ $dq, $self->_table_to_dq($x) ]
609       };
610     }
611     $dq;
612   } elsif (ref($from) eq 'SCALAR' or (ref($from) eq 'REF')) {
613     $self->_literal_to_dq($$from);
614   } else {
615     $self->_ident_to_dq($from);
616   }
617 }
618
619 # And bindtype
620 sub _bindtype (@) {
621   #my ($self, $col, @vals) = @_;
622
623   #LDNOTE : changed original implementation below because it did not make
624   # sense when bindtype eq 'columns' and @vals > 1.
625 #  return $self->{bindtype} eq 'columns' ? [ $col, @vals ] : @vals;
626
627   # called often - tighten code
628   return $_[0]->bind_meta
629     ? map {[$_[1], $_]} @_[2 .. $#_]
630     : @_[2 .. $#_]
631   ;
632 }
633
634 # Dies if any element of @bind is not in [colname => value] format
635 # if bindtype is 'columns'.
636 sub _assert_bindval_matches_bindtype {
637 #  my ($self, @bind) = @_;
638   my $self = shift;
639   if ($self->bind_meta) {
640     for (@_) {
641       if (!defined $_ || ref($_) ne 'ARRAY' || @$_ != 2) {
642         die "bindtype 'columns' selected, you need to pass: [column_name => bind_value]"
643       }
644     }
645   }
646 }
647
648 # Fix SQL case, if so requested
649 sub _sqlcase {
650   return $_[0]->lower_case ? $_[1] : uc($_[1]);
651 }
652
653 1;