sub _collapse_cond {
my ($self, $where, $where_is_anded_array) = @_;
+ my $fin;
+
if (! $where) {
return;
}
or return;
# Consolidate various @conds back into something more compact
- my $fin;
-
for my $c (@conds) {
if (ref $c ne 'HASH') {
push @{$fin->{-and}}, $c;
}
else {
for my $col (sort keys %$c) {
- if (exists $fin->{$col}) {
- my ($l, $r) = ($fin->{$col}, $c->{$col});
-
- (ref $_ ne 'ARRAY' or !@$_) and $_ = [ -and => $_ ] for ($l, $r);
- if (@$l and @$r and $l->[0] eq $r->[0] and $l->[0] =~ /^\-and$/i) {
- $fin->{$col} = [ -and => map { @$_[1..$#$_] } ($l, $r) ];
- }
- else {
- $fin->{$col} = [ -and => $fin->{$col}, $c->{$col} ];
- }
+ # consolidate all -and nodes
+ if ($col =~ /^\-and$/i) {
+ push @{$fin->{-and}},
+ ref $c->{$col} eq 'ARRAY' ? @{$c->{$col}}
+ : ref $c->{$col} eq 'HASH' ? %{$c->{$col}}
+ : { $col => $c->{$col} }
+ ;
+ }
+ elsif ($col =~ /^\-/) {
+ push @{$fin->{-and}}, { $col => $c->{$col} };
+ }
+ elsif (exists $fin->{$col}) {
+ $fin->{$col} = [ -and => map {
+ (ref $_ eq 'ARRAY' and ($_->[0]||'') =~ /^\-and$/i )
+ ? @{$_}[1..$#$_]
+ : $_
+ ;
+ } ($fin->{$col}, $c->{$col}) ];
}
else {
$fin->{$col} = $c->{$col};
}
}
}
-
- # unroll single-element -and nodes
- if ( ref $fin->{-and} eq 'ARRAY' and @{$fin->{-and}} == 1 ) {
- my $piece = (delete $fin->{-and})->[0];
- if (ref $piece eq 'ARRAY') {
- $fin->{-or} = $fin->{-or} ? [ $piece, $fin->{-or} ] : $piece;
- }
- elsif (! exists $fin->{''}) {
- $fin->{''} = $piece;
- }
- }
-
- # compress same-column conds found in $fin
- for my $col ( keys %$fin ) {
- next unless ref $fin->{$col} eq 'ARRAY' and ($fin->{$col}[0]||'') eq '-and';
- my $val_bag = { map {
- (! defined $_ ) ? ( UNDEF => undef )
- : ( ! ref $_ or is_plain_value $_ ) ? ( "VAL_$_" => $_ )
- : ( ( 'SER_' . serialize $_ ) => $_ )
- } @{$fin->{$col}}[1 .. $#{$fin->{$col}}] };
-
- if (keys %$val_bag == 1 ) {
- ($fin->{$col}) = values %$val_bag;
- }
- else {
- $fin->{$col} = [ -and => map { $val_bag->{$_} } sort keys %$val_bag ];
- }
- }
-
- return $fin;
}
elsif (ref $where eq 'ARRAY') {
-
# we are always at top-level here, it is safe to dump empty *standalone* pieces
my $fin_idx;
my $sub_elt = $self->_collapse_cond({ $logic_mod => $where->[$i] })
or next;
- $fin_idx->{ serialize $sub_elt } = $sub_elt;
+ $fin_idx->{ "SER_" . serialize $sub_elt } = $sub_elt;
}
elsif (! length ref $where->[$i] ) {
- $fin_idx->{"$where->[$i]_$i"} = $self->_collapse_cond({ @{$where}[$i, $i+1] }) || next;
+ my $sub_elt = $self->_collapse_cond({ @{$where}[$i, $i+1] })
+ or next;
+
+ $fin_idx->{ "COL_$where->[$i]_" . serialize $sub_elt } = $sub_elt;
$i++;
}
else {
- $fin_idx->{ serialize $where->[$i] } = $self->_collapse_cond( $where->[$i] ) || next;
+ $fin_idx->{ "SER_" . serialize $where->[$i] } = $self->_collapse_cond( $where->[$i] ) || next;
}
}
return unless $fin_idx;
- return ( keys %$fin_idx == 1 ) ? (values %$fin_idx)[0] : {
+ $fin = ( keys %$fin_idx == 1 ) ? (values %$fin_idx)[0] : {
-or => [ map
{ ref $fin_idx->{$_} eq 'HASH' ? %{$fin_idx->{$_}} : $fin_idx->{$_} }
sort keys %$fin_idx
}
else {
# not a hash not an array
- return { '' => $where };
+ $fin = { '' => $where };
+ }
+
+ # unroll single-element -and's
+ while (
+ $fin->{-and}
+ and
+ @{$fin->{-and}} < 2
+ ) {
+ my $and = delete $fin->{-and};
+ last if @$and == 0;
+
+ # at this point we have @$and == 1
+ if (
+ ref $and->[0] eq 'HASH'
+ and
+ ! grep { exists $fin->{$_} } keys %{$and->[0]}
+ ) {
+ $fin = {
+ %$fin, %{$and->[0]}
+ };
+ }
+ }
+
+ # compress same-column conds found in $fin
+ for my $col ( grep { $_ !~ /^\-/ } keys %$fin ) {
+ next unless ref $fin->{$col} eq 'ARRAY' and ($fin->{$col}[0]||'') =~ /^\-and$/i;
+ my $val_bag = { map {
+ (! defined $_ ) ? ( UNDEF => undef )
+ : ( ! ref $_ or is_plain_value $_ ) ? ( "VAL_$_" => $_ )
+ : ( ( 'SER_' . serialize $_ ) => $_ )
+ } @{$fin->{$col}}[1 .. $#{$fin->{$col}}] };
+
+ if (keys %$val_bag == 1 ) {
+ ($fin->{$col}) = values %$val_bag;
+ }
+ else {
+ $fin->{$col} = [ -and => map { $val_bag->{$_} } sort keys %$val_bag ];
+ }
}
- die 'should not get here';
+ return keys %$fin ? $fin : ();
}
sub _collapse_cond_unroll_pairs {
my $bare_cond = is_literal_value($c->{cond}) ? { '=', $c->{cond} } : $c->{cond};
my @query_steps = (
- # this is a monkey-wrench, always there
+ # these are monkey-wrenches, always there
{ title => { '!=', [ -and => \'bar' ] }, year => { '!=', [ -and => 'bar' ] } },
+ { -or => [ genreid => undef, genreid => { '!=' => \42 } ] },
+ { -or => [ genreid => undef, genreid => { '!=' => \42 } ] },
{ title => $bare_cond, year => { '=', $c->{cond} } },
{ -and => [ year => $bare_cond, { title => { '=', $c->{cond} } } ] },
"(
SELECT me.title
FROM cd me
- WHERE title != bar AND title $c->{sql} AND year != ? AND year $c->{sql}
+ WHERE
+ ( genreid != 42 OR genreid IS NULL )
+ AND
+ ( genreid != 42 OR genreid IS NULL )
+ AND
+ title != bar
+ AND
+ title $c->{sql}
+ AND
+ year != ?
+ AND
+ year $c->{sql}
)",
\@bind,
'Double condition correctly collapsed for steps' . Dumper \@query_steps,
rank => undef,
},
},
+ (map { {
+ where => $_,
+ sql => 'WHERE (rank = 13 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank != 42)',
+ collapsed_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank = 13) AND (artistid = ? OR charfield IS NULL OR rank != 42)',
+ cc_result => { -and => [
+ { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
+ { -or => [ artistid => 1, charfield => undef, rank => { '!=' => \42 } ] },
+ ] },
+ efcc_result => {},
+ efcc_n_result => {},
+ } } (
+ { -and => [
+ -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
+ -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
+ ] },
+
+ {
+ -OR => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
+ -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
+ },
+ ) ),
+ {
+ where => { -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => { '=' => 1 }, genreid => { '=' => \['?', 2] } ] },
+ sql => 'WHERE rank = 13 OR charfield IS NULL OR artistid = ? OR genreid = ?',
+ collapsed_sql => 'WHERE artistid = ? OR charfield IS NULL OR genreid = ? OR rank = 13',
+ cc_result => { -or => [ artistid => 1, charfield => undef, genreid => { '=' => \['?', 2] }, rank => { '=' => \13 } ] },
+ efcc_result => {},
+ efcc_n_result => {},
+ },
+ {
+ where => { -and => [
+ -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
+ -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '=' => \13 } },
+ ] },
+ cc_result => { -and => [
+ { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
+ { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
+ ] },
+ sql => 'WHERE (rank = 13 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
+ collapsed_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank = 13) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
+ efcc_result => {},
+ efcc_n_result => {},
+ },
+ {
+ where => { -and => [
+ -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
+ -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
+ -and => [ foo => { '=' => \1 }, bar => 2 ],
+ -and => [ foo => 3, bar => { '=' => \4 } ],
+ -exists => \'(SELECT 1)',
+ -exists => \'(SELECT 2)',
+ -not => { foo => 69 },
+ -not => { foo => 42 },
+ ]},
+ sql => 'WHERE
+ ( rank = 13 OR charfield IS NULL OR artistid = ? )
+ AND ( artistid = ? OR charfield IS NULL OR rank != 42 )
+ AND foo = 1
+ AND bar = ?
+ AND foo = ?
+ AND bar = 4
+ AND (EXISTS (SELECT 1))
+ AND (EXISTS (SELECT 2))
+ AND NOT foo = ?
+ AND NOT foo = ?
+ ',
+ collapsed_sql => 'WHERE
+ ( artistid = ? OR charfield IS NULL OR rank = 13 )
+ AND ( artistid = ? OR charfield IS NULL OR rank != 42 )
+ AND (EXISTS (SELECT 1))
+ AND (EXISTS (SELECT 2))
+ AND NOT foo = ?
+ AND NOT foo = ?
+ AND bar = 4
+ AND bar = ?
+ AND foo = 1
+ AND foo = ?
+ ',
+ cc_result => {
+ -and => [
+ { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
+ { -or => [ artistid => 1, charfield => undef, rank => { '!=' => \42 } ] },
+ { -exists => \'(SELECT 1)' },
+ { -exists => \'(SELECT 2)' },
+ { -not => { foo => 69 } },
+ { -not => { foo => 42 } },
+ ],
+ foo => [ -and => { '=' => \1 }, 3 ],
+ bar => [ -and => { '=' => \4 }, 2 ],
+ },
+ efcc_result => {
+ foo => UNRESOLVABLE_CONDITION,
+ bar => UNRESOLVABLE_CONDITION,
+ },
+ efcc_n_result => {
+ foo => UNRESOLVABLE_CONDITION,
+ bar => UNRESOLVABLE_CONDITION,
+ },
+ },
{
where => { -and => [
[ '_macro.to' => { -like => '%correct%' }, '_wc_macros.to' => { -like => '%correct%' } ],