use DBIx::Class::Carp;
use Carp 'croak';
use SQL::Abstract qw( is_literal_value is_plain_value );
-use DBIx::Class::_Util qw( UNRESOLVABLE_CONDITION serialize dump_value );
+use DBIx::Class::_Util qw( UNRESOLVABLE_CONDITION dump_value modver_gt_or_eq );
+
+# Can not use DBIx::Class::_Util::serialize as it is based on
+# Storable and leaks through differences between PVIV and an identical IV
+# Since SQLA itself is lossy in this regard (it does not make proper copies
+# for efficiency) one could end up in a situation where semantically
+# identical values aren't treated as such
+my $dd_obj;
+sub lax_serialize ($) {
+ my $dump_str = (
+ $dd_obj
+ ||=
+ do {
+ require Data::Dumper;
+
+ # Warnings without this on early loads under -w
+ # Why? Because fuck me, that's why :/
+ local $Data::Dumper::Indent = 0
+ unless defined $Data::Dumper::Indent;
+
+ # Make sure each option is spelled out with a value, so that
+ # global environment changes can not override any of these
+ # between two serialization calls
+ #
+ my $d = Data::Dumper->new([])
+ ->Indent('0')
+ ->Purity(0)
+ ->Pad('')
+ ->Useqq(0)
+ ->Terse(1)
+ ->Freezer('')
+ ->Toaster('')
+ ->Deepcopy(0)
+ ->Quotekeys(0)
+ ->Bless('bless')
+ ->Pair(' => ')
+ ->Maxdepth(0)
+ ->Useperl(0)
+ ->Sortkeys(1)
+ ->Deparse(0)
+ ;
+
+ # FIXME - this is kinda ridiculous - there ought to be a
+ # Data::Dumper->new_with_defaults or somesuch...
+ #
+ if( modver_gt_or_eq ( 'Data::Dumper', '2.136' ) ) {
+ $d->Sparseseen(1);
+
+ if( modver_gt_or_eq ( 'Data::Dumper', '2.153' ) ) {
+ $d->Maxrecurse(1000);
+
+ if( modver_gt_or_eq ( 'Data::Dumper', '2.160' ) ) {
+ $d->Trailingcomma(0);
+ }
+ }
+ }
+
+ $d;
+ }
+ )->Values([$_[0]])->Dump;
+
+ $dd_obj->Reset->Values([]);
+
+ $dump_str;
+}
# Attempts to flatten a passed in SQLA condition as much as possible towards
push @{$fin->{-and}}, $c;
}
else {
- for my $col (sort keys %$c) {
+ for my $col (keys %$c) {
# consolidate all -and nodes
if ($col =~ /^\-and$/i) {
}
}
}
+
+ # a deduplication (and sort) pass on all individual -and/-or members
+ for my $op (qw( -and -or )) {
+ if( @{ $fin->{$op} || [] } > 1 ) {
+ my $seen_chunks = { map {
+ lax_serialize($_) => $_
+ } @{$fin->{$op}} };
+
+ $fin->{$op} = [ @{$seen_chunks}{ sort keys %$seen_chunks } ];
+ }
+ }
}
elsif (ref $where eq 'ARRAY') {
# we are always at top-level here, it is safe to dump empty *standalone* pieces
my @keys = keys %$sub_elt;
if ( @keys == 1 and $keys[0] !~ /^\-/ ) {
- $fin_idx->{ "COL_$keys[0]_" . serialize $sub_elt } = $sub_elt;
+ $fin_idx->{ "COL_$keys[0]_" . lax_serialize $sub_elt } = $sub_elt;
}
else {
- $fin_idx->{ "SER_" . serialize $sub_elt } = $sub_elt;
+ $fin_idx->{ "SER_" . lax_serialize $sub_elt } = $sub_elt;
}
}
elsif (! length ref $where->[$i] ) {
my $sub_elt = normalize_sqla_condition({ @{$where}[$i, $i+1] })
or next;
- $fin_idx->{ "COL_$where->[$i]_" . serialize $sub_elt } = $sub_elt;
+ $fin_idx->{ "COL_$where->[$i]_" . lax_serialize $sub_elt } = $sub_elt;
$i++;
}
else {
- $fin_idx->{ "SER_" . serialize $where->[$i] } = normalize_sqla_condition( $where->[$i] ) || next;
+ $fin_idx->{ "SER_" . lax_serialize $where->[$i] } = normalize_sqla_condition( $where->[$i] ) || next;
}
}
my $val_bag = { map {
(! defined $_ ) ? ( UNDEF => undef )
: ( ! length ref $_ or is_plain_value $_ ) ? ( "VAL_$_" => $_ )
- : ( ( 'SER_' . serialize $_ ) => $_ )
+ : ( ( 'SER_' . lax_serialize $_ ) => $_ )
} @{$fin->{$col}}[1 .. $#{$fin->{$col}}] };
if (keys %$val_bag == 1 ) {
is_literal_value($v->{'='})
)
) {
- $vals->{ 'SER_' . serialize $v->{'='} } = $v->{'='};
+ $vals->{ 'SER_' . lax_serialize $v->{'='} } = $v->{'='};
}
}
elsif (
$vals->{
! defined $subval->{$c} ? 'UNDEF'
: ( ! length ref $subval->{$c} or is_plain_value $subval->{$c} ) ? "VAL_$subval->{$c}"
- : ( 'SER_' . serialize $subval->{$c} )
+ : ( 'SER_' . lax_serialize $subval->{$c} )
} = $subval->{$c};
}
}
}
sub serialize ($) {
+ # stable hash order
local $Storable::canonical = 1;
+
+ # explicitly false - there is nothing sensible that can come out of
+ # an attempt at CODE serialization
+ local $Storable::Deparse;
+
+ # take no chances
+ local $Storable::forgive_me;
+
+ # FIXME
+ # A number of codepaths *expect* this to be Storable.pm-based so that
+ # the STORABLE_freeze hooks in the metadata subtree get executed properly
nfreeze($_[0]);
}
->Deparse(1)
;
- $d->Sparseseen(1) if modver_gt_or_eq (
- 'Data::Dumper', '2.136'
- );
+ # FIXME - this is kinda ridiculous - there ought to be a
+ # Data::Dumper->new_with_defaults or somesuch...
+ #
+ if( modver_gt_or_eq ( 'Data::Dumper', '2.136' ) ) {
+ $d->Sparseseen(1);
+
+ if( modver_gt_or_eq ( 'Data::Dumper', '2.153' ) ) {
+ $d->Maxrecurse(1000);
+
+ if( modver_gt_or_eq ( 'Data::Dumper', '2.160' ) ) {
+ $d->Trailingcomma(1);
+ }
+ }
+ }
$d;
}
croak "Nonsensical minimum version supplied"
if ! defined $ver or $ver !~ $ver_rx;
- no strict 'refs';
- my $ver_cache = ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= ( $mod->VERSION
- ? {}
- : croak "$mod does not seem to provide a version (perhaps it never loaded)"
- );
+ my $ver_cache = do {
+ no strict 'refs';
+ ${"${mod}::__DBIC_MODULE_VERSION_CHECKS__"} ||= {}
+ };
! defined $ver_cache->{$ver}
and
local $SIG{__WARN__} = sigwarn_silencer( qr/\Qisn't numeric in subroutine entry/ )
if SPURIOUS_VERSION_CHECK_WARNINGS;
+ # prevent captures by potential __WARN__ hooks or the like:
+ # there is nothing of value that can be happening here, and
+ # leaving a hook in-place can only serve to fail some test
+ local $SIG{__WARN__} if (
+ ! SPURIOUS_VERSION_CHECK_WARNINGS
+ and
+ $SIG{__WARN__}
+ );
+
+ croak "$mod does not seem to provide a version (perhaps it never loaded)"
+ unless $mod->VERSION;
+
local $SIG{__DIE__} if $SIG{__DIE__};
local $@;
eval { $mod->VERSION($ver) } ? 1 : 0;
is($num, 69, 'test overloaded object is "sane"');
is("$num", 69, 'test overloaded object is "sane"');
+my $AttUQoLtUaE = 42;
+my $PVIVmaker = $AttUQoLtUaE . '';
+
my @tests = (
{
where => { artistid => 1, charfield => undef },
},
{
where => { -and => [ \'foo=bar', [ { artistid => { '=', $num } } ], { name => 'Caterwauler McCrae'}, \'buzz=bozz' ] },
- normalized => { -and => [ \'foo=bar', \'buzz=bozz' ], name => 'Caterwauler McCrae', artistid => $num },
+ normalized => { -and => [ \'buzz=bozz', \'foo=bar' ], name => 'Caterwauler McCrae', artistid => $num },
sql => 'WHERE foo=bar AND artistid = ? AND name = ? AND buzz=bozz',
- normalized_sql => 'WHERE foo=bar AND buzz=bozz AND artistid = ? AND name = ?',
+ normalized_sql => 'WHERE buzz=bozz AND foo=bar AND artistid = ? AND name = ?',
equality_extract => { name => 'Caterwauler McCrae', artistid => $num },
},
{
},
{
where => { artistid => { '=' => [ 1 ], }, charfield => { '=' => [ -AND => \'1', \['?',2] ] }, rank => { '=' => [ -OR => $num, $num ] } },
- normalized => { artistid => 1, charfield => [-and => { '=' => \['?',2] }, { '=' => \'1' } ], rank => { '=' => [$num, $num] } },
+ normalized => { artistid => 1, charfield => [-and => { '=' => \'1' }, { '=' => \['?',2] } ], rank => { '=' => [$num, $num] } },
sql => 'WHERE artistid = ? AND charfield = 1 AND charfield = ? AND ( rank = ? OR rank = ? )',
- normalized_sql => 'WHERE artistid = ? AND charfield = ? AND charfield = 1 AND ( rank = ? OR rank = ? )',
equality_extract => { artistid => 1, charfield => UNRESOLVABLE_CONDITION },
},
{
(map { {
where => $_,
sql => 'WHERE (rank = 13 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank != 42)',
- normalized_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank = 13) AND (artistid = ? OR charfield IS NULL OR rank != 42)',
+ normalized_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank != 42) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
normalized => { -and => [
- { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
{ -or => [ artistid => 1, charfield => undef, rank => { '!=' => \42 } ] },
+ { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
] },
equality_extract => {},
equality_considering_nulls_extract => {},
{ -and => [
-or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
- -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
+ -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \$AttUQoLtUaE } },
] },
{
},
{
where => { -and => [
- -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
- -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '=' => \13 } },
- ] },
- normalized => { -and => [
- { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
- { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
+ -or => [ rank => { '=' => \$AttUQoLtUaE }, charfield => { '=' => undef }, artistid => 1 ],
+ -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '=' => \42 } },
] },
- sql => 'WHERE (rank = 13 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
- normalized_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank = 13) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
+ normalized => {
+ -or => [ artistid => 1, charfield => undef, rank => { '=' => \42 } ],
+ },
+ sql => 'WHERE (rank = 42 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank = 42)',
+ normalized_sql => 'WHERE artistid = ? OR charfield IS NULL OR rank = 42',
equality_extract => {},
equality_considering_nulls_extract => {},
},
{
where => { -and => [
+ { -or => [ \42 ] },
+ { -and => [
+ { -or => [ \$AttUQoLtUaE ] },
+ { -or => [ \13 ] },
+ ] },
+ ] },
+ normalized => {
+ -and => [ \13, \42 ],
+ },
+ sql => 'WHERE 42 AND 42 AND 13',
+ normalized_sql => 'WHERE 13 AND 42',
+ equality_extract => {},
+ },
+ {
+ where => { -and => [
-or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
-or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
-and => [ foo => { '=' => \1 }, bar => 2 ],
AND NOT foo = ?
',
normalized_sql => 'WHERE
- ( artistid = ? OR charfield IS NULL OR rank = 13 )
- AND ( artistid = ? OR charfield IS NULL OR rank != 42 )
- AND (EXISTS (SELECT 1))
+ (EXISTS (SELECT 1))
AND (EXISTS (SELECT 2))
AND NOT foo = ?
AND NOT foo = ?
+ AND ( artistid = ? OR charfield IS NULL OR rank != 42 )
+ AND ( artistid = ? OR charfield IS NULL OR rank = 13 )
AND bar = 4
AND bar = ?
AND foo = 1
',
normalized => {
-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 } },
+ { -not => { foo => 69 } },
+ { -or => [ artistid => 1, charfield => undef, rank => { '!=' => \42 } ] },
+ { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
],
foo => [ -and => { '=' => \1 }, 3 ],
bar => [ -and => { '=' => \4 }, 2 ],
\'baz = ber',
],
},
- sql => 'WHERE foo = bar AND baz = ber',
normalized => {
-and => [
- \'foo = bar',
\'baz = ber',
+ \'foo = bar',
],
},
+ sql => 'WHERE foo = bar AND baz = ber',
+ normalized_sql => 'WHERE baz = ber AND foo = bar',
equality_extract => {},
},
{
x => { -ident => 'y' },
],
},
- sql => 'WHERE foo = bar AND baz = ber AND x = y',
normalized => {
-and => [
- \'foo = bar',
\'baz = ber',
+ \'foo = bar',
],
x => { '=' => { -ident => 'y' } }
},
+ sql => 'WHERE foo = bar AND baz = ber AND x = y',
+ normalized_sql => 'WHERE baz = ber AND foo = bar AND x = y',
equality_extract => { x => { -ident => 'y' } },
},
);