use List::Util 'first';
use Scalar::Util 'blessed';
-use Sub::Name 'subname';
-use DBIx::Class::_Util qw(is_plain_value is_literal_value);
+use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
+use SQL::Abstract qw(is_plain_value is_literal_value);
use namespace::clean;
#
my $outer_attrs = { %$attrs };
delete @{$outer_attrs}{qw(from bind rows offset group_by _grouped_by_distinct having)};
- my $inner_attrs = { %$attrs };
- delete @{$inner_attrs}{qw(for collapse select as _related_results_construction)};
+ my $inner_attrs = { %$attrs, _simple_passthrough_construction => 1 };
+ delete @{$inner_attrs}{qw(for collapse select as)};
# there is no point of ordering the insides if there is no limit
delete $inner_attrs->{order_by} if (
my @cols = (
( map { $_->[0] } $self->_extract_order_criteria($order_by) ),
- ( $where ? @{ $self->_extract_fixed_condition_columns($where) || [] } : () ),
+ ( $where ? keys %{ $self->_extract_fixed_condition_columns($where) } : () ),
) or return 0;
my $colinfo = $self->_resolve_column_info($ident, \@cols);
? $colinfos->{$_}{-colname}
: ()
}
- @{$self->_extract_fixed_condition_columns($attrs->{where}) || []}
+ keys %{ $self->_extract_fixed_condition_columns($attrs->{where}) }
)
: ()
];
: { $w[0] => undef }
;
}
- elsif ( ref $w[0] ) {
- return \@w;
- }
- elsif ( @w == 2 ) {
+ elsif ( @w == 2 and ! ref $w[0]) {
if ( ( $w[0]||'' ) =~ /^\-and$/i ) {
return (ref $w[1] eq 'HASH' or ref $w[1] eq 'ARRAY')
? $self->_collapse_cond($w[1], (ref $w[1] eq 'ARRAY') )
return $self->_collapse_cond({ @w });
}
}
+ else {
+ return { -or => \@w };
+ }
}
else {
# not a hash not an array
return { '' => $where };
}
- # catchall, some of the things above fall through
- return $where;
+ die 'should not get here';
}
sub _collapse_cond_unroll_pairs {
if (ref $rhs eq 'HASH' and ! keys %$rhs) {
# FIXME - SQLA seems to be doing... nothing...?
}
+ elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-ident}) {
+ push @conds, { $lhs => { '=', $rhs } };
+ }
+ elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-value} and is_plain_value $rhs->{-value}) {
+ push @conds, { $lhs => $rhs->{-value} };
+ }
elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{'='}) {
- for my $p ($self->_collapse_cond_unroll_pairs([ [ $lhs => $rhs->{'='} ] ])) {
-
- # extra sanity check
- if (keys %$p > 1) {
- require Data::Dumper::Concise;
- local $Data::Dumper::Deepcopy = 1;
- $self->throw_exception(
- "Internal error: unexpected collapse unroll:"
- . Data::Dumper::Concise::Dumper { in => { $lhs => $rhs }, out => $p }
- );
- }
+ if( is_literal_value $rhs->{'='}) {
+ push @conds, { $lhs => $rhs };
+ }
+ else {
+ for my $p ($self->_collapse_cond_unroll_pairs([ [ $lhs => $rhs->{'='} ] ])) {
+
+ # extra sanity check
+ if (keys %$p > 1) {
+ require Data::Dumper::Concise;
+ local $Data::Dumper::Deepcopy = 1;
+ $self->throw_exception(
+ "Internal error: unexpected collapse unroll:"
+ . Data::Dumper::Concise::Dumper { in => { $lhs => $rhs }, out => $p }
+ );
+ }
- my ($l, $r) = %$p;
+ my ($l, $r) = %$p;
- push @conds, ( ! length ref $r or is_plain_value($r) )
- ? { $l => $r }
- : { $l => { '=' => $r } }
- ;
+ push @conds, ( ! length ref $r or is_plain_value($r) )
+ ? { $l => $r }
+ : { $l => { '=' => $r } }
+ ;
+ }
}
}
elsif (ref $rhs eq 'ARRAY') {
return @conds;
}
-
-# returns an arrayref of column names which *definitely* have some
-# sort of non-nullable *single* equality requested in the given condition
-# specification. This is used to figure out if a resultset is
-# constrained to a column which is part of a unique constraint,
-# which in turn allows us to better predict how ordering will behave
-# etc.
+# Analyzes a given condition and attempts to extract all columns
+# with a definitive fixed-condition criteria. Returns a hashref
+# of k/v pairs suitable to be passed to set_columns(), with a
+# MAJOR CAVEAT - multi-value (contradictory) equalities are still
+# represented as a reference to the UNRESOVABLE_CONDITION constant
+# The reason we do this is that some codepaths only care about the
+# codition being stable, as opposed to actually making sense
+#
+# The normal mode is used to figure out if a resultset is constrained
+# to a column which is part of a unique constraint, which in turn
+# allows us to better predict how ordering will behave etc.
+#
+# With the optional "consider_nulls" boolean argument, the function
+# is instead used to infer inambiguous values from conditions
+# (e.g. the inheritance of resultset conditions on new_result)
#
-# this is a rudimentary, incomplete, and error-prone extractor
-# however this is OK - it is conservative, and if we can not find
-# something that is in fact there - the stack will recover gracefully
+my $undef_marker = \ do{ my $x = 'undef' };
sub _extract_fixed_condition_columns {
- my $self = shift;
- my $where_hash = $self->_collapse_cond(shift);
-
- my $res;
- for my $c (keys %$where_hash) {
- if (defined (my $v = $where_hash->{$c}) ) {
- if (
- ! length ref $v
- or
- is_plain_value ($v)
- or
- (
- ref $v eq 'HASH'
- and
- keys %$v == 1
- and
- ref $v->{'='}
- and
- is_literal_value($v->{'='})
- )
- ) {
- $res->{$c} = 1;
+ my ($self, $where, $consider_nulls) = @_;
+ my $where_hash = $self->_collapse_cond($_[1]);
+
+ my $res = {};
+ my ($c, $v);
+ for $c (keys %$where_hash) {
+ my $vals;
+
+ if (!defined ($v = $where_hash->{$c}) ) {
+ $vals->{$undef_marker} = $v if $consider_nulls
+ }
+ elsif (
+ ref $v eq 'HASH'
+ and
+ keys %$v == 1
+ ) {
+ if (exists $v->{-value}) {
+ if (defined $v->{-value}) {
+ $vals->{$v->{-value}} = $v->{-value}
+ }
+ elsif( $consider_nulls ) {
+ $vals->{$undef_marker} = $v->{-value};
+ }
}
- elsif (ref $v eq 'ARRAY' and ($v->[0]||'') eq '-and') {
- $res->{$_} = 1 for map { @{ $self->_extract_fixed_condition_columns({ $c => $_ }) } } @{$v}[1..$#$v];
+ # do not need to check for plain values - _collapse_cond did it for us
+ elsif(ref $v->{'='} and is_literal_value($v->{'='}) ) {
+ $vals->{$v->{'='}} = $v->{'='};
}
}
+ elsif (
+ ! length ref $v
+ or
+ is_plain_value ($v)
+ ) {
+ $vals->{$v} = $v;
+ }
+ elsif (ref $v eq 'ARRAY' and ($v->[0]||'') eq '-and') {
+ for ( @{$v}[1..$#$v] ) {
+ my $subval = $self->_extract_fixed_condition_columns({ $c => $_ }, 'consider nulls'); # always fish nulls out on recursion
+ next unless exists $subval->{$c}; # didn't find anything
+ $vals->{defined $subval->{$c} ? $subval->{$c} : $undef_marker} = $subval->{$c};
+ }
+ }
+
+ if (keys %$vals == 1) {
+ ($res->{$c}) = (values %$vals)
+ unless !$consider_nulls and exists $vals->{$undef_marker};
+ }
+ elsif (keys %$vals > 1) {
+ $res->{$c} = UNRESOLVABLE_CONDITION;
+ }
}
- return [ sort keys %$res ];
+ $res;
}
1;