fail_on_internal_wantarray fail_on_internal_call UNRESOLVABLE_CONDITION
);
use Try::Tiny;
-use Data::Compare (); # no imports!!! guard against insane architecture
# not importing first() as it will clash with our own method
use List::Util ();
(ref $_ eq 'HASH' and ! keys %$_)
) and $_ = undef for ($left, $right);
- # either on of the two undef or both undef
- if ( ( (defined $left) xor (defined $right) ) or ! defined $left ) {
+ # either one of the two undef
+ if ( (defined $left) xor (defined $right) ) {
return defined $left ? $left : $right;
}
-
- my $cond = $self->result_source->schema->storage->_collapse_cond({ -and => [$left, $right] });
-
- for my $c (grep { ref $cond->{$_} eq 'ARRAY' and ($cond->{$_}[0]||'') eq '-and' } keys %$cond) {
-
- my @vals = sort @{$cond->{$c}}[ 1..$#{$cond->{$c}} ];
- my @fin = shift @vals;
-
- for my $v (@vals) {
- push @fin, $v unless Data::Compare::Compare( $fin[-1], $v );
- }
-
- $cond->{$c} = (@fin == 1) ? $fin[0] : [-and => @fin ];
+ # both undef
+ elsif ( ! defined $left ) {
+ return undef
+ }
+ else {
+ return $self->result_source->schema->storage->_collapse_cond({ -and => [$left, $right] });
}
-
- $cond;
}
=head2 search_literal
}
}
+ # 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') {
Scalar::Util
List::Util
- Data::Compare
Class::Accessor::Grouped
Class::C3::Componentised
register_lazy_loadable_requires(qw(
Moo
Context::Preserve
+ Data::Compare
));
my $s = DBICTest::Schema->connect('dbi:SQLite::memory:');
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More;
+use lib qw(t/lib);
+use DBICTest ':DiffSQL';
+use SQL::Abstract qw(is_plain_value is_literal_value);
+use List::Util 'shuffle';
+use Data::Dumper;
+$Data::Dumper::Terse = 1;
+$Data::Dumper::Useqq = 1;
+$Data::Dumper::Indent = 0;
+
+my $schema = DBICTest->init_schema();
+
+for my $c (
+ { cond => undef, sql => 'IS NULL' },
+ { cond => { -value => undef }, sql => 'IS NULL' },
+ { cond => \'foo', sql => '= foo' },
+ { cond => 'foo', sql => '= ?', bind => [
+ [ { dbic_colname => "title", sqlt_datatype => "varchar", sqlt_size => 100 } => 'foo' ],
+ [ { dbic_colname => "year", sqlt_datatype => "varchar", sqlt_size => 100 } => 'foo' ],
+ ]},
+ { cond => { -value => 'foo' }, sql => '= ?', bind => [
+ [ { dbic_colname => "title", sqlt_datatype => "varchar", sqlt_size => 100 } => 'foo' ],
+ [ { dbic_colname => "year", sqlt_datatype => "varchar", sqlt_size => 100 } => 'foo' ],
+ ]},
+ { cond => \[ '?', "foo" ], sql => '= ?', bind => [
+ [ {} => 'foo' ],
+ [ {} => 'foo' ],
+ ]},
+) {
+ my $rs = $schema->resultset('CD')->search({}, { columns => 'title' });
+
+ my $bare_cond = is_literal_value($c->{cond}) ? { '=', $c->{cond} } : $c->{cond};
+
+ my @query_steps = (
+ # this is a monkey-wrench, always there
+ { title => { '!=', [ -and => \'bar' ] }, year => { '!=', [ -and => 'bar' ] } },
+
+ { title => $bare_cond, year => { '=', $c->{cond} } },
+ { -and => [ year => $bare_cond, { title => { '=', $c->{cond} } } ] },
+ [ year => $bare_cond ],
+ [ title => $bare_cond ],
+ { -and => [ { year => { '=', $c->{cond} } }, { title => { '=', $c->{cond} } } ] },
+ { -and => { -or => { year => { '=', $c->{cond} } } }, -or => { title => $bare_cond } },
+ );
+
+ if (my $v = is_plain_value($c->{cond})) {
+ push @query_steps,
+ { year => $v->[0] },
+ { title => $v->[0] },
+ { -and => [ year => $v->[0], title => $v->[0] ] },
+ ;
+ }
+
+ @query_steps = shuffle @query_steps;
+
+ $rs = $rs->search($_) for @query_steps;
+
+ my @bind = @{$c->{bind} || []};
+ {
+ no warnings 'misc';
+ splice @bind, 1, 0, [ { dbic_colname => "year", sqlt_datatype => "varchar", sqlt_size => 100 } => 'bar' ];
+ }
+
+ is_same_sql_bind (
+ $rs->as_query,
+ "(
+ SELECT me.title
+ FROM cd me
+ WHERE title != bar AND title $c->{sql} AND year != ? AND year $c->{sql}
+ )",
+ \@bind,
+ 'Double condition correctly collapsed for steps' . Dumper \@query_steps,
+ );
+}
+
+done_testing;
},
{
where => { artistid => { '=' => [ 1 ], }, charfield => { '=' => [-and => \'1', \['?',2] ] }, rank => { '=' => [ $num, $num ] } },
- cc_result => { artistid => 1, charfield => [-and => { '=' => \'1' }, { '=' => \['?',2] } ], rank => { '=' => [$num, $num] } },
+ cc_result => { artistid => 1, charfield => [ -and => { '=' => \['?',2] }, { '=' => \'1' } ], rank => { '=' => [$num, $num] } },
sql => 'WHERE artistid = ? AND charfield = 1 AND charfield = ? AND ( rank = ? OR rank = ? )',
+ collapsed_sql => 'WHERE artistid = ? AND charfield = ? AND charfield = 1 AND ( rank = ? OR rank = ? )',
efcc_result => { artistid => 1, charfield => UNRESOLVABLE_CONDITION },
},
{
where => { -and => [ artistid => 1, artistid => 2 ], name => [ -and => { '!=', 1 }, 2 ], charfield => [ -or => { '=', 2 } ], rank => [-and => undef, { '=', undef }, { '!=', 2 } ] },
- cc_result => { artistid => [ -and => 1, 2 ], name => [ -and => { '!=', 1 }, 2 ], charfield => 2, rank => [ -and => undef, undef, { '!=', 2 } ] },
+ cc_result => { artistid => [ -and => 1, 2 ], name => [ -and => { '!=', 1 }, 2 ], charfield => 2, rank => [ -and => { '!=', 2 }, undef ] },
sql => 'WHERE artistid = ? AND artistid = ? AND charfield = ? AND name != ? AND name = ? AND rank IS NULL AND rank IS NULL AND rank != ?',
+ collapsed_sql => 'WHERE artistid = ? AND artistid = ? AND charfield = ? AND name != ? AND name = ? AND rank != ? AND rank IS NULL',
efcc_result => {
artistid => UNRESOLVABLE_CONDITION,
name => 2,
# batshit insanity, just to be thorough
{
where => { -and => [ [ 'artistid' ], [ -and => [ artistid => { '!=', 69 }, artistid => undef, artistid => { '=' => 200 } ]], artistid => [], { -or => [] }, { -and => [] }, [ 'charfield' ], { name => [] }, 'rank' ] },
- cc_result => { artistid => [ -and => undef, { '!=', 69 }, undef, 200, [] ], charfield => undef, name => [], rank => undef },
+ cc_result => { artistid => [ -and => [], { '!=', 69 }, undef, 200 ], charfield => undef, name => [], rank => undef },
sql => 'WHERE artistid IS NULL AND artistid != ? AND artistid IS NULL AND artistid = ? AND 0=1 AND charfield IS NULL AND 0=1 AND rank IS NULL',
+ collapsed_sql => 'WHERE 0=1 AND artistid != ? AND artistid IS NULL AND artistid = ? AND charfield IS NULL AND 0=1 AND rank IS NULL',
efcc_result => { artistid => UNRESOLVABLE_CONDITION },
efcc_n_result => { artistid => UNRESOLVABLE_CONDITION, charfield => undef, rank => undef },
},
) {
my $name = do { local ($Data::Dumper::Indent, $Data::Dumper::Terse, $Data::Dumper::Sortkeys) = (0, 1, 1); Dumper $w };
- my @orig_sql_bind = $sm->where($w);
+ my ($generated_sql) = $sm->where($w);
- is_same_sql ( $orig_sql_bind[0], $t->{sql}, "Expected SQL from $name" )
+ is_same_sql ( $generated_sql, $t->{sql}, "Expected SQL from $name" )
if exists $t->{sql};
my $collapsed_cond = $schema->storage->_collapse_cond($w);
- is_same_sql_bind(
- \[ $sm->where($collapsed_cond) ],
- \\@orig_sql_bind,
- "Collapse did not alter final SQL based on $name",
+ is_same_sql(
+ ($sm->where($collapsed_cond))[0],
+ ( $t->{collapsed_sql} || $t->{sql} || $generated_sql ),
+ "Collapse did not alter *the semantics* of the final SQL based on $name",
);
is_deeply(