X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=4cc2c3ab8ca7724e4e4beb4bc36bbd0b036155d4;hb=771e7f6967f9026010eb24d8721facbe9473cb09;hp=22f77ebb50f0f7e02a63778c9019ba5343b17cbf;hpb=2ddaa0021e815632c4abf28a142dee3c154175b2;p=scpubgit%2FQ-Branch.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 22f77eb..4cc2c3a 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -187,21 +187,47 @@ sub new { $opt{expand_unary} = {}; $opt{expand} = { - -ident => '_expand_ident', - -value => '_expand_value', -not => '_expand_not', -bool => '_expand_bool', - -and => '_expand_andor', - -or => '_expand_andor', + -and => '_expand_op_andor', + -or => '_expand_op_andor', + -nest => '_expand_nest', }; - $opt{render_op} = our $RENDER_OP; + $opt{expand_op} = { + 'between' => '_expand_between', + 'not between' => '_expand_between', + 'in' => '_expand_in', + 'not in' => '_expand_in', + 'nest' => '_expand_nest', + (map +($_ => '_expand_op_andor'), + qw(and or)), + }; + + # 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)), %{$opt{render}||{}} }; + $opt{render_op} = our $RENDER_OP; + return bless \%opt, $class; } @@ -536,14 +562,13 @@ sub render_expr { } sub _expand_expr { - my ($self, $expr, $logic) = @_; + my ($self, $expr) = @_; our $Expand_Depth ||= 0; local $Expand_Depth = $Expand_Depth + 1; return undef unless defined($expr); if (ref($expr) eq 'HASH') { return undef unless my $kc = keys %$expr; if ($kc > 1) { - $logic ||= 'and'; - return $self->_expand_andor("-${logic}", $expr); + return $self->_expand_op_andor(-and => $expr); } my ($key, $value) = %$expr; if ($key =~ /^-/ and $key =~ s/ [_\s]? \d+ $//x ) { @@ -553,11 +578,11 @@ sub _expand_expr { if (my $exp = $self->{expand}{$key}) { return $self->$exp($key, $value); } - return $self->_expand_expr_hashpair($key, $value, $logic); + return $self->_expand_expr_hashpair($key, $value); } if (ref($expr) eq 'ARRAY') { - my $logic = lc($logic || $self->{logic}); - return $self->_expand_andor("-${logic}", $expr); + my $logic = '-'.lc($self->{logic}); + return $self->_expand_op_andor($logic, $expr); } if (my $literal = is_literal_value($expr)) { return +{ -literal => $literal }; @@ -572,7 +597,7 @@ sub _expand_expr { } sub _expand_expr_hashpair { - my ($self, $k, $v, $logic) = @_; + my ($self, $k, $v) = @_; unless (defined($k) and length($k)) { if (defined($k) and my $literal = is_literal_value($v)) { belch 'Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead'; @@ -580,26 +605,12 @@ sub _expand_expr_hashpair { } puke "Supplying an empty left hand side argument is not supported"; } + $self->_assert_pass_injection_guard($k =~ /^-(.*)$/s) if $k =~ /^-/; if ($k =~ /^-/) { - $self->_assert_pass_injection_guard($k =~ /^-(.*)$/s); - if ($k eq '-nest') { - # 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}) { - unless (our $Nest_Warned) { - belch( - "-nest in search conditions is deprecated, you most probably wanted:\n" - .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }| - ); - $Nest_Warned = 1; - } - } - return $self->_expand_expr($v); - } if (my ($rest) = $k =~/^-not[_ ](.*)$/) { return +{ -op => [ 'not', - $self->_expand_expr({ "-${rest}", $v }, $logic) + $self->_expand_expr({ "-${rest}", $v }) ] }; } { @@ -663,11 +674,10 @@ sub _expand_expr_hashpair { } if (ref($v) eq 'HASH') { if (keys %$v > 1) { - return { -op => [ - 'and', - map $self->_expand_expr({ $k => { $_ => $v->{$_} } }), + return $self->_expand_op_andor(-and => [ + map +{ $k => { $_ => $v->{$_} } }, sort keys %$v - ] }; + ]); } return undef unless keys %$v; my ($vk, $vv) = %$v; @@ -677,69 +687,12 @@ sub _expand_expr_hashpair { belch 'Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. ' . "You probably wanted ...-and => [ -$op => COND1, -$op => COND2 ... ]"; } - if ($op =~ /^(?:not )?between$/) { + if (my $x = $self->{expand_op}{$op}) { local our $Cur_Col_Meta = $k; - my @rhs = map $self->_expand_expr($_), - ref($vv) eq 'ARRAY' ? @$vv : $vv; - unless ( - (@rhs == 1 and ref($rhs[0]) eq 'HASH' and $rhs[0]->{-literal}) - or - (@rhs == 2 and defined($rhs[0]) and defined($rhs[1])) - ) { - puke "Operator '${\uc($op)}' requires either an arrayref with two defined values or expressions, or a single literal scalarref/arrayref-ref"; - } - return +{ -op => [ - $op, - $self->_expand_ident(-ident => $k), - @rhs - ] } - } - if ($op =~ /^(?:not )?in$/) { - 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), - [ { -literal => [ $opened_sql, @bind ] } ] - ] }; - } - my $undef_err = - 'SQL::Abstract before v1.75 used to generate incorrect SQL when the ' - . "-${\uc($op)} operator was given an undef-containing list: !!!AUDIT YOUR CODE " - . 'AND DATA!!! (the upcoming Data::Query-based version of SQL::Abstract ' - . 'will emit the logically correct SQL instead of raising this exception)' - ; - 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, $_ ] } } - 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), - \@rhs - ] }; + return $self->$x($op, $vv, $k); } - if ($op eq 'ident') { - if (! defined $vv or (ref($vv) and ref($vv) eq 'ARRAY')) { - puke "-$op requires a single plain scalar argument (a quotable identifier) or an arrayref of identifier parts"; - } - return +{ -op => [ - $self->{cmp}, - $self->_expand_ident(-ident => $k), - $self->_expand_ident(-ident => $vv), - ] }; - } - if ($op eq 'value') { + if ($op eq 'value' and not defined($vv)) { return $self->_expand_expr({ $k, undef }) unless defined($vv); - return +{ -op => [ - $self->{cmp}, - $self->_expand_ident(-ident => $k), - { -bind => [ $k, $vv ] } - ] }; } if ($op =~ /^is(?: not)?$/) { puke "$op can only take undef as argument" @@ -751,15 +704,6 @@ sub _expand_expr_hashpair { ); return +{ -op => [ $op.' null', $self->_expand_ident(-ident => $k) ] }; } - if ($op =~ /^(and|or)$/) { - if (ref($vv) eq 'HASH') { - return +{ -op => [ - $op, - map $self->_expand_expr({ $k, { $_ => $vv->{$_} } }), - sort keys %$vv - ] }; - } - } if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) { return { -op => [ $op, $self->_expand_ident(-ident => $k), $vv ] }; } @@ -796,11 +740,10 @@ sub _expand_expr_hashpair { : $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 +{ -op => [ - $logic =~ /^-(.*)$/, - map $self->_expand_expr({ $k => { $vk => $_ } }), + return $self->_expand_op_andor($logic => [ + map +{ $k => { $vk => $_ } }, @values - ] }; + ]); } if ( !defined($vv) @@ -829,14 +772,14 @@ sub _expand_expr_hashpair { if (ref($v) eq 'ARRAY') { return $self->sqlfalse unless @$v; $self->_debug("ARRAY($k) means distribute over elements"); - my $this_logic = lc( + my $logic = lc( $v->[0] =~ /^-(and|or)$/i ? shift(@{$v = [ @$v ]}) - : '-'.($self->{logic} || 'or') + : '-'.lc($self->{logic} || 'OR') + ); + return $self->_expand_op_andor( + $logic => [ map +{ $k => $_ }, @$v ] ); - return $self->_expand_expr({ - $this_logic => [ map +{ $k => $_ }, @$v ] - }); } if (my $literal = is_literal_value($v)) { unless (length $k) { @@ -855,7 +798,10 @@ sub _expand_expr_hashpair { } sub _expand_ident { - my ($self, undef, $body) = @_; + my ($self, $op, $body) = @_; + 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"; + } my @parts = map split(/\Q${\($self->{name_sep}||'.')}\E/, $_), ref($body) ? @$body : $body; return { -ident => $parts[-1] } if $self->{_dequalify_idents}; @@ -882,18 +828,22 @@ sub _expand_bool { return $self->_expand_ident(-ident => $v); } -sub _expand_andor { - my ($self, $k, $v) = @_; - my ($logic) = $k =~ /^-(.*)$/; +sub _expand_op_andor { + my ($self, $logic, $v, $k) = @_; + if (defined $k) { + $v = [ map +{ $k, { $_ => $v->{$_} } }, + sort keys %$v ]; + } + my ($logop) = $logic =~ /^-?(.*)$/; if (ref($v) eq 'HASH') { return +{ -op => [ - $logic, - map $self->_expand_expr({ $_ => $v->{$_} }, $logic), + $logop, + map $self->_expand_expr({ $_ => $v->{$_} }), sort keys %$v ] }; } if (ref($v) eq 'ARRAY') { - $logic eq 'and' or $logic eq 'or' or puke "unknown logic: $logic"; + $logop eq 'and' or $logop eq 'or' or puke "unknown logic: $logop"; my @expr = grep { (ref($_) eq 'ARRAY' and @$_) @@ -923,11 +873,77 @@ sub _expand_andor { } # ??? # return $res[0] if @res == 1; - return { -op => [ $logic, @res ] }; + return { -op => [ $logop, @res ] }; } die "notreached"; } +sub _expand_between { + my ($self, $op, $vv, $k) = @_; + local our $Cur_Col_Meta = $k; + my @rhs = map $self->_expand_expr($_), + ref($vv) eq 'ARRAY' ? @$vv : $vv; + unless ( + (@rhs == 1 and ref($rhs[0]) eq 'HASH' and $rhs[0]->{-literal}) + or + (@rhs == 2 and defined($rhs[0]) and defined($rhs[1])) + ) { + puke "Operator '${\uc($op)}' requires either an arrayref with two defined values or expressions, or a single literal scalarref/arrayref-ref"; + } + return +{ -op => [ + $op, + $self->_expand_ident(-ident => $k), + @rhs + ] } +} + +sub _expand_in { + my ($self, $op, $vv, $k) = @_; + 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), + [ { -literal => [ $opened_sql, @bind ] } ] + ] }; + } + my $undef_err = + 'SQL::Abstract before v1.75 used to generate incorrect SQL when the ' + . "-${\uc($op)} operator was given an undef-containing list: !!!AUDIT YOUR CODE " + . 'AND DATA!!! (the upcoming Data::Query-based version of SQL::Abstract ' + . 'will emit the logically correct SQL instead of raising this exception)' + ; + 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, $_ ] } } + 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), + \@rhs + ] }; +} + +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}) { + unless (our $Nest_Warned) { + belch( + "-nest in search conditions is deprecated, you most probably wanted:\n" + .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }| + ); + $Nest_Warned = 1; + } + } + return $self->_expand_expr($v); +} + sub _recurse_where { my ($self, $where, $logic) = @_;