| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package   #hide from PAUSE | 
| 2 |  |  |  |  |  |  | DBIx::Class::SQLMaker::Util; | 
| 3 |  |  |  |  |  |  |  | 
| 4 | 313 |  |  | 313 |  | 1988 | use strict; | 
|  | 313 |  |  |  |  | 669 |  | 
|  | 313 |  |  |  |  | 8321 |  | 
| 5 | 313 |  |  | 313 |  | 1613 | use warnings; | 
|  | 313 |  |  |  |  | 628 |  | 
|  | 313 |  |  |  |  | 8127 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 313 |  |  | 313 |  | 1637 | use base 'Exporter'; | 
|  | 313 |  |  |  |  | 849 |  | 
|  | 313 |  |  |  |  | 34145 |  | 
| 8 |  |  |  |  |  |  | our @EXPORT_OK = qw( | 
| 9 |  |  |  |  |  |  | normalize_sqla_condition | 
| 10 |  |  |  |  |  |  | extract_equality_conditions | 
| 11 |  |  |  |  |  |  | ); | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 313 |  |  | 313 |  | 2151 | use DBIx::Class::Carp; | 
|  | 313 |  |  |  |  | 1024 |  | 
|  | 313 |  |  |  |  | 2007 |  | 
| 14 | 313 |  |  | 313 |  | 2040 | use Carp 'croak'; | 
|  | 313 |  |  |  |  | 709 |  | 
|  | 313 |  |  |  |  | 16355 |  | 
| 15 | 313 |  |  | 313 |  | 163771 | use SQL::Abstract qw( is_literal_value is_plain_value ); | 
|  | 313 |  |  |  |  | 2993161 |  | 
|  | 313 |  |  |  |  | 27300 |  | 
| 16 | 313 |  |  | 313 |  | 3273 | use DBIx::Class::_Util qw( UNRESOLVABLE_CONDITION dump_value modver_gt_or_eq ); | 
|  | 313 |  |  |  |  | 790 |  | 
|  | 313 |  |  |  |  | 789590 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | # Can not use DBIx::Class::_Util::serialize as it is based on | 
| 19 |  |  |  |  |  |  | # Storable and leaks through differences between PVIV and an identical IV | 
| 20 |  |  |  |  |  |  | # Since SQLA itself is lossy in this regard (it does not make proper copies | 
| 21 |  |  |  |  |  |  | # for efficiency) one could end up in a situation where semantically | 
| 22 |  |  |  |  |  |  | # identical values aren't treated as such | 
| 23 |  |  |  |  |  |  | my $dd_obj; | 
| 24 |  |  |  |  |  |  | sub lax_serialize ($) { | 
| 25 |  |  |  |  |  |  | my $dump_str = ( | 
| 26 |  |  |  |  |  |  | $dd_obj | 
| 27 |  |  |  |  |  |  | ||= | 
| 28 | 13075 |  | 66 | 13075 | 0 | 111645 | do { | 
| 29 | 105 |  |  |  |  | 62056 | require Data::Dumper; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | # Warnings without this on early loads under -w | 
| 32 |  |  |  |  |  |  | # Why? Because fuck me, that's why :/ | 
| 33 | 105 | 100 |  |  |  | 649833 | local $Data::Dumper::Indent = 0 | 
| 34 |  |  |  |  |  |  | unless defined $Data::Dumper::Indent; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | # Make sure each option is spelled out with a value, so that | 
| 37 |  |  |  |  |  |  | # global environment changes can not override any of these | 
| 38 |  |  |  |  |  |  | # between two serialization calls | 
| 39 |  |  |  |  |  |  | # | 
| 40 | 105 |  |  |  |  | 1197 | my $d = Data::Dumper->new([]) | 
| 41 |  |  |  |  |  |  | ->Indent('0') | 
| 42 |  |  |  |  |  |  | ->Purity(0) | 
| 43 |  |  |  |  |  |  | ->Pad('') | 
| 44 |  |  |  |  |  |  | ->Useqq(0) | 
| 45 |  |  |  |  |  |  | ->Terse(1) | 
| 46 |  |  |  |  |  |  | ->Freezer('') | 
| 47 |  |  |  |  |  |  | ->Toaster('') | 
| 48 |  |  |  |  |  |  | ->Deepcopy(0) | 
| 49 |  |  |  |  |  |  | ->Quotekeys(0) | 
| 50 |  |  |  |  |  |  | ->Bless('bless') | 
| 51 |  |  |  |  |  |  | ->Pair(' => ') | 
| 52 |  |  |  |  |  |  | ->Maxdepth(0) | 
| 53 |  |  |  |  |  |  | ->Useperl(0) | 
| 54 |  |  |  |  |  |  | ->Sortkeys(1) | 
| 55 |  |  |  |  |  |  | ->Deparse(0) | 
| 56 |  |  |  |  |  |  | ; | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | # FIXME - this is kinda ridiculous - there ought to be a | 
| 59 |  |  |  |  |  |  | # Data::Dumper->new_with_defaults or somesuch... | 
| 60 |  |  |  |  |  |  | # | 
| 61 | 105 | 50 |  |  |  | 14223 | if( modver_gt_or_eq ( 'Data::Dumper', '2.136' ) ) { | 
| 62 | 105 |  |  |  |  | 686 | $d->Sparseseen(1); | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 105 | 50 |  |  |  | 1068 | if( modver_gt_or_eq ( 'Data::Dumper', '2.153' ) ) { | 
| 65 | 105 |  |  |  |  | 824 | $d->Maxrecurse(1000); | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 105 | 50 |  |  |  | 939 | if( modver_gt_or_eq ( 'Data::Dumper', '2.160' ) ) { | 
| 68 | 105 |  |  |  |  | 625 | $d->Trailingcomma(0); | 
| 69 |  |  |  |  |  |  | } | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 105 |  |  |  |  | 3138 | $d; | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  | )->Values([$_[0]])->Dump; | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 13075 |  |  |  |  | 441210 | $dd_obj->Reset->Values([]); | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 13075 |  |  |  |  | 217873 | $dump_str; | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # Attempts to flatten a passed in SQLA condition as much as possible towards | 
| 84 |  |  |  |  |  |  | # a plain hashref, *without* altering its semantics. | 
| 85 |  |  |  |  |  |  | # | 
| 86 |  |  |  |  |  |  | # FIXME - while relatively robust, this is still imperfect, one of the first | 
| 87 |  |  |  |  |  |  | # things to tackle when we get access to a formalized AST. Note that this code | 
| 88 |  |  |  |  |  |  | # is covered by a *ridiculous* amount of tests, so starting with porting this | 
| 89 |  |  |  |  |  |  | # code would be a rather good exercise | 
| 90 |  |  |  |  |  |  | sub normalize_sqla_condition { | 
| 91 | 47562 |  |  | 47562 | 0 | 179972 | my ($where, $where_is_anded_array) = @_; | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 47562 |  |  |  |  | 71958 | my $fin; | 
| 94 |  |  |  |  |  |  |  | 
| 95 | 47562 | 100 | 100 |  |  | 219881 | if (! $where) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 96 | 272 |  |  |  |  | 544 | return; | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  | elsif ($where_is_anded_array or ref $where eq 'HASH') { | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 42096 |  |  |  |  | 71056 | my @pairs; | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 42096 | 100 |  |  |  | 111289 | my @pieces = $where_is_anded_array ? @$where : $where; | 
| 103 | 42096 |  |  |  |  | 99675 | while (@pieces) { | 
| 104 | 46689 |  |  |  |  | 84126 | my $chunk = shift @pieces; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 46689 | 100 |  |  |  | 113633 | if (ref $chunk eq 'HASH') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 107 | 45321 |  |  |  |  | 156055 | for (sort keys %$chunk) { | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | # Match SQLA 1.79 behavior | 
| 110 | 49997 | 100 |  |  |  | 111912 | unless( length $_ ) { | 
| 111 | 128 | 100 |  |  |  | 525 | is_literal_value($chunk->{$_}) | 
| 112 |  |  |  |  |  |  | ? carp 'Hash-pairs consisting of an empty string with a literal are deprecated, use -and => [ $literal ] instead' | 
| 113 |  |  |  |  |  |  | : croak 'Supplying an empty left hand side argument is not supported in hash-pairs' | 
| 114 |  |  |  |  |  |  | ; | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 49929 |  |  |  |  | 181264 | push @pairs, $_ => $chunk->{$_}; | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  | elsif (ref $chunk eq 'ARRAY') { | 
| 121 | 511 | 100 |  |  |  | 2441 | push @pairs, -or => $chunk | 
| 122 |  |  |  |  |  |  | if @$chunk; | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  | elsif ( ! length ref $chunk) { | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | # Match SQLA 1.79 behavior | 
| 127 | 454 | 100 | 100 |  |  | 2667 | croak("Supplying an empty left hand side argument is not supported in array-pairs") | 
|  |  |  | 66 |  |  |  |  | 
| 128 |  |  |  |  |  |  | if $where_is_anded_array and (! defined $chunk or ! length $chunk); | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 444 |  |  |  |  | 1227 | push @pairs, $chunk, shift @pieces; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  | else { | 
| 133 | 403 |  |  |  |  | 1149 | push @pairs, '', $chunk; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  | } | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 42018 | 100 |  |  |  | 94547 | return unless @pairs; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 41688 | 100 |  |  |  | 112501 | my @conds = _normalize_cond_unroll_pairs(\@pairs) | 
| 140 |  |  |  |  |  |  | or return; | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | # Consolidate various @conds back into something more compact | 
| 143 | 41120 |  |  |  |  | 90170 | for my $c (@conds) { | 
| 144 | 50662 | 50 |  |  |  | 119016 | if (ref $c ne 'HASH') { | 
| 145 | 0 |  |  |  |  | 0 | push @{$fin->{-and}}, $c; | 
|  | 0 |  |  |  |  | 0 |  | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  | else { | 
| 148 | 50662 |  |  |  |  | 134409 | for my $col (keys %$c) { | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # consolidate all -and nodes | 
| 151 | 56636 | 100 |  |  |  | 223530 | if ($col =~ /^\-and$/i) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 152 | 998 |  |  |  |  | 3221 | push @{$fin->{-and}}, | 
| 153 | 998 |  |  |  |  | 2693 | ref $c->{$col} eq 'ARRAY' ? @{$c->{$col}} | 
| 154 | 0 |  |  |  |  | 0 | : ref $c->{$col} eq 'HASH' ? %{$c->{$col}} | 
| 155 | 998 | 0 |  |  |  | 1506 | : { $col => $c->{$col} } | 
|  |  | 50 |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | ; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  | elsif ($col =~ /^\-/) { | 
| 159 | 2028 |  |  |  |  | 2915 | push @{$fin->{-and}}, { $col => $c->{$col} }; | 
|  | 2028 |  |  |  |  | 7327 |  | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | elsif (exists $fin->{$col}) { | 
| 162 |  |  |  |  |  |  | $fin->{$col} = [ -and => map { | 
| 163 |  |  |  |  |  |  | (ref $_ eq 'ARRAY' and ($_->[0]||'') =~ /^\-and$/i ) | 
| 164 | 3160 | 100 | 100 |  |  | 15528 | ? @{$_}[1..$#$_] | 
|  | 89 |  |  |  |  | 342 |  | 
| 165 |  |  |  |  |  |  | : $_ | 
| 166 |  |  |  |  |  |  | ; | 
| 167 | 1580 |  |  |  |  | 5224 | } ($fin->{$col}, $c->{$col}) ]; | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  | else { | 
| 170 | 52030 |  |  |  |  | 147921 | $fin->{$col} = $c->{$col}; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | # a deduplication (and sort) pass on all individual -and/-or members | 
| 177 | 41120 |  |  |  |  | 78272 | for my $op (qw( -and -or )) { | 
| 178 | 82240 | 100 |  |  |  | 116663 | if( @{ $fin->{$op} || [] } > 1 ) { | 
|  | 82240 | 100 |  |  |  | 378714 |  | 
| 179 |  |  |  |  |  |  | my $seen_chunks = { map { | 
| 180 | 796 |  |  |  |  | 1328 | lax_serialize($_) => $_ | 
| 181 | 315 |  |  |  |  | 455 | } @{$fin->{$op}} }; | 
|  | 315 |  |  |  |  | 586 |  | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 315 |  |  |  |  | 1134 | $fin->{$op} = [ @{$seen_chunks}{ sort keys %$seen_chunks } ]; | 
|  | 315 |  |  |  |  | 1202 |  | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | elsif (ref $where eq 'ARRAY') { | 
| 188 |  |  |  |  |  |  | # we are always at top-level here, it is safe to dump empty *standalone* pieces | 
| 189 | 4498 |  |  |  |  | 7501 | my $fin_idx; | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 4498 |  |  |  |  | 15085 | for (my $i = 0; $i <= $#$where; $i++ ) { | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | # Match SQLA 1.79 behavior | 
| 194 | 7505 | 100 | 100 |  |  | 33521 | croak( | 
| 195 |  |  |  |  |  |  | "Supplying an empty left hand side argument is not supported in array-pairs" | 
| 196 |  |  |  |  |  |  | ) if (! defined $where->[$i] or ! length $where->[$i]); | 
| 197 |  |  |  |  |  |  |  | 
| 198 | 7349 |  | 100 |  |  | 36385 | my $logic_mod = lc ( ($where->[$i] =~ /^(\-(?:and|or))$/i)[0] || '' ); | 
| 199 |  |  |  |  |  |  |  | 
| 200 | 7349 | 100 |  |  |  | 20294 | if ($logic_mod) { | 
|  |  | 100 |  |  |  |  |  | 
| 201 | 1175 |  |  |  |  | 1733 | $i++; | 
| 202 | 1175 | 50 | 66 |  |  | 4512 | croak("Unsupported top-level op/arg pair: [ $logic_mod => $where->[$i] ]") | 
| 203 |  |  |  |  |  |  | unless ref $where->[$i] eq 'HASH' or ref $where->[$i] eq 'ARRAY'; | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 1175 | 100 |  |  |  | 4029 | my $sub_elt = normalize_sqla_condition({ $logic_mod => $where->[$i] }) | 
| 206 |  |  |  |  |  |  | or next; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 922 |  |  |  |  | 2454 | my @keys = keys %$sub_elt; | 
| 209 | 922 | 100 | 100 |  |  | 4412 | if ( @keys == 1 and $keys[0] !~ /^\-/ ) { | 
| 210 | 369 |  |  |  |  | 1472 | $fin_idx->{ "COL_$keys[0]_" . lax_serialize $sub_elt } = $sub_elt; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  | else { | 
| 213 | 553 |  |  |  |  | 1456 | $fin_idx->{ "SER_" . lax_serialize $sub_elt } = $sub_elt; | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  | elsif (! length ref $where->[$i] ) { | 
| 217 | 4013 | 100 |  |  |  | 6530 | my $sub_elt = normalize_sqla_condition({ @{$where}[$i, $i+1] }) | 
|  | 4013 |  |  |  |  | 12796 |  | 
| 218 |  |  |  |  |  |  | or next; | 
| 219 |  |  |  |  |  |  |  | 
| 220 | 4012 |  |  |  |  | 15063 | $fin_idx->{ "COL_$where->[$i]_" . lax_serialize $sub_elt } = $sub_elt; | 
| 221 | 4012 |  |  |  |  | 12815 | $i++; | 
| 222 |  |  |  |  |  |  | } | 
| 223 |  |  |  |  |  |  | else { | 
| 224 | 2161 |  | 100 |  |  | 7268 | $fin_idx->{ "SER_" . lax_serialize $where->[$i] } = normalize_sqla_condition( $where->[$i] ) || next; | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 4164 | 100 |  |  |  | 16805 | if (! $fin_idx) { | 
|  |  | 100 |  |  |  |  |  | 
| 229 | 580 |  |  |  |  | 1842 | return; | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  | elsif ( keys %$fin_idx == 1 ) { | 
| 232 | 2041 |  |  |  |  | 7399 | $fin = (values %$fin_idx)[0]; | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | else { | 
| 235 | 1543 |  |  |  |  | 2560 | my @or; | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | # at this point everything is at most one level deep - unroll if needed | 
| 238 | 1543 |  |  |  |  | 5865 | for (sort keys %$fin_idx) { | 
| 239 | 4376 | 100 | 66 |  |  | 10544 | if ( ref $fin_idx->{$_} eq 'HASH' and keys %{$fin_idx->{$_}} == 1 ) { | 
|  | 4376 |  |  |  |  | 11351 |  | 
| 240 | 4333 |  |  |  |  | 5864 | my ($l, $r) = %{$fin_idx->{$_}}; | 
|  | 4333 |  |  |  |  | 9239 |  | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 4333 | 100 | 66 |  |  | 16733 | if ( | 
|  |  | 50 | 66 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 243 |  |  |  |  |  |  | ref $r eq 'ARRAY' | 
| 244 |  |  |  |  |  |  | and | 
| 245 |  |  |  |  |  |  | ( | 
| 246 |  |  |  |  |  |  | ( @$r == 1 and $l =~ /^\-and$/i ) | 
| 247 |  |  |  |  |  |  | or | 
| 248 |  |  |  |  |  |  | $l =~ /^\-or$/i | 
| 249 |  |  |  |  |  |  | ) | 
| 250 |  |  |  |  |  |  | ) { | 
| 251 | 136 |  |  |  |  | 388 | push @or, @$r | 
| 252 |  |  |  |  |  |  | } | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | elsif ( | 
| 255 |  |  |  |  |  |  | ref $r eq 'HASH' | 
| 256 |  |  |  |  |  |  | and | 
| 257 |  |  |  |  |  |  | keys %$r == 1 | 
| 258 |  |  |  |  |  |  | and | 
| 259 |  |  |  |  |  |  | $l =~ /^\-(?:and|or)$/i | 
| 260 |  |  |  |  |  |  | ) { | 
| 261 | 0 |  |  |  |  | 0 | push @or, %$r; | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | else { | 
| 265 | 4197 |  |  |  |  | 9544 | push @or, $l, $r; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  | else { | 
| 269 | 43 |  |  |  |  | 112 | push @or, $fin_idx->{$_}; | 
| 270 |  |  |  |  |  |  | } | 
| 271 |  |  |  |  |  |  | } | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 1543 |  |  |  |  | 6519 | $fin->{-or} = \@or; | 
| 274 |  |  |  |  |  |  | } | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  | else { | 
| 277 |  |  |  |  |  |  | # not a hash not an array | 
| 278 | 696 |  |  |  |  | 2198 | $fin = { -and => [ $where ] }; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | # unroll single-element -and's | 
| 282 | 45400 |  | 100 |  |  | 134167 | while ( | 
| 283 |  |  |  |  |  |  | $fin->{-and} | 
| 284 |  |  |  |  |  |  | and | 
| 285 | 3605 |  |  |  |  | 10484 | @{$fin->{-and}} < 2 | 
| 286 |  |  |  |  |  |  | ) { | 
| 287 | 3269 |  |  |  |  | 6189 | my $and = delete $fin->{-and}; | 
| 288 | 3269 | 50 |  |  |  | 6512 | last if @$and == 0; | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | # at this point we have @$and == 1 | 
| 291 | 3269 | 100 | 66 |  |  | 8429 | if ( | 
| 292 |  |  |  |  |  |  | ref $and->[0] eq 'HASH' | 
| 293 |  |  |  |  |  |  | and | 
| 294 | 1832 |  |  |  |  | 6399 | ! grep { exists $fin->{$_} } keys %{$and->[0]} | 
|  | 1832 |  |  |  |  | 5054 |  | 
| 295 |  |  |  |  |  |  | ) { | 
| 296 |  |  |  |  |  |  | $fin = { | 
| 297 | 1832 |  |  |  |  | 3539 | %$fin, %{$and->[0]} | 
|  | 1832 |  |  |  |  | 7723 |  | 
| 298 |  |  |  |  |  |  | }; | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  | else { | 
| 301 | 1437 |  |  |  |  | 2365 | $fin->{-and} = $and; | 
| 302 | 1437 |  |  |  |  | 2422 | last; | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # compress same-column conds found in $fin | 
| 307 | 45400 |  |  |  |  | 127576 | for my $col ( grep { $_ !~ /^\-/ } keys %$fin ) { | 
|  | 59538 |  |  |  |  | 194245 |  | 
| 308 | 54064 | 100 | 100 |  |  | 183584 | next unless ref $fin->{$col} eq 'ARRAY' and ($fin->{$col}[0]||'') =~ /^\-and$/i; | 
|  |  |  | 100 |  |  |  |  | 
| 309 |  |  |  |  |  |  | my $val_bag = { map { | 
| 310 | 5477 | 100 | 66 |  |  | 31518 | (! defined $_ )                          ? ( UNDEF => undef ) | 
|  |  | 100 |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | : ( ! length ref $_ or is_plain_value $_ ) ? ( "VAL_$_" => $_ ) | 
| 312 |  |  |  |  |  |  | : ( ( 'SER_' . lax_serialize $_ ) => $_ ) | 
| 313 | 2671 |  |  |  |  | 6838 | } @{$fin->{$col}}[1 .. $#{$fin->{$col}}] }; | 
|  | 2671 |  |  |  |  | 6907 |  | 
|  | 2671 |  |  |  |  | 7267 |  | 
| 314 |  |  |  |  |  |  |  | 
| 315 | 2671 | 100 |  |  |  | 11109 | if (keys %$val_bag == 1 ) { | 
| 316 | 348 |  |  |  |  | 1850 | ($fin->{$col}) = values %$val_bag; | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  | else { | 
| 319 | 2323 |  |  |  |  | 9029 | $fin->{$col} = [ -and => map { $val_bag->{$_} } sort keys %$val_bag ]; | 
|  | 4728 |  |  |  |  | 17122 |  | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  |  | 
| 323 | 45400 | 50 |  |  |  | 224614 | return keys %$fin ? $fin : (); | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | sub _normalize_cond_unroll_pairs { | 
| 327 | 44603 |  |  | 44603 |  | 70635 | my $pairs = shift; | 
| 328 |  |  |  |  |  |  |  | 
| 329 | 44603 |  |  |  |  | 63830 | my @conds; | 
| 330 |  |  |  |  |  |  |  | 
| 331 | 44603 |  |  |  |  | 101557 | while (@$pairs) { | 
| 332 | 54333 |  |  |  |  | 134189 | my ($lhs, $rhs) = splice @$pairs, 0, 2; | 
| 333 |  |  |  |  |  |  |  | 
| 334 | 54333 | 100 |  |  |  | 251651 | if (! length $lhs) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 335 | 463 |  |  |  |  | 1185 | push @conds, normalize_sqla_condition($rhs); | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  | elsif ( $lhs =~ /^\-and$/i ) { | 
| 338 | 5569 |  |  |  |  | 24750 | push @conds, normalize_sqla_condition($rhs, (ref $rhs eq 'ARRAY')); | 
| 339 |  |  |  |  |  |  | } | 
| 340 |  |  |  |  |  |  | elsif ( $lhs =~ /^\-or$/i ) { | 
| 341 |  |  |  |  |  |  | push @conds, normalize_sqla_condition( | 
| 342 | 2660 | 100 |  |  |  | 10174 | (ref $rhs eq 'HASH') ? [ map { $_ => $rhs->{$_} } sort keys %$rhs ] : $rhs | 
|  | 595 |  |  |  |  | 1960 |  | 
| 343 |  |  |  |  |  |  | ); | 
| 344 |  |  |  |  |  |  | } | 
| 345 |  |  |  |  |  |  | else { | 
| 346 | 45641 | 100 | 100 |  |  | 495079 | if (ref $rhs eq 'HASH' and ! keys %$rhs) { | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 347 |  |  |  |  |  |  | # FIXME - SQLA seems to be doing... nothing...? | 
| 348 |  |  |  |  |  |  | } | 
| 349 |  |  |  |  |  |  | # normalize top level -ident, for saner extract_equality_conditions() code | 
| 350 |  |  |  |  |  |  | elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-ident}) { | 
| 351 | 11292 |  |  |  |  | 60308 | push @conds, { $lhs => { '=', $rhs } }; | 
| 352 |  |  |  |  |  |  | } | 
| 353 |  |  |  |  |  |  | elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-value} and is_plain_value $rhs->{-value}) { | 
| 354 | 33 |  |  |  |  | 265 | push @conds, { $lhs => $rhs->{-value} }; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{'='}) { | 
| 357 | 4183 | 100 | 100 |  |  | 22047 | if ( length ref $rhs->{'='} and is_literal_value $rhs->{'='} ) { | 
| 358 | 1268 |  |  |  |  | 13250 | push @conds, { $lhs => $rhs }; | 
| 359 |  |  |  |  |  |  | } | 
| 360 |  |  |  |  |  |  | else { | 
| 361 | 2915 |  |  |  |  | 22418 | for my $p (_normalize_cond_unroll_pairs([ $lhs => $rhs->{'='} ])) { | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | # extra sanity check | 
| 364 | 2920 | 50 |  |  |  | 10981 | if (keys %$p > 1) { | 
| 365 | 0 |  |  |  |  | 0 | local $Data::Dumper::Deepcopy = 1; | 
| 366 | 0 |  |  |  |  | 0 | croak( | 
| 367 |  |  |  |  |  |  | "Internal error: unexpected collapse unroll:" | 
| 368 |  |  |  |  |  |  | . dump_value { in => { $lhs => $rhs }, out => $p } | 
| 369 |  |  |  |  |  |  | ); | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  |  | 
| 372 | 2920 |  |  |  |  | 9618 | my ($l, $r) = %$p; | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | push @conds, ( | 
| 375 |  |  |  |  |  |  | ! length ref $r | 
| 376 |  |  |  |  |  |  | or | 
| 377 |  |  |  |  |  |  | # the unroller recursion may return a '=' prepended value already | 
| 378 | 2920 | 100 | 100 |  |  | 29276 | ref $r eq 'HASH' and keys %$rhs == 1 and exists $rhs->{'='} | 
| 379 |  |  |  |  |  |  | or | 
| 380 |  |  |  |  |  |  | is_plain_value($r) | 
| 381 |  |  |  |  |  |  | ) | 
| 382 |  |  |  |  |  |  | ? { $l => $r } | 
| 383 |  |  |  |  |  |  | : { $l => { '=' => $r } } | 
| 384 |  |  |  |  |  |  | ; | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  | elsif (ref $rhs eq 'ARRAY') { | 
| 389 |  |  |  |  |  |  | # some of these conditionals encounter multi-values - roll them out using | 
| 390 |  |  |  |  |  |  | # an unshift, which will cause extra looping in the while{} above | 
| 391 | 328 | 100 | 100 |  |  | 1705 | if (! @$rhs ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 392 | 66 |  |  |  |  | 219 | push @conds, { $lhs => [] }; | 
| 393 |  |  |  |  |  |  | } | 
| 394 |  |  |  |  |  |  | elsif ( ($rhs->[0]||'') =~ /^\-(?:and|or)$/i ) { | 
| 395 | 118 | 50 |  |  |  | 310 | croak("Value modifier not followed by any values: $lhs => [ $rhs->[0] ] ") | 
| 396 |  |  |  |  |  |  | if @$rhs == 1; | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 118 | 100 |  |  |  | 398 | if( $rhs->[0] =~ /^\-and$/i ) { | 
|  |  | 100 |  |  |  |  |  | 
| 399 | 108 |  |  |  |  | 278 | unshift @$pairs, map { $lhs => $_ } @{$rhs}[1..$#$rhs]; | 
|  | 245 |  |  |  |  | 758 |  | 
|  | 108 |  |  |  |  | 277 |  | 
| 400 |  |  |  |  |  |  | } | 
| 401 |  |  |  |  |  |  | # if not an AND then it's an OR | 
| 402 |  |  |  |  |  |  | elsif(@$rhs == 2) { | 
| 403 | 5 |  |  |  |  | 21 | unshift @$pairs, $lhs => $rhs->[1]; | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  | else { | 
| 406 | 5 |  |  |  |  | 13 | push @conds, { $lhs => [ @{$rhs}[1..$#$rhs] ] }; | 
|  | 5 |  |  |  |  | 21 |  | 
| 407 |  |  |  |  |  |  | } | 
| 408 |  |  |  |  |  |  | } | 
| 409 |  |  |  |  |  |  | elsif (@$rhs == 1) { | 
| 410 | 15 |  |  |  |  | 114 | unshift @$pairs, $lhs => $rhs->[0]; | 
| 411 |  |  |  |  |  |  | } | 
| 412 |  |  |  |  |  |  | else { | 
| 413 | 129 |  |  |  |  | 645 | push @conds, { $lhs => $rhs }; | 
| 414 |  |  |  |  |  |  | } | 
| 415 |  |  |  |  |  |  | } | 
| 416 |  |  |  |  |  |  | # unroll func + { -value => ... } | 
| 417 |  |  |  |  |  |  | elsif ( | 
| 418 |  |  |  |  |  |  | ref $rhs eq 'HASH' | 
| 419 |  |  |  |  |  |  | and | 
| 420 |  |  |  |  |  |  | ( my ($subop) = keys %$rhs ) == 1 | 
| 421 |  |  |  |  |  |  | and | 
| 422 |  |  |  |  |  |  | length ref ((values %$rhs)[0]) | 
| 423 |  |  |  |  |  |  | and | 
| 424 |  |  |  |  |  |  | my $vref = is_plain_value( (values %$rhs)[0] ) | 
| 425 |  |  |  |  |  |  | ) { | 
| 426 | 25 | 100 |  |  |  | 357 | push @conds, ( | 
| 427 |  |  |  |  |  |  | (length ref $$vref) | 
| 428 |  |  |  |  |  |  | ? { $lhs => $rhs } | 
| 429 |  |  |  |  |  |  | : { $lhs => { $subop => $$vref } } | 
| 430 |  |  |  |  |  |  | ); | 
| 431 |  |  |  |  |  |  | } | 
| 432 |  |  |  |  |  |  | else { | 
| 433 | 29774 |  |  |  |  | 126736 | push @conds, { $lhs => $rhs }; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  | } | 
| 436 |  |  |  |  |  |  | } | 
| 437 |  |  |  |  |  |  |  | 
| 438 | 44265 |  |  |  |  | 150055 | return @conds; | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | # Analyzes a given condition and attempts to extract all columns | 
| 442 |  |  |  |  |  |  | # with a definitive fixed-condition criteria. Returns a hashref | 
| 443 |  |  |  |  |  |  | # of k/v pairs suitable to be passed to set_columns(), with a | 
| 444 |  |  |  |  |  |  | # MAJOR CAVEAT - multi-value (contradictory) equalities are still | 
| 445 |  |  |  |  |  |  | # represented as a reference to the UNRESOVABLE_CONDITION constant | 
| 446 |  |  |  |  |  |  | # The reason we do this is that some codepaths only care about the | 
| 447 |  |  |  |  |  |  | # codition being stable, as opposed to actually making sense | 
| 448 |  |  |  |  |  |  | # | 
| 449 |  |  |  |  |  |  | # The normal mode is used to figure out if a resultset is constrained | 
| 450 |  |  |  |  |  |  | # to a column which is part of a unique constraint, which in turn | 
| 451 |  |  |  |  |  |  | # allows us to better predict how ordering will behave etc. | 
| 452 |  |  |  |  |  |  | # | 
| 453 |  |  |  |  |  |  | # With the optional "consider_nulls" boolean argument, the function | 
| 454 |  |  |  |  |  |  | # is instead used to infer inambiguous values from conditions | 
| 455 |  |  |  |  |  |  | # (e.g. the inheritance of resultset conditions on new_result) | 
| 456 |  |  |  |  |  |  | # | 
| 457 |  |  |  |  |  |  | sub extract_equality_conditions { | 
| 458 | 8256 |  |  | 8256 | 0 | 3777546 | my ($where, $consider_nulls) = @_; | 
| 459 | 8256 |  |  |  |  | 23110 | my $where_hash = normalize_sqla_condition($where); | 
| 460 |  |  |  |  |  |  |  | 
| 461 | 8256 |  |  |  |  | 18160 | my $res = {}; | 
| 462 | 8256 |  |  |  |  | 15672 | my ($c, $v); | 
| 463 | 8256 |  |  |  |  | 20763 | for $c (keys %$where_hash) { | 
| 464 | 10108 |  |  |  |  | 16248 | my $vals; | 
| 465 |  |  |  |  |  |  |  | 
| 466 | 10108 | 100 | 100 |  |  | 59714 | if (!defined ($v = $where_hash->{$c}) ) { | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 | 100 |  |  |  |  | 
| 467 | 322 | 100 |  |  |  | 1436 | $vals->{UNDEF} = $v if $consider_nulls | 
| 468 |  |  |  |  |  |  | } | 
| 469 |  |  |  |  |  |  | elsif ( | 
| 470 |  |  |  |  |  |  | ref $v eq 'HASH' | 
| 471 |  |  |  |  |  |  | and | 
| 472 |  |  |  |  |  |  | keys %$v == 1 | 
| 473 |  |  |  |  |  |  | ) { | 
| 474 | 1557 | 100 | 100 |  |  | 11254 | if (exists $v->{-value}) { | 
|  |  | 100 | 66 |  |  |  |  | 
| 475 | 12 | 50 |  |  |  | 31 | if (defined $v->{-value}) { | 
|  |  | 0 |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | $vals->{"VAL_$v->{-value}"} = $v->{-value} | 
| 477 | 12 |  |  |  |  | 49 | } | 
| 478 |  |  |  |  |  |  | elsif( $consider_nulls ) { | 
| 479 | 0 |  |  |  |  | 0 | $vals->{UNDEF} = $v->{-value}; | 
| 480 |  |  |  |  |  |  | } | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  | # do not need to check for plain values - normalize_sqla_condition did it for us | 
| 483 |  |  |  |  |  |  | elsif( | 
| 484 |  |  |  |  |  |  | length ref $v->{'='} | 
| 485 |  |  |  |  |  |  | and | 
| 486 |  |  |  |  |  |  | ( | 
| 487 |  |  |  |  |  |  | ( ref $v->{'='} eq 'HASH' and keys %{$v->{'='}} == 1 and exists $v->{'='}{-ident} ) | 
| 488 |  |  |  |  |  |  | or | 
| 489 |  |  |  |  |  |  | is_literal_value($v->{'='}) | 
| 490 |  |  |  |  |  |  | ) | 
| 491 |  |  |  |  |  |  | ) { | 
| 492 | 1409 |  |  |  |  | 8414 | $vals->{ 'SER_' . lax_serialize $v->{'='} } = $v->{'='}; | 
| 493 |  |  |  |  |  |  | } | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  | elsif ( | 
| 496 |  |  |  |  |  |  | ! length ref $v | 
| 497 |  |  |  |  |  |  | or | 
| 498 |  |  |  |  |  |  | is_plain_value ($v) | 
| 499 |  |  |  |  |  |  | ) { | 
| 500 | 6643 |  |  |  |  | 27187 | $vals->{"VAL_$v"} = $v; | 
| 501 |  |  |  |  |  |  | } | 
| 502 |  |  |  |  |  |  | elsif (ref $v eq 'ARRAY' and ($v->[0]||'') eq '-and') { | 
| 503 | 87 |  |  |  |  | 982 | for ( @{$v}[1..$#$v] ) { | 
|  | 87 |  |  |  |  | 210 |  | 
| 504 | 198 |  |  |  |  | 526 | my $subval = extract_equality_conditions({ $c => $_ }, 'consider nulls');  # always fish nulls out on recursion | 
| 505 | 198 | 100 |  |  |  | 507 | next unless exists $subval->{$c};  # didn't find anything | 
| 506 |  |  |  |  |  |  | $vals->{ | 
| 507 |  |  |  |  |  |  | ! defined $subval->{$c}                                        ? 'UNDEF' | 
| 508 |  |  |  |  |  |  | : ( ! length ref $subval->{$c} or is_plain_value $subval->{$c} ) ? "VAL_$subval->{$c}" | 
| 509 |  |  |  |  |  |  | : ( 'SER_' . lax_serialize $subval->{$c} ) | 
| 510 | 147 | 100 | 66 |  |  | 751 | } = $subval->{$c}; | 
|  |  | 100 |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | } | 
| 512 |  |  |  |  |  |  | } | 
| 513 |  |  |  |  |  |  |  | 
| 514 | 10108 | 100 |  |  |  | 52232 | if (keys %$vals == 1) { | 
|  |  | 100 |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | ($res->{$c}) = (values %$vals) | 
| 516 | 8176 | 100 | 100 |  |  | 54191 | unless !$consider_nulls and exists $vals->{UNDEF}; | 
| 517 |  |  |  |  |  |  | } | 
| 518 |  |  |  |  |  |  | elsif (keys %$vals > 1) { | 
| 519 | 60 |  |  |  |  | 198 | $res->{$c} = UNRESOLVABLE_CONDITION; | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  | } | 
| 522 |  |  |  |  |  |  |  | 
| 523 | 8256 |  |  |  |  | 36457 | $res; | 
| 524 |  |  |  |  |  |  | } | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | 1; |