X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=b7dab9931673f04ad023366287d4a0b90890e6cd;hb=f5def2aa230884f9f244faf05bb30e7001c3a91f;hp=3391319bf93e563ff9b3216ba8dbd8c8c9dac8fc;hpb=585b7eab27021926c280e47a9d49f37427e9dc0d;p=dbsrgits%2FSQL-Abstract.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 3391319..b7dab99 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -187,12 +187,10 @@ 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', }; @@ -201,17 +199,28 @@ sub new { 'not between' => '_expand_between', 'in' => '_expand_in', 'not in' => '_expand_in', - (map +($_ => sub { - my ($self, $op, $arg, $k) = @_; - return +{ -op => [ - $self->{cmp}, - $self->_expand_ident(-ident => $k), - $self->_expand_expr({ '-'.$op => $arg }), - ] }; - }), qw(ident value)), '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}||{}} @@ -553,13 +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) { - return $self->_expand_andor(-and => $expr); + return $self->_expand_op_andor(-and => $expr); } my ($key, $value) = %$expr; if ($key =~ /^-/ and $key =~ s/ [_\s]? \d+ $//x ) { @@ -569,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 }; @@ -588,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'; @@ -596,50 +605,17 @@ 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 =~ /^-/) { - if (my ($rest) = $k =~/^-not[_ ](.*)$/) { - return +{ -op => [ - 'not', - $self->_expand_expr({ "-${rest}", $v }, $logic) - ] }; - } - { - my $op = $k; - $op =~ s/^-// if length($op) > 1; - - # top level special ops are illegal in general - # note that, arguably, if it makes no sense at top level, it also - # makes no sense on the other side of an = sign or similar but DBIC - # gets disappointingly upset if I disallow it - if ( - (our $Expand_Depth) == 1 - 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 ] }; - } - } - if ($self->{render}{$k}) { - return { $k => $v }; - } - if ( - ref($v) eq 'HASH' - and keys %$v == 1 - and (keys %$v)[0] =~ /^-/ - ) { - my ($func) = $k =~ /^-(.*)$/; - if (List::Util::first { $func =~ $_->{regex} } @{$self->{special_ops}}) { - return +{ -op => [ $func, $self->_expand_expr($v) ] }; - } - return +{ -func => [ $func, $self->_expand_expr($v) ] }; - } - if (!ref($v) or is_literal_value($v)) { - return +{ -op => [ $k =~ /^-(.*)$/, $self->_expand_expr($v) ] }; - } + return $self->_expand_expr_hashpair_op($k, $v); } + return $self->_expand_expr_hashpair_ident($k, $v); +} + +sub _expand_expr_hashpair_ident { + my ($self, $k, $v) = @_; + + # undef needs to be re-sent with cmp to achieve IS/IS NOT NULL + if ( !defined($v) or ( @@ -650,41 +626,40 @@ sub _expand_expr_hashpair { ) { return $self->_expand_expr({ $k => { $self->{cmp} => undef } }); } + + my $ik = $self->_expand_ident(-ident => $k); + + # scalars and objects get expanded as whatever requested or values + if (!ref($v) or Scalar::Util::blessed($v)) { my $d = our $Default_Scalar_To; - return +{ - -op => [ - $self->{cmp}, - $self->_expand_ident(-ident => $k), - ($d - ? $self->_expand_expr($d => $v) - : { -bind => [ $k, $v ] } - ) - ] - }; + local our $Cur_Col_Meta = $k; + return $self->_expand_expr_hashpair_ident( + $k, + ($d + ? $self->_expand_expr($d => $v) + : { -value => $v } + ) + ); } if (ref($v) eq 'HASH') { if (keys %$v > 1) { - return $self->_expand_andor(-and => [ - map +{ $k => { $_ => $v->{$_} } }, + return $self->_expand_op_andor(-and => [ + map +{ $_ => $v->{$_} }, sort keys %$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 ) { - 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 ... ]"; + 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 eq 'value' and not defined($vv)) { - return $self->_expand_expr({ $k, undef }) unless defined($vv); - } if ($op =~ /^is(?: not)?$/) { puke "$op can only take undef as argument" if defined($vv) @@ -693,21 +668,15 @@ sub _expand_expr_hashpair { and exists($vv->{-value}) and !defined($vv->{-value}) ); - return +{ -op => [ $op.' null', $self->_expand_ident(-ident => $k) ] }; - } - if ($op =~ /^(and|or)$/) { - return $self->_expand_andor('-'.$op, [ - map +{ $k, { $_ => $vv->{$_} } }, - sort keys %$vv - ]); + return +{ -op => [ $op.' null', $ik ] }; } if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) { - return { -op => [ $op, $self->_expand_ident(-ident => $k), $vv ] }; + return { -op => [ $op, $ik, $vv ] }; } if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) { return { -op => [ $self->{cmp}, - $self->_expand_ident(-ident => $k), + $ik, { -op => [ $op, $vv ] } ] }; } @@ -737,10 +706,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 $self->_expand_andor($logic => [ - map +{ $k => { $vk => $_ } }, + return $self->_expand_op_andor($logic => [ + map +{ $vk => $_ }, @values - ]); + ], $k); } if ( !defined($vv) @@ -757,25 +726,25 @@ sub _expand_expr_hashpair { : $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', $self->_expand_ident(-ident => $k) ] }; + return +{ -op => [ $is.' null', $ik ] }; } local our $Cur_Col_Meta = $k; return +{ -op => [ $op, - $self->_expand_ident(-ident => $k), + $ik, $self->_expand_expr($vv) ] }; } 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_andor( - $this_logic => [ map +{ $k => $_ }, @$v ] + return $self->_expand_op_andor( + $logic => $v, $k ); } if (my $literal = is_literal_value($v)) { @@ -794,6 +763,67 @@ sub _expand_expr_hashpair { die "notreached"; } +sub _expand_expr_hashpair_op { + my ($self, $k, $v) = @_; + + my $op = $k; + $op =~ s/^-// if length($op) > 1; + $self->_assert_pass_injection_guard($op); + + # Ops prefixed with -not_ get converted + + if (my ($rest) = $op =~/^not[_ ](.*)$/) { + return +{ -op => [ + 'not', + $self->_expand_expr({ "-${rest}", $v }) + ] }; + } + + # the old special op system requires illegality for top-level use + + if ( + (our $Expand_Depth) == 1 + and List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}} + ) { + puke "Illegal use of top-level '-$op'" + } + + # 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 + # certainly wrong and there should be expansion anyway) + + if ($self->{render}{$k}) { + return { $k => $v }; + } + + # hashref RHS values get expanded and used as op/func args + + if ( + ref($v) eq 'HASH' + and keys %$v == 1 + and (keys %$v)[0] =~ /^-/ + ) { + my ($func) = $k =~ /^-(.*)$/; + if (List::Util::first { $func =~ $_->{regex} } @{$self->{special_ops}}) { + return +{ -op => [ $func, $self->_expand_expr($v) ] }; + } + return +{ -func => [ $func, $self->_expand_expr($v) ] }; + } + + # scalars and literals get simply expanded + + if (!ref($v) or is_literal_value($v)) { + return +{ -op => [ $op, $self->_expand_expr($v) ] }; + } + + die "notreached"; +} + sub _expand_ident { my ($self, $op, $body) = @_; unless (defined($body) or (ref($body) and ref($body) eq 'ARRAY')) { @@ -825,18 +855,25 @@ 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, $_ }, + (ref($v) eq 'HASH') + ? (map +{ $_ => $v->{$_} }, sort keys %$v) + : @$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 @$_) @@ -866,7 +903,7 @@ sub _expand_andor { } # ??? # return $res[0] if @res == 1; - return { -op => [ $logic, @res ] }; + return { -op => [ $logop, @res ] }; } die "notreached"; }