Skip to content

Commit

Permalink
Fix condition collapser corrupting -X operators
Browse files Browse the repository at this point in the history
This is (fingercross) the complete fix for RT#98161
  • Loading branch information
ribasushi committed Sep 3, 2014
1 parent 22485a7 commit 135ac69
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 51 deletions.
116 changes: 67 additions & 49 deletions lib/DBIx/Class/Storage/DBIHacks.pm
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,8 @@ sub _extract_colinfo_of_stable_main_source_order_by_portion {
sub _collapse_cond {
my ($self, $where, $where_is_anded_array) = @_;

my $fin;

if (! $where) {
return;
}
Expand Down Expand Up @@ -1018,65 +1020,40 @@ sub _collapse_cond {
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;

Expand All @@ -1092,20 +1069,23 @@ sub _collapse_cond {
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
Expand All @@ -1114,10 +1094,48 @@ sub _collapse_cond {
}
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 {
Expand Down
17 changes: 15 additions & 2 deletions t/search/stack_cond.t
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ for my $c (
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} } } ] },
Expand Down Expand Up @@ -69,7 +71,18 @@ for my $c (
"(
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,
Expand Down
99 changes: 99 additions & 0 deletions t/sqlmaker/dbihacks_internals.t
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,105 @@ for my $t (
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%' } ],
Expand Down

0 comments on commit 135ac69

Please sign in to comment.