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