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