Fix eq_bind checker
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract / Converter.pm
CommitLineData
a82e41dc 1package SQL::Abstract::Converter;
2
3use Carp ();
4use List::Util ();
5use Scalar::Util ();
6use 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);
10use Data::Query::ExprHelpers qw(perl_scalar_value);
11use Moo;
8b9b83ae 12use namespace::clean;
a82e41dc 13
14has renderer_will_quote => (
15 is => 'ro'
16);
17
18has lower_case => (
19 is => 'ro'
20);
21
22has default_logic => (
23 is => 'ro', coerce => sub { uc($_[0]) }, default => sub { 'OR' }
24);
25
26has bind_meta => (
27 is => 'ro', default => sub { 1 }
28);
29
30has cmp => (is => 'ro', default => sub { '=' });
31
32has sqltrue => (is => 'ro', default => sub { '1=1' });
33has sqlfalse => (is => 'ro', default => sub { '0=1' });
34
35has special_ops => (is => 'ro', default => sub { [] });
36
37# XXX documented but I don't current fail any tests not using it
38has unary_ops => (is => 'ro', default => sub { [] });
39
40has injection_guard => (
41 is => 'ro',
42 default => sub {
43 qr/
44 \;
45 |
46 ^ \s* go \s
47 /xmi;
48 }
49);
50
51has identifier_sep => (
52 is => 'ro', default => sub { '.' },
53);
54
55has always_quote => (is => 'ro', default => sub { 1 });
56
57has convert => (is => 'ro');
58
59has array_datatypes => (is => 'ro');
60
61sub _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
73sub _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
84sub _value_to_dq {
85 my ($self, $value) = @_;
86 $self->_maybe_convert_dq(perl_scalar_value($value, our $Cur_Col_Meta));
87}
88
89sub _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
99sub _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
115sub _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
125sub _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
134sub _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
165sub _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
182sub _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
204sub _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
220sub _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
87af4204 234 return $self->_select_select_to_dq($fields, $ordered_dq);
a82e41dc 235}
236
87af4204 237sub _select_select_to_dq {
a82e41dc 238 my ($self, $fields, $from_dq) = @_;
239
240 $fields ||= '*';
241
242 return +{
243 type => DQ_SELECT,
87af4204 244 select => $self->_select_field_list_to_dq($fields),
a82e41dc 245 from => $from_dq,
246 };
247}
248
249sub _select_field_list_to_dq {
250 my ($self, $fields) = @_;
87af4204 251 [ map $self->_select_field_to_dq($_),
252 ref($fields) eq 'ARRAY' ? @$fields : $fields ];
a82e41dc 253}
254
255sub _select_field_to_dq {
256 my ($self, $field) = @_;
87af4204 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)
a82e41dc 265}
266
267sub _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
276sub _where_to_dq {
277 my ($self, $where, $logic) = @_;
278
279 return undef unless defined($where);
280
e177c256 281 # if we're given a simple string assume it's a literal
282 return $self->_literal_to_dq($where) if !ref($where);
283
a82e41dc 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
290sub _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);
62d17764 302 } elsif (ref($where) eq 'REF' and ref($$where) eq 'HASH') {
303 return $$where;
a82e41dc 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
310sub _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
337sub _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
357sub _where_to_dq_SCALAR {
358 shift->_value_to_dq(@_);
359}
360
361sub _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
410sub _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
549sub _order_by_to_dq {
550 my ($self, $arg, $dir, $from) = @_;
551
552 return unless $arg;
553
554 my $dq = {
555 type => DQ_ORDER,
3d82b6c9 556 (defined($dir) ? (reverse => !!($dir =~ /desc/i)) : ()),
a82e41dc 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') {
8b9b83ae 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
daf6f830 590 if (my ($ident, $dir) = $$arg =~ /^(\w+)(?:\s+(desc|asc))?$/i) {
3d82b6c9 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 }
a82e41dc 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
612sub _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,
31a41fd9 620 left => $dq,
621 right => $self->_table_to_dq($x),
a82e41dc 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
633sub _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'.
649sub _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
662sub _sqlcase {
663 return $_[0]->lower_case ? $_[1] : uc($_[1]);
664}
665
6661;