X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=1eae7091e0e01c23740248fee772f300d219de64;hb=97c9af3238f6ea29a346242b787b9567301e8cbd;hp=7655a49f88d5b837d9968243b9cc9d742aa76c0a;hpb=e748969fa7f29d7e83f8d0ff22cd20ecccf1bf13;p=scpubgit%2FQ-Branch.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 7655a49..1eae709 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -38,8 +38,10 @@ our $AUTOLOAD; # See section WHERE: BUILTIN SPECIAL OPERATORS below for implementation my @BUILTIN_SPECIAL_OPS = ( {regex => qr/^ (?: not \s )? between $/ix, handler => sub { die "NOPE" }}, - {regex => qr/^ (?: not \s )? in $/ix, handler => sub { die "NOPE" }}, {regex => qr/^ is (?: \s+ not )? $/ix, handler => sub { die "NOPE" }}, + {regex => qr/^ (?: not \s )? in $/ix, handler => sub { die "NOPE" }}, + {regex => qr/^ ident $/ix, handler => sub { die "NOPE" }}, + {regex => qr/^ value $/ix, handler => sub { die "NOPE" }}, ); #====================================================================== @@ -68,6 +70,15 @@ sub is_literal_value ($) { : undef; } +sub is_undef_value ($) { + !defined($_[0]) + or ( + ref($_[0]) eq 'HASH' + and exists $_[0]->{-value} + and not defined $_[0]->{-value} + ); +} + # FIXME XSify - this can be done so much more efficiently sub is_plain_value ($) { no strict 'refs'; @@ -147,8 +158,8 @@ sub new { $opt{equality_op} = qr/^( \Q$opt{cmp}\E | \= )$/ix; $opt{inequality_op} = qr/^( != | <> )$/ix; - $opt{like_op} = qr/^ (is\s+)? r?like $/xi; - $opt{not_like_op} = qr/^ (is\s+)? not \s+ r?like $/xi; + $opt{like_op} = qr/^ (is_)?r?like $/xi; + $opt{not_like_op} = qr/^ (is_)?not_r?like $/xi; # SQL booleans $opt{sqltrue} ||= '1=1'; @@ -161,10 +172,8 @@ sub new { push @{$opt{special_ops}}, @BUILTIN_SPECIAL_OPS; if ($class->isa('DBIx::Class::SQLMaker')) { - push @{$opt{special_ops}}, our $DBIC_Compat_Op ||= { - regex => qr/^(?:ident|value)$/i, handler => sub { die "NOPE" } - }; - $opt{is_dbic_sqlmaker} = 1; + $opt{warn_once_on_nest} = 1; + $opt{disable_old_special_ops} = 1; } # unary operators @@ -192,41 +201,52 @@ sub new { -and => '_expand_op_andor', -or => '_expand_op_andor', -nest => '_expand_nest', + -bind => sub { shift; +{ @_ } }, + -in => '_expand_in', + -not_in => '_expand_in', + -row => sub { + my ($self, $node, $args) = @_; + +{ $node => [ map $self->expand_expr($_), @$args ] }; + }, + -between => '_expand_between', + -not_between => '_expand_between', + -op => sub { + my ($self, $node, $args) = @_; + my ($op, @opargs) = @$args; + +{ $node => [ $op, map $self->expand_expr($_), @opargs ] }; + }, + (map +($_ => '_expand_op_is'), ('-is', '-is_not')), + -ident => '_expand_ident', + -value => '_expand_value', }; $opt{expand_op} = { 'between' => '_expand_between', - 'not between' => '_expand_between', + 'not_between' => '_expand_between', 'in' => '_expand_in', - 'not in' => '_expand_in', + 'not_in' => '_expand_in', 'nest' => '_expand_nest', - (map +($_ => '_expand_op_andor'), - qw(and or)), + (map +($_ => '_expand_op_andor'), ('and', 'or')), + (map +($_ => '_expand_op_is'), ('is', 'is_not')), + 'ident' => '_expand_ident', + 'value' => '_expand_value', }; - # placeholder for _expand_unop system - { - my %unops = (-ident => '_expand_ident', -value => '_expand_value'); - foreach my $name (keys %unops) { - $opt{expand}{$name} = $unops{$name}; - my ($op) = $name =~ /^-(.*)$/; - $opt{expand_op}{$op} = sub { - my ($self, $op, $arg, $k) = @_; - return +{ -op => [ - $self->{cmp}, - $self->_expand_ident(-ident => $k), - $self->_expand_expr({ '-'.$op => $arg }), - ] }; - }; - } - } - $opt{render} = { - (map +("-$_", "_render_$_"), qw(op func bind ident literal list)), + (map +("-$_", "_render_$_"), qw(op func bind ident literal row)), %{$opt{render}||{}} }; - $opt{render_op} = our $RENDER_OP; + $opt{render_op} = { + (map +($_ => '_render_op_between'), 'between', 'not_between'), + (map +($_ => '_render_op_in'), 'in', 'not_in'), + (map +($_ => '_render_unop_postfix'), + 'is_null', 'is_not_null', 'asc', 'desc', + ), + (not => '_render_op_not'), + (map +($_ => '_render_op_andor'), qw(and or)), + ',' => '_render_op_multop', + }; return bless \%opt, $class; } @@ -277,7 +297,7 @@ sub _returning { my $f = $options->{returning}; my ($sql, @bind) = $self->render_aqt( - $self->_expand_maybe_list_expr($f, undef, -ident) + $self->_expand_maybe_list_expr($f, -ident) ); return wantarray ? $self->_sqlcase(' returning ') . $sql @@ -479,7 +499,7 @@ sub _select_fields { my ($self, $fields) = @_; return $fields unless ref($fields); return $self->render_aqt( - $self->_expand_maybe_list_expr($fields, undef, '-ident') + $self->_expand_maybe_list_expr($fields, '-ident') ); } @@ -540,6 +560,8 @@ sub where { return wantarray ? ($sql, @bind) : $sql; } +{ our $Default_Scalar_To = -value } + sub expand_expr { my ($self, $expr, $default_scalar_to) = @_; local our $Default_Scalar_To = $default_scalar_to if $default_scalar_to; @@ -561,6 +583,12 @@ sub render_expr { $self->render_aqt($self->expand_expr($expr)); } +sub _normalize_op { + my ($self, $raw) = @_; + s/^-(?=.)//, s/\s+/_/g for my $op = lc $raw; + $op; +} + sub _expand_expr { my ($self, $expr) = @_; our $Expand_Depth ||= 0; local $Expand_Depth = $Expand_Depth + 1; @@ -588,10 +616,7 @@ sub _expand_expr { return +{ -literal => $literal }; } if (!ref($expr) or Scalar::Util::blessed($expr)) { - if (my $d = our $Default_Scalar_To) { - return $self->_expand_expr({ $d => $expr }); - } - return $self->_expand_value(-value => $expr); + return $self->_expand_expr_scalar($expr); } die "notreached"; } @@ -614,120 +639,34 @@ sub _expand_expr_hashpair { sub _expand_expr_hashpair_ident { my ($self, $k, $v) = @_; - # undef needs to be re-sent with cmp to achieve IS/IS NOT NULL + local our $Cur_Col_Meta = $k; - if ( - !defined($v) - or ( - ref($v) eq 'HASH' - and exists $v->{-value} - and not defined $v->{-value} - ) - ) { - return $self->_expand_expr({ $k => { $self->{cmp} => undef } }); + # hash with multiple or no elements is andor + + if (ref($v) eq 'HASH' and keys %$v != 1) { + return $self->_expand_op_andor(-and => $v, $k); } - my $ik = $self->_expand_ident(-ident => $k); + # undef needs to be re-sent with cmp to achieve IS/IS NOT NULL + + if (is_undef_value($v)) { + return $self->_expand_expr_hashpair_cmp($k => undef); + } # scalars and objects get expanded as whatever requested or values if (!ref($v) or Scalar::Util::blessed($v)) { - my $d = our $Default_Scalar_To; - local our $Cur_Col_Meta = $k; - return $self->_expand_expr_hashpair_ident( - $k, - ($d - ? $self->_expand_expr($d => $v) - : { -value => $v } - ) - ); + return $self->_expand_expr_hashpair_scalar($k, $v); } + + # single key hashref is a hashtriple + if (ref($v) eq 'HASH') { - if (keys %$v > 1) { - return $self->_expand_op_andor(-and => $v, $k); - } - return undef unless keys %$v; - my ($vk, $vv) = %$v; - my $op = join ' ', split '_', (map lc, $vk =~ /^-?(.*)$/)[0]; - $self->_assert_pass_injection_guard($op); - if ($op =~ s/ [_\s]? \d+ $//x ) { - return $self->_expand_expr($k, $v); - } - if (my $x = $self->{expand_op}{$op}) { - local our $Cur_Col_Meta = $k; - return $self->$x($op, $vv, $k); - } - if ($op =~ /^is(?: not)?$/) { - puke "$op can only take undef as argument" - if defined($vv) - and not ( - ref($vv) eq 'HASH' - and exists($vv->{-value}) - and !defined($vv->{-value}) - ); - return +{ -op => [ $op.' null', $ik ] }; - } - if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) { - return { -op => [ $op, $ik, $vv ] }; - } - if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { - return { -op => [ - $self->{cmp}, - $ik, - { -op => [ $op, $vv ] } - ] }; - } - if (ref($vv) eq 'ARRAY') { - my @raw = @$vv; - my $logic = (defined($raw[0]) and $raw[0] =~ /^-(and|or)$/i) - ? shift @raw : '-or'; - my @values = map +{ $vk => $_ }, @raw; - if ( - $op =~ $self->{inequality_op} - or $op =~ $self->{not_like_op} - ) { - if (lc($logic) eq '-or' and @values > 1) { - belch "A multi-element arrayref as an argument to the inequality op '${\uc($op)}' " - . 'is technically equivalent to an always-true 1=1 (you probably wanted ' - . "to say ...{ \$inequality_op => [ -and => \@values ] }... instead)" - ; - } - } - unless (@values) { - # try to DWIM on equality operators - return - $op =~ $self->{equality_op} ? $self->sqlfalse - : $op =~ $self->{like_op} ? belch("Supplying an empty arrayref to '@{[ uc $op]}' is deprecated") && $self->sqlfalse - : $op =~ $self->{inequality_op} ? $self->sqltrue - : $op =~ $self->{not_like_op} ? belch("Supplying an empty arrayref to '@{[ uc $op]}' is deprecated") && $self->sqltrue - : puke "operator '$op' applied on an empty array (field '$k')"; - } - return $self->_expand_op_andor($logic => \@values, $k); - } - if ( - !defined($vv) - or ( - ref($vv) eq 'HASH' - and exists $vv->{-value} - and not defined $vv->{-value} - ) - ) { - my $is = - $op =~ /^not$/i ? 'is not' # legacy - : $op =~ $self->{equality_op} ? 'is' - : $op =~ $self->{like_op} ? belch("Supplying an undefined argument to '@{[ uc $op]}' is deprecated") && 'is' - : $op =~ $self->{inequality_op} ? 'is not' - : $op =~ $self->{not_like_op} ? belch("Supplying an undefined argument to '@{[ uc $op]}' is deprecated") && 'is not' - : puke "unexpected operator '$op' with undef operand"; - return +{ -op => [ $is.' null', $ik ] }; - } - local our $Cur_Col_Meta = $k; - return +{ -op => [ - $op, - $ik, - $self->_expand_expr($vv) - ] }; + return $self->_expand_expr_hashtriple($k, %$v); } + + # arrayref needs re-engineering over the elements + if (ref($v) eq 'ARRAY') { return $self->sqlfalse unless @$v; $self->_debug("ARRAY($k) means distribute over elements"); @@ -740,6 +679,7 @@ sub _expand_expr_hashpair_ident { $logic => $v, $k ); } + if (my $literal = is_literal_value($v)) { unless (length $k) { belch 'Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead'; @@ -756,35 +696,55 @@ sub _expand_expr_hashpair_ident { die "notreached"; } +sub _expand_expr_scalar { + my ($self, $expr) = @_; + + return $self->_expand_expr({ (our $Default_Scalar_To) => $expr }); +} + +sub _expand_expr_hashpair_scalar { + my ($self, $k, $v) = @_; + + return $self->_expand_expr_hashpair_cmp( + $k, $self->_expand_expr_scalar($v), + ); +} + sub _expand_expr_hashpair_op { my ($self, $k, $v) = @_; - my $op = $k; - $op =~ s/^-// if length($op) > 1; - $self->_assert_pass_injection_guard($op); + $self->_assert_pass_injection_guard($k =~ /\A-(.*)\Z/s); + + my $op = $self->_normalize_op($k); # Ops prefixed with -not_ get converted - if (my ($rest) = $op =~/^not[_ ](.*)$/) { + if (my ($rest) = $op =~/^not_(.*)$/) { return +{ -op => [ 'not', $self->_expand_expr({ "-${rest}", $v }) - ] }; + ] }; } - # the old special op system requires illegality for top-level use + { # Old SQLA compat - if ( - (our $Expand_Depth) == 1 - and List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}} - ) { - puke "Illegal use of top-level '-$op'" - } + my $op = join(' ', split '_', $op); + + # the old special op system requires illegality for top-level use - # the old unary op system means we should touch nothing and let it work + if ( + (our $Expand_Depth) == 1 + and $self->{disable_old_special_ops} + and List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}} + ) { + puke "Illegal use of top-level '-$op'" + } - if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { - return { -op => [ $op, $v ] }; + # the old unary op system means we should touch nothing and let it work + + if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { + return { -op => [ $op, $v ] }; + } } # an explicit node type is currently assumed to be expanded (this is almost @@ -802,8 +762,10 @@ sub _expand_expr_hashpair_op { and (keys %$v)[0] =~ /^-/ ) { my ($func) = $k =~ /^-(.*)$/; - if (List::Util::first { $func =~ $_->{regex} } @{$self->{special_ops}}) { - return +{ -op => [ $func, $self->_expand_expr($v) ] }; + { # Old SQLA compat + if (List::Util::first { $func =~ $_->{regex} } @{$self->{special_ops}}) { + return +{ -op => [ $func, $self->_expand_expr($v) ] }; + } } return +{ -func => [ $func, $self->_expand_expr($v) ] }; } @@ -817,8 +779,112 @@ sub _expand_expr_hashpair_op { die "notreached"; } +sub _expand_expr_hashpair_cmp { + my ($self, $k, $v) = @_; + $self->_expand_expr_hashtriple($k, $self->{cmp}, $v); +} + +sub _expand_expr_hashtriple { + my ($self, $k, $vk, $vv) = @_; + + my $ik = $self->_expand_ident(-ident => $k); + + my $op = $self->_normalize_op($vk); + $self->_assert_pass_injection_guard($op); + + if ($op =~ s/ _? \d+ $//x ) { + return $self->_expand_expr($k, { $vk, $vv }); + } + if (my $x = $self->{expand_op}{$op}) { + local our $Cur_Col_Meta = $k; + return $self->$x($op, $vv, $k); + } + { # Old SQLA compat + + my $op = join(' ', split '_', $op); + + if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) { + return { -op => [ $op, $ik, $vv ] }; + } + if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { + return { -op => [ + $self->{cmp}, + $ik, + { -op => [ $op, $vv ] } + ] }; + } + } + if (ref($vv) eq 'ARRAY') { + my @raw = @$vv; + my $logic = (defined($raw[0]) and $raw[0] =~ /^-(and|or)$/i) + ? shift @raw : '-or'; + my @values = map +{ $vk => $_ }, @raw; + if ( + $op =~ $self->{inequality_op} + or $op =~ $self->{not_like_op} + ) { + if (lc($logic) eq '-or' and @values > 1) { + belch "A multi-element arrayref as an argument to the inequality op '${\uc(join ' ', split '_', $op)}' " + . 'is technically equivalent to an always-true 1=1 (you probably wanted ' + . "to say ...{ \$inequality_op => [ -and => \@values ] }... instead)" + ; + } + } + unless (@values) { + # try to DWIM on equality operators + return ($self->_dwim_op_to_is($op, + "Supplying an empty arrayref to '%s' is deprecated", + "operator '%s' applied on an empty array (field '$k')" + ) ? $self->sqlfalse : $self->sqltrue); + } + return $self->_expand_op_andor($logic => \@values, $k); + } + if (is_undef_value($vv)) { + my $is = ($self->_dwim_op_to_is($op, + "Supplying an undefined argument to '%s' is deprecated", + "unexpected operator '%s' with undef operand", + ) ? 'is' : 'is not'); + + return $self->_expand_expr_hashpair($k => { $is, undef }); + } + local our $Cur_Col_Meta = $k; + return +{ -op => [ + $op, + $ik, + $self->_expand_expr($vv) + ] }; +} + +sub _dwim_op_to_is { + my ($self, $raw, $empty, $fail) = @_; + + my $op = $self->_normalize_op($raw); + + if ($op =~ /^not$/i) { + return 0; + } + if ($op =~ $self->{equality_op}) { + return 1; + } + if ($op =~ $self->{like_op}) { + belch(sprintf $empty, uc(join ' ', split '_', $op)); + return 1; + } + if ($op =~ $self->{inequality_op}) { + return 0; + } + if ($op =~ $self->{not_like_op}) { + belch(sprintf $empty, uc(join ' ', split '_', $op)); + return 0; + } + puke(sprintf $fail, $op); +} + sub _expand_ident { - my ($self, $op, $body) = @_; + my ($self, $op, $body, $k) = @_; + return $self->_expand_expr_hashpair_cmp( + $k, { -ident => $body } + ) if defined($k); unless (defined($body) or (ref($body) and ref($body) eq 'ARRAY')) { puke "$op requires a single plain scalar argument (a quotable identifier) or an arrayref of identifier parts"; } @@ -832,6 +898,9 @@ sub _expand_ident { } sub _expand_value { + return $_[0]->_expand_expr_hashpair_cmp( + $_[3], { -value => $_[2] }, + ) if defined($_[3]); +{ -bind => [ our $Cur_Col_Meta, $_[2] ] }; } @@ -859,6 +928,7 @@ sub _expand_op_andor { } my ($logop) = $logic =~ /^-?(.*)$/; if (ref($v) eq 'HASH') { + return undef unless keys %$v; return +{ -op => [ $logop, map $self->_expand_expr({ $_ => $v->{$_} }), @@ -901,9 +971,24 @@ sub _expand_op_andor { die "notreached"; } +sub _expand_op_is { + my ($self, $op, $vv, $k) = @_; + $op =~ s/^-//; + ($k, $vv) = @$vv unless defined $k; + puke "$op can only take undef as argument" + if defined($vv) + and not ( + ref($vv) eq 'HASH' + and exists($vv->{-value}) + and !defined($vv->{-value}) + ); + return +{ -op => [ $op.'_null', $self->expand_expr($k, -ident) ] }; +} + sub _expand_between { my ($self, $op, $vv, $k) = @_; - local our $Cur_Col_Meta = $k; + $op =~ s/^-//; + $k = shift @{$vv = [ @$vv ]} unless defined $k; my @rhs = map $self->_expand_expr($_), ref($vv) eq 'ARRAY' ? @$vv : $vv; unless ( @@ -915,18 +1000,20 @@ sub _expand_between { } return +{ -op => [ $op, - $self->_expand_ident(-ident => $k), + $self->expand_expr(ref($k) ? $k : { -ident => $k }), @rhs ] } } sub _expand_in { - my ($self, $op, $vv, $k) = @_; + my ($self, $raw, $vv, $k) = @_; + $k = shift @{$vv = [ @$vv ]} unless defined $k; + my $op = $self->_normalize_op($raw); if (my $literal = is_literal_value($vv)) { my ($sql, @bind) = @$literal; my $opened_sql = $self->_open_outer_paren($sql); return +{ -op => [ - $op, $self->_expand_ident(-ident => $k), + $op, $self->expand_expr($k, -ident), [ { -literal => [ $opened_sql, @bind ] } ] ] }; } @@ -938,15 +1025,14 @@ sub _expand_in { ; puke("Argument passed to the '${\uc($op)}' operator can not be undefined") if !defined($vv); - my @rhs = map $self->_expand_expr($_), - map { ref($_) ? $_ : { -bind => [ $k, $_ ] } } + my @rhs = map $self->expand_expr($_, -value), map { defined($_) ? $_: puke($undef_err) } (ref($vv) eq 'ARRAY' ? @$vv : $vv); return $self->${\($op =~ /^not/ ? 'sqltrue' : 'sqlfalse')} unless @rhs; return +{ -op => [ $op, - $self->_expand_ident(-ident => $k), + $self->expand_expr($k, -ident), \@rhs ] }; } @@ -955,7 +1041,7 @@ sub _expand_nest { my ($self, $op, $v) = @_; # DBIx::Class requires a nest warning to be emitted once but the private # method it overrode to do so no longer exists - if ($self->{is_dbic_sqlmaker}) { + if ($self->{warn_once_on_nest}) { unless (our $Nest_Warned) { belch( "-nest in search conditions is deprecated, you most probably wanted:\n" @@ -996,10 +1082,10 @@ sub _render_ident { return $self->_convert($self->_quote($ident)); } -sub _render_list { - my ($self, $list) = @_; - my @parts = grep length($_->[0]), map [ $self->render_aqt($_) ], @$list; - return join(', ', map $_->[0], @parts), map @{$_}[1..$#$_], @parts; +sub _render_row { + my ($self, $values) = @_; + my ($sql, @bind) = $self->_render_op([ ',', @$values ]); + return "($sql)", @bind; } sub _render_func { @@ -1025,6 +1111,39 @@ sub _render_literal { return @$literal; } +sub _render_op { + my ($self, $v) = @_; + my ($op, @args) = @$v; + if (my $r = $self->{render_op}{$op}) { + return $self->$r($op, \@args); + } + + { # Old SQLA compat + + my $op = join(' ', split '_', $op); + + my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}; + if ($us and @args > 1) { + puke "Special op '${op}' requires first value to be identifier" + unless my ($ident) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0]; + my $k = join(($self->{name_sep}||'.'), @$ident); + local our $Expand_Depth = 1; + return $self->${\($us->{handler})}($k, $op, $args[1]); + } + if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { + return $self->${\($us->{handler})}($op, $args[0]); + } + + } + if (@args == 1) { + return $self->_render_unop_prefix($op, \@args); + } else { + return $self->_render_op_multop($op, \@args); + } + die "notreached"; +} + + sub _render_op_between { my ($self, $op, $args) = @_; my ($left, $low, $high) = @$args; @@ -1041,84 +1160,56 @@ sub _render_op_between { }; my ($lhsql, @lhbind) = $self->render_aqt($left); return ( - join(' ', '(', $lhsql, $self->_sqlcase($op), $rhsql, ')'), + join(' ', + '(', $lhsql, + $self->_sqlcase(join ' ', split '_', $op), + $rhsql, ')' + ), @lhbind, @rhbind ); } -our $RENDER_OP = { - (map +($_ => '_render_op_between'), 'between', 'not between'), - (map +($_ => sub { - my ($self, $op, $args) = @_; - my ($lhs, $rhs) = @$args; - my @in_bind; - my @in_sql = map { - my ($sql, @bind) = $self->render_aqt($_); - push @in_bind, @bind; - $sql; - } @$rhs; - my ($lhsql, @lbind) = $self->render_aqt($lhs); - return ( - $lhsql.' '.$self->_sqlcase($op).' ( ' - .join(', ', @in_sql) - .' )', - @lbind, @in_bind - ); - }), 'in', 'not in'), - (map +($_ => '_render_unop_postfix'), - 'is null', 'is not null', 'asc', 'desc', - ), - (not => '_render_op_not'), - (map +($_ => sub { - my ($self, $op, $args) = @_; - my @parts = grep length($_->[0]), map [ $self->render_aqt($_) ], @$args; - return '' unless @parts; - return @{$parts[0]} if @parts == 1; - my ($final_sql) = join( - ' '.$self->_sqlcase($op).' ', - map $_->[0], @parts - ); - return ( - '('.$final_sql.')', - map @{$_}[1..$#$_], @parts - ); - }), qw(and or)), -}; +sub _render_op_in { + my ($self, $op, $args) = @_; + my ($lhs, $rhs) = @$args; + my @in_bind; + my @in_sql = map { + my ($sql, @bind) = $self->render_aqt($_); + push @in_bind, @bind; + $sql; + } @$rhs; + my ($lhsql, @lbind) = $self->render_aqt($lhs); + return ( + $lhsql.' '.$self->_sqlcase(join ' ', split '_', $op).' ( ' + .join(', ', @in_sql) + .' )', + @lbind, @in_bind + ); +} -sub _render_op { - my ($self, $v) = @_; - my ($op, @args) = @$v; - if (my $r = $self->{render_op}{$op}) { - return $self->$r($op, \@args); - } - my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}; - if ($us and @args > 1) { - puke "Special op '${op}' requires first value to be identifier" - unless my ($ident) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0]; - my $k = join(($self->{name_sep}||'.'), @$ident); - local our $Expand_Depth = 1; - return $self->${\($us->{handler})}($k, $op, $args[1]); - } - if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { - return $self->${\($us->{handler})}($op, $args[0]); - } - if (@args == 1) { - return $self->_render_unop_prefix($op, \@args); - } else { - my @parts = grep length($_->[0]), map [ $self->render_aqt($_) ], @args; - return '' unless @parts; - my ($final_sql) = join( - ' '.$self->_sqlcase($op).' ', - map $_->[0], @parts - ); - return ( - $final_sql, - map @{$_}[1..$#$_], @parts - ); - } - die "unhandled"; +sub _render_op_andor { + my ($self, $op, $args) = @_; + my @parts = grep length($_->[0]), map [ $self->render_aqt($_) ], @$args; + return '' unless @parts; + return @{$parts[0]} if @parts == 1; + my ($sql, @bind) = $self->_render_op_multop($op, $args); + return '( '.$sql.' )', @bind; } +sub _render_op_multop { + my ($self, $op, $args) = @_; + my @parts = grep length($_->[0]), map [ $self->render_aqt($_) ], @$args; + return '' unless @parts; + return @{$parts[0]} if @parts == 1; + my ($final_sql) = join( + ($op eq ',' ? '' : ' ').$self->_sqlcase(join ' ', split '_', $op).' ', + map $_->[0], @parts + ); + return ( + $final_sql, + map @{$_}[1..$#$_], @parts + ); +} sub _render_op_not { my ($self, $op, $v) = @_; my ($sql, @bind) = $self->_render_unop_prefix($op, $v); @@ -1128,14 +1219,15 @@ sub _render_op_not { sub _render_unop_prefix { my ($self, $op, $v) = @_; my ($expr_sql, @bind) = $self->render_aqt($v->[0]); - my $op_sql = $self->_sqlcase($op); + + my $op_sql = $self->_sqlcase($op); # join ' ', split '_', $op); return ("${op_sql} ${expr_sql}", @bind); } sub _render_unop_postfix { my ($self, $op, $v) = @_; my ($expr_sql, @bind) = $self->render_aqt($v->[0]); - my $op_sql = $self->_sqlcase($op); + my $op_sql = $self->_sqlcase(join ' ', split '_', $op); return ($expr_sql.' '.$op_sql, @bind); } @@ -1196,7 +1288,9 @@ sub _expand_order_by { ), map $self->expand_expr($_, -ident), map ref($_) eq 'ARRAY' ? @$_ : $_, @to_expand; - return (@exp > 1 ? { -list => \@exp } : $exp[0]); + return undef unless @exp; + return undef if @exp == 1 and not defined($exp[0]); + return +{ -op => [ ',', @exp ] }; }; local @{$self->{expand}}{qw(-asc -desc)} = (($expander) x 2); @@ -1235,8 +1329,9 @@ sub _chunkify_order_by { if $expanded->{-ident} or @{$expanded->{-literal}||[]} == 1; for ($expanded) { - if (ref() eq 'HASH' and my $l = $_->{-list}) { - return map $self->_chunkify_order_by($_), @$l; + if (ref() eq 'HASH' and $_->{-op} and $_->{-op}[0] eq ',') { + my ($comma, @list) = @{$_->{-op}}; + return map $self->_chunkify_order_by($_), @list; } return [ $self->render_aqt($_) ]; } @@ -1250,7 +1345,7 @@ sub _table { my $self = shift; my $from = shift; ($self->render_aqt( - $self->_expand_maybe_list_expr($from, undef, -ident) + $self->_expand_maybe_list_expr($from, -ident) ))[0]; } @@ -1260,18 +1355,11 @@ sub _table { #====================================================================== sub _expand_maybe_list_expr { - my ($self, $expr, $logic, $default) = @_; - my $e = do { - if (ref($expr) eq 'ARRAY') { - return { -list => [ - map $self->expand_expr($_, $default), @$expr - ] } if @$expr > 1; - $expr->[0] - } else { - $expr - } - }; - return $self->expand_expr($e, $default); + my ($self, $expr, $default) = @_; + return +{ -op => [ ',', + map $self->expand_expr($_, $default), + ref($expr) eq 'ARRAY' ? @$expr : $expr + ] }; } # highly optimized, as it's called way too often