line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package #hide from PAUSE |
2
|
|
|
|
|
|
|
DBIx::Class::Storage::DBIHacks; |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# This module contains code that should never have seen the light of day, |
6
|
|
|
|
|
|
|
# does not belong in the Storage, or is otherwise unfit for public |
7
|
|
|
|
|
|
|
# display. The arrival of SQLA2 should immediately obsolete 90% of this |
8
|
|
|
|
|
|
|
# |
9
|
|
|
|
|
|
|
|
10
|
270
|
|
|
270
|
|
2473
|
use strict; |
|
270
|
|
|
|
|
945
|
|
|
270
|
|
|
|
|
7898
|
|
11
|
270
|
|
|
270
|
|
1667
|
use warnings; |
|
270
|
|
|
|
|
847
|
|
|
270
|
|
|
|
|
7543
|
|
12
|
|
|
|
|
|
|
|
13
|
270
|
|
|
270
|
|
1519
|
use base 'DBIx::Class::Storage'; |
|
270
|
|
|
|
|
813
|
|
|
270
|
|
|
|
|
149943
|
|
14
|
270
|
|
|
270
|
|
2229
|
use mro 'c3'; |
|
270
|
|
|
|
|
940
|
|
|
270
|
|
|
|
|
2301
|
|
15
|
|
|
|
|
|
|
|
16
|
270
|
|
|
270
|
|
8920
|
use List::Util 'first'; |
|
270
|
|
|
|
|
975
|
|
|
270
|
|
|
|
|
18896
|
|
17
|
270
|
|
|
270
|
|
1893
|
use Scalar::Util 'blessed'; |
|
270
|
|
|
|
|
881
|
|
|
270
|
|
|
|
|
12998
|
|
18
|
270
|
|
|
270
|
|
1821
|
use DBIx::Class::_Util qw(UNRESOLVABLE_CONDITION serialize); |
|
270
|
|
|
|
|
907
|
|
|
270
|
|
|
|
|
14518
|
|
19
|
270
|
|
|
270
|
|
2715
|
use SQL::Abstract qw(is_plain_value is_literal_value); |
|
270
|
|
|
|
|
12012
|
|
|
270
|
|
|
|
|
14706
|
|
20
|
270
|
|
|
270
|
|
2029
|
use DBIx::Class::Carp; |
|
270
|
|
|
|
|
917
|
|
|
270
|
|
|
|
|
1468
|
|
21
|
270
|
|
|
270
|
|
2390
|
use namespace::clean; |
|
270
|
|
|
|
|
2838
|
|
|
270
|
|
|
|
|
1577
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# |
24
|
|
|
|
|
|
|
# This code will remove non-selecting/non-restricting joins from |
25
|
|
|
|
|
|
|
# {from} specs, aiding the RDBMS query optimizer |
26
|
|
|
|
|
|
|
# |
27
|
|
|
|
|
|
|
sub _prune_unused_joins { |
28
|
949
|
|
|
949
|
|
2712
|
my ($self, $attrs) = @_; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# only standard {from} specs are supported, and we could be disabled in general |
31
|
|
|
|
|
|
|
return ($attrs->{from}, {}) unless ( |
32
|
|
|
|
|
|
|
ref $attrs->{from} eq 'ARRAY' |
33
|
|
|
|
|
|
|
and |
34
|
949
|
|
|
|
|
29337
|
@{$attrs->{from}} > 1 |
35
|
|
|
|
|
|
|
and |
36
|
|
|
|
|
|
|
ref $attrs->{from}[0] eq 'HASH' |
37
|
|
|
|
|
|
|
and |
38
|
949
|
100
|
33
|
|
|
3798
|
ref $attrs->{from}[1] eq 'ARRAY' |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
66
|
|
|
|
|
39
|
|
|
|
|
|
|
and |
40
|
|
|
|
|
|
|
$self->_use_join_optimizer |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
892
|
|
|
|
|
4014
|
my $orig_aliastypes = $self->_resolve_aliastypes_from_select_args($attrs); |
44
|
|
|
|
|
|
|
|
45
|
892
|
|
|
|
|
3875
|
my $new_aliastypes = { %$orig_aliastypes }; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# we will be recreating this entirely |
48
|
892
|
|
|
|
|
2851
|
my @reclassify = 'joining'; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# a grouped set will not be affected by amount of rows. Thus any |
51
|
|
|
|
|
|
|
# purely multiplicator classifications can go |
52
|
|
|
|
|
|
|
# (will be reintroduced below if needed by something else) |
53
|
|
|
|
|
|
|
push @reclassify, qw(multiplying premultiplied) |
54
|
892
|
100
|
100
|
|
|
5351
|
if $attrs->{_force_prune_multiplying_joins} or $attrs->{group_by}; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# nuke what will be recalculated |
57
|
892
|
|
|
|
|
1858
|
delete @{$new_aliastypes}{@reclassify}; |
|
892
|
|
|
|
|
2425
|
|
58
|
|
|
|
|
|
|
|
59
|
892
|
|
|
|
|
2736
|
my @newfrom = $attrs->{from}[0]; # FROM head is always present |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# recalculate what we need once the multipliers are potentially gone |
62
|
|
|
|
|
|
|
# ignore premultiplies, since they do not add any value to anything |
63
|
892
|
|
|
|
|
1680
|
my %need_joins; |
64
|
892
|
|
|
|
|
3093
|
for ( @{$new_aliastypes}{grep { $_ ne 'premultiplied' } keys %$new_aliastypes }) { |
|
892
|
|
|
|
|
2618
|
|
|
2537
|
|
|
|
|
5483
|
|
65
|
|
|
|
|
|
|
# add all requested aliases |
66
|
2377
|
|
|
|
|
6942
|
$need_joins{$_} = 1 for keys %$_; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# add all their parents (as per joinpath which is an AoH { table => alias }) |
69
|
2377
|
|
|
|
|
5183
|
$need_joins{$_} = 1 for map { values %$_ } map { @{$_->{-parents}} } values %$_; |
|
3366
|
|
|
|
|
8827
|
|
|
4079
|
|
|
|
|
5244
|
|
|
4079
|
|
|
|
|
8272
|
|
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
892
|
|
|
|
|
2452
|
for my $j (@{$attrs->{from}}[1..$#{$attrs->{from}}]) { |
|
892
|
|
|
|
|
2384
|
|
|
892
|
|
|
|
|
2349
|
|
73
|
|
|
|
|
|
|
push @newfrom, $j if ( |
74
|
|
|
|
|
|
|
(! defined $j->[0]{-alias}) # legacy crap |
75
|
|
|
|
|
|
|
|| |
76
|
|
|
|
|
|
|
$need_joins{$j->[0]{-alias}} |
77
|
1522
|
100
|
100
|
|
|
8037
|
); |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# we have a new set of joiners - for everything we nuked pull the classification |
81
|
|
|
|
|
|
|
# off the original stack |
82
|
892
|
|
|
|
|
2321
|
for my $ctype (@reclassify) { |
83
|
|
|
|
|
|
|
$new_aliastypes->{$ctype} = { map |
84
|
2684
|
100
|
|
|
|
9413
|
{ $need_joins{$_} ? ( $_ => $orig_aliastypes->{$ctype}{$_} ) : () } |
85
|
1336
|
|
|
|
|
2175
|
keys %{$orig_aliastypes->{$ctype}} |
|
1336
|
|
|
|
|
3789
|
|
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
892
|
|
|
|
|
6500
|
return ( \@newfrom, $new_aliastypes ); |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# |
93
|
|
|
|
|
|
|
# This is the code producing joined subqueries like: |
94
|
|
|
|
|
|
|
# SELECT me.*, other.* FROM ( SELECT me.* FROM ... ) JOIN other ON ... |
95
|
|
|
|
|
|
|
# |
96
|
|
|
|
|
|
|
sub _adjust_select_args_for_complex_prefetch { |
97
|
87
|
|
|
87
|
|
318
|
my ($self, $attrs) = @_; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
$self->throw_exception ('Complex prefetches are not supported on resultsets with a custom from attribute') unless ( |
100
|
|
|
|
|
|
|
ref $attrs->{from} eq 'ARRAY' |
101
|
|
|
|
|
|
|
and |
102
|
87
|
|
|
|
|
995
|
@{$attrs->{from}} > 1 |
103
|
|
|
|
|
|
|
and |
104
|
|
|
|
|
|
|
ref $attrs->{from}[0] eq 'HASH' |
105
|
|
|
|
|
|
|
and |
106
|
87
|
50
|
33
|
|
|
819
|
ref $attrs->{from}[1] eq 'ARRAY' |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
107
|
|
|
|
|
|
|
); |
108
|
|
|
|
|
|
|
|
109
|
87
|
|
|
|
|
236
|
my $root_alias = $attrs->{alias}; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# generate inner/outer attribute lists, remove stuff that doesn't apply |
112
|
87
|
|
|
|
|
729
|
my $outer_attrs = { %$attrs }; |
113
|
87
|
|
|
|
|
303
|
delete @{$outer_attrs}{qw(from bind rows offset group_by _grouped_by_distinct having)}; |
|
87
|
|
|
|
|
364
|
|
114
|
|
|
|
|
|
|
|
115
|
87
|
|
|
|
|
650
|
my $inner_attrs = { %$attrs, _simple_passthrough_construction => 1 }; |
116
|
87
|
|
|
|
|
283
|
delete @{$inner_attrs}{qw(for collapse select as)}; |
|
87
|
|
|
|
|
272
|
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# there is no point of ordering the insides if there is no limit |
119
|
|
|
|
|
|
|
delete $inner_attrs->{order_by} if ( |
120
|
|
|
|
|
|
|
delete $inner_attrs->{_order_is_artificial} |
121
|
|
|
|
|
|
|
or |
122
|
|
|
|
|
|
|
! $inner_attrs->{rows} |
123
|
87
|
100
|
100
|
|
|
608
|
); |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# generate the inner/outer select lists |
126
|
|
|
|
|
|
|
# for inside we consider only stuff *not* brought in by the prefetch |
127
|
|
|
|
|
|
|
# on the outside we substitute any function for its alias |
128
|
87
|
|
|
|
|
200
|
$outer_attrs->{select} = [ @{$attrs->{select}} ]; |
|
87
|
|
|
|
|
413
|
|
129
|
|
|
|
|
|
|
|
130
|
87
|
|
|
|
|
231
|
my ($root_node, $root_node_offset); |
131
|
|
|
|
|
|
|
|
132
|
87
|
|
|
|
|
195
|
for my $i (0 .. $#{$inner_attrs->{from}}) { |
|
87
|
|
|
|
|
396
|
|
133
|
107
|
|
|
|
|
274
|
my $node = $inner_attrs->{from}[$i]; |
134
|
107
|
50
|
33
|
|
|
481
|
my $h = (ref $node eq 'HASH') ? $node |
|
|
100
|
|
|
|
|
|
135
|
|
|
|
|
|
|
: (ref $node eq 'ARRAY' and ref $node->[0] eq 'HASH') ? $node->[0] |
136
|
|
|
|
|
|
|
: next |
137
|
|
|
|
|
|
|
; |
138
|
|
|
|
|
|
|
|
139
|
107
|
50
|
50
|
|
|
738
|
if ( ($h->{-alias}||'') eq $root_alias and $h->{-rsrc} ) { |
|
|
|
66
|
|
|
|
|
140
|
87
|
|
|
|
|
202
|
$root_node = $h; |
141
|
87
|
|
|
|
|
182
|
$root_node_offset = $i; |
142
|
87
|
|
|
|
|
247
|
last; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
87
|
50
|
|
|
|
285
|
$self->throw_exception ('Complex prefetches are not supported on resultsets with a custom from attribute') |
147
|
|
|
|
|
|
|
unless $root_node; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# use the heavy duty resolver to take care of aliased/nonaliased naming |
150
|
87
|
|
|
|
|
393
|
my $colinfo = $self->_resolve_column_info($inner_attrs->{from}); |
151
|
87
|
|
|
|
|
236
|
my $selected_root_columns; |
152
|
|
|
|
|
|
|
|
153
|
87
|
|
|
|
|
238
|
for my $i (0 .. $#{$outer_attrs->{select}}) { |
|
87
|
|
|
|
|
417
|
|
154
|
754
|
|
|
|
|
1263
|
my $sel = $outer_attrs->{select}->[$i]; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
next if ( |
157
|
754
|
100
|
100
|
|
|
2757
|
$colinfo->{$sel} and $colinfo->{$sel}{-source_alias} ne $root_alias |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
|
160
|
322
|
100
|
66
|
|
|
1457
|
if (ref $sel eq 'HASH' ) { |
|
|
100
|
|
|
|
|
|
161
|
17
|
|
66
|
|
|
90
|
$sel->{-as} ||= $attrs->{as}[$i]; |
162
|
17
|
|
33
|
|
|
80
|
$outer_attrs->{select}->[$i] = join ('.', $root_alias, ($sel->{-as} || "inner_column_$i") ); |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
elsif (! ref $sel and my $ci = $colinfo->{$sel}) { |
165
|
295
|
|
|
|
|
678
|
$selected_root_columns->{$ci->{-colname}} = 1; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
322
|
|
|
|
|
479
|
push @{$inner_attrs->{select}}, $sel; |
|
322
|
|
|
|
|
700
|
|
169
|
|
|
|
|
|
|
|
170
|
322
|
|
|
|
|
494
|
push @{$inner_attrs->{as}}, $attrs->{as}[$i]; |
|
322
|
|
|
|
|
841
|
|
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# We will need to fetch all native columns in the inner subquery, which may |
174
|
|
|
|
|
|
|
# be a part of an *outer* join condition, or an order_by (which needs to be |
175
|
|
|
|
|
|
|
# preserved outside), or wheres. In other words everything but the inner |
176
|
|
|
|
|
|
|
# selector |
177
|
|
|
|
|
|
|
# We can not just fetch everything because a potential has_many restricting |
178
|
|
|
|
|
|
|
# join collapse *will not work* on heavy data types. |
179
|
87
|
|
|
|
|
1114
|
my $connecting_aliastypes = $self->_resolve_aliastypes_from_select_args({ |
180
|
|
|
|
|
|
|
%$inner_attrs, |
181
|
|
|
|
|
|
|
select => [], |
182
|
|
|
|
|
|
|
}); |
183
|
|
|
|
|
|
|
|
184
|
87
|
100
|
|
|
|
572
|
for (sort map { keys %{$_->{-seen_columns}||{}} } map { values %$_ } values %$connecting_aliastypes) { |
|
630
|
|
|
|
|
909
|
|
|
630
|
|
|
|
|
2764
|
|
|
352
|
|
|
|
|
823
|
|
185
|
555
|
50
|
|
|
|
1211
|
my $ci = $colinfo->{$_} or next; |
186
|
555
|
100
|
100
|
|
|
2038
|
if ( |
187
|
|
|
|
|
|
|
$ci->{-source_alias} eq $root_alias |
188
|
|
|
|
|
|
|
and |
189
|
|
|
|
|
|
|
! $selected_root_columns->{$ci->{-colname}}++ |
190
|
|
|
|
|
|
|
) { |
191
|
|
|
|
|
|
|
# adding it to both to keep limits not supporting dark selectors happy |
192
|
37
|
|
|
|
|
77
|
push @{$inner_attrs->{select}}, $ci->{-fq_colname}; |
|
37
|
|
|
|
|
144
|
|
193
|
37
|
|
|
|
|
75
|
push @{$inner_attrs->{as}}, $ci->{-fq_colname}; |
|
37
|
|
|
|
|
114
|
|
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# construct the inner {from} and lock it in a subquery |
198
|
|
|
|
|
|
|
# we need to prune first, because this will determine if we need a group_by below |
199
|
|
|
|
|
|
|
# throw away all non-selecting, non-restricting multijoins |
200
|
|
|
|
|
|
|
# (since we def. do not care about multiplication of the contents of the subquery) |
201
|
87
|
|
|
|
|
276
|
my $inner_subq = do { |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# must use it here regardless of user requests (vastly gentler on optimizer) |
204
|
87
|
|
|
|
|
353
|
local $self->{_use_join_optimizer} = 1; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# throw away multijoins since we def. do not care about those inside the subquery |
207
|
87
|
|
|
|
|
1075
|
($inner_attrs->{from}, my $inner_aliastypes) = $self->_prune_unused_joins ({ |
208
|
|
|
|
|
|
|
%$inner_attrs, _force_prune_multiplying_joins => 1 |
209
|
|
|
|
|
|
|
}); |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# uh-oh a multiplier (which is not us) left in, this is a problem for limits |
212
|
|
|
|
|
|
|
# we will need to add a group_by to collapse the resultset for proper counts |
213
|
87
|
100
|
100
|
|
|
432
|
if ( |
|
|
|
100
|
|
|
|
|
214
|
64
|
50
|
|
|
|
430
|
grep { $_ ne $root_alias } keys %{ $inner_aliastypes->{multiplying} || {} } |
|
87
|
|
|
|
|
485
|
|
215
|
|
|
|
|
|
|
and |
216
|
|
|
|
|
|
|
# if there are user-supplied groups - assume user knows wtf they are up to |
217
|
|
|
|
|
|
|
( ! $inner_aliastypes->{grouping} or $inner_attrs->{_grouped_by_distinct} ) |
218
|
|
|
|
|
|
|
) { |
219
|
|
|
|
|
|
|
|
220
|
39
|
|
|
|
|
94
|
my $cur_sel = { map { $_ => 1 } @{$inner_attrs->{select}} }; |
|
195
|
|
|
|
|
407
|
|
|
39
|
|
|
|
|
123
|
|
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
# *possibly* supplement the main selection with pks if not already |
223
|
|
|
|
|
|
|
# there, as they will have to be a part of the group_by to collapse |
224
|
|
|
|
|
|
|
# things properly |
225
|
39
|
|
|
|
|
110
|
my $inner_select_with_extras; |
226
|
39
|
|
|
|
|
246
|
my @pks = map { "$root_alias.$_" } $root_node->{-rsrc}->primary_columns |
227
|
|
|
|
|
|
|
or $self->throw_exception( sprintf |
228
|
|
|
|
|
|
|
'Unable to perform complex limited prefetch off %s without declared primary key', |
229
|
|
|
|
|
|
|
$root_node->{-rsrc}->source_name, |
230
|
39
|
50
|
|
|
|
364
|
); |
231
|
39
|
|
|
|
|
128
|
for my $col (@pks) { |
232
|
0
|
|
0
|
|
|
0
|
push @{ $inner_select_with_extras ||= [ @{$inner_attrs->{select}} ] }, $col |
|
0
|
|
|
|
|
0
|
|
233
|
39
|
50
|
|
|
|
215
|
unless $cur_sel->{$col}++; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
39
|
50
|
|
|
|
570
|
($inner_attrs->{group_by}, $inner_attrs->{order_by}) = $self->_group_over_selection({ |
237
|
|
|
|
|
|
|
%$inner_attrs, |
238
|
|
|
|
|
|
|
$inner_select_with_extras ? ( select => $inner_select_with_extras ) : (), |
239
|
|
|
|
|
|
|
_aliastypes => $inner_aliastypes, |
240
|
|
|
|
|
|
|
}); |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# we already optimized $inner_attrs->{from} above |
244
|
|
|
|
|
|
|
# and already local()ized |
245
|
86
|
|
|
|
|
360
|
$self->{_use_join_optimizer} = 0; |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# generate the subquery |
248
|
|
|
|
|
|
|
$self->_select_args_to_query ( |
249
|
86
|
|
|
|
|
239
|
@{$inner_attrs}{qw(from select where)}, |
|
86
|
|
|
|
|
597
|
|
250
|
|
|
|
|
|
|
$inner_attrs, |
251
|
|
|
|
|
|
|
); |
252
|
|
|
|
|
|
|
}; |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# Generate the outer from - this is relatively easy (really just replace |
255
|
|
|
|
|
|
|
# the join slot with the subquery), with a major caveat - we can not |
256
|
|
|
|
|
|
|
# join anything that is non-selecting (not part of the prefetch), but at |
257
|
|
|
|
|
|
|
# the same time is a multi-type relationship, as it will explode the result. |
258
|
|
|
|
|
|
|
# |
259
|
|
|
|
|
|
|
# There are two possibilities here |
260
|
|
|
|
|
|
|
# - either the join is non-restricting, in which case we simply throw it away |
261
|
|
|
|
|
|
|
# - it is part of the restrictions, in which case we need to collapse the outer |
262
|
|
|
|
|
|
|
# result by tackling yet another group_by to the outside of the query |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# work on a shallow copy |
265
|
86
|
|
|
|
|
229
|
my @orig_from = @{$attrs->{from}}; |
|
86
|
|
|
|
|
345
|
|
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
|
268
|
86
|
|
|
|
|
284
|
$outer_attrs->{from} = \ my @outer_from; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# we may not be the head |
271
|
86
|
100
|
|
|
|
311
|
if ($root_node_offset) { |
272
|
|
|
|
|
|
|
# first generate the outer_from, up to the substitution point |
273
|
17
|
|
|
|
|
67
|
@outer_from = splice @orig_from, 0, $root_node_offset; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# substitute the subq at the right spot |
276
|
|
|
|
|
|
|
push @outer_from, [ |
277
|
|
|
|
|
|
|
{ |
278
|
|
|
|
|
|
|
-alias => $root_alias, |
279
|
|
|
|
|
|
|
-rsrc => $root_node->{-rsrc}, |
280
|
|
|
|
|
|
|
$root_alias => $inner_subq, |
281
|
|
|
|
|
|
|
}, |
282
|
|
|
|
|
|
|
# preserve attrs from what is now the head of the from after the splice |
283
|
17
|
|
|
|
|
124
|
@{$orig_from[0]}[1 .. $#{$orig_from[0]}], |
|
17
|
|
|
|
|
50
|
|
|
17
|
|
|
|
|
58
|
|
284
|
|
|
|
|
|
|
]; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
else { |
287
|
|
|
|
|
|
|
@outer_from = { |
288
|
|
|
|
|
|
|
-alias => $root_alias, |
289
|
|
|
|
|
|
|
-rsrc => $root_node->{-rsrc}, |
290
|
69
|
|
|
|
|
379
|
$root_alias => $inner_subq, |
291
|
|
|
|
|
|
|
}; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
86
|
|
|
|
|
216
|
shift @orig_from; # what we just replaced above |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# scan the *remaining* from spec against different attributes, and see which joins are needed |
297
|
|
|
|
|
|
|
# in what role |
298
|
|
|
|
|
|
|
my $outer_aliastypes = $outer_attrs->{_aliastypes} = |
299
|
86
|
|
|
|
|
909
|
$self->_resolve_aliastypes_from_select_args({ %$outer_attrs, from => \@orig_from }); |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# unroll parents |
302
|
86
|
|
|
|
|
538
|
my ($outer_select_chain, @outer_nonselecting_chains) = map { +{ |
303
|
344
|
100
|
|
|
|
572
|
map { $_ => 1 } map { values %$_} map { @{$_->{-parents}} } values %{ $outer_aliastypes->{$_} || {} } |
|
269
|
|
|
|
|
790
|
|
|
269
|
|
|
|
|
627
|
|
|
182
|
|
|
|
|
317
|
|
|
182
|
|
|
|
|
480
|
|
|
344
|
|
|
|
|
1486
|
|
304
|
|
|
|
|
|
|
} } qw/selecting restricting grouping ordering/; |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# see what's left - throw away if not selecting/restricting |
307
|
86
|
|
|
|
|
259
|
my $may_need_outer_group_by; |
308
|
86
|
|
|
|
|
373
|
while (my $j = shift @orig_from) { |
309
|
119
|
|
|
|
|
307
|
my $alias = $j->[0]{-alias}; |
310
|
|
|
|
|
|
|
|
311
|
119
|
100
|
|
|
|
391
|
if ( |
|
|
100
|
|
|
|
|
|
312
|
|
|
|
|
|
|
$outer_select_chain->{$alias} |
313
|
|
|
|
|
|
|
) { |
314
|
109
|
|
|
|
|
432
|
push @outer_from, $j |
315
|
|
|
|
|
|
|
} |
316
|
14
|
|
|
14
|
|
53
|
elsif (first { $_->{$alias} } @outer_nonselecting_chains ) { |
317
|
8
|
|
|
|
|
20
|
push @outer_from, $j; |
318
|
8
|
100
|
66
|
|
|
73
|
$may_need_outer_group_by ||= $outer_aliastypes->{multiplying}{$alias} ? 1 : 0; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
# also throw in a synthetic group_by if a non-selecting multiplier, |
323
|
|
|
|
|
|
|
# to guard against cross-join explosions |
324
|
|
|
|
|
|
|
# the logic is somewhat fragile, but relies on the idea that if a user supplied |
325
|
|
|
|
|
|
|
# a group by on their own - they know what they were doing |
326
|
86
|
100
|
100
|
|
|
354
|
if ( $may_need_outer_group_by and $attrs->{_grouped_by_distinct} ) { |
327
|
2
|
|
|
|
|
24
|
($outer_attrs->{group_by}, $outer_attrs->{order_by}) = $self->_group_over_selection ({ |
328
|
|
|
|
|
|
|
%$outer_attrs, |
329
|
|
|
|
|
|
|
from => \@outer_from, |
330
|
|
|
|
|
|
|
}); |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# This is totally horrific - the {where} ends up in both the inner and outer query |
334
|
|
|
|
|
|
|
# Unfortunately not much can be done until SQLA2 introspection arrives, and even |
335
|
|
|
|
|
|
|
# then if where conditions apply to the *right* side of the prefetch, you may have |
336
|
|
|
|
|
|
|
# to both filter the inner select (e.g. to apply a limit) and then have to re-filter |
337
|
|
|
|
|
|
|
# the outer select to exclude joins you didn't want in the first place |
338
|
|
|
|
|
|
|
# |
339
|
|
|
|
|
|
|
# OTOH it can be seen as a plus: <ash> (notes that this query would make a DBA cry ;) |
340
|
86
|
|
|
|
|
2934
|
return $outer_attrs; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# |
344
|
|
|
|
|
|
|
# I KNOW THIS SUCKS! GET SQLA2 OUT THE DOOR SO THIS CAN DIE! |
345
|
|
|
|
|
|
|
# |
346
|
|
|
|
|
|
|
# Due to a lack of SQLA2 we fall back to crude scans of all the |
347
|
|
|
|
|
|
|
# select/where/order/group attributes, in order to determine what |
348
|
|
|
|
|
|
|
# aliases are needed to fulfill the query. This information is used |
349
|
|
|
|
|
|
|
# throughout the code to prune unnecessary JOINs from the queries |
350
|
|
|
|
|
|
|
# in an attempt to reduce the execution time. |
351
|
|
|
|
|
|
|
# Although the method is pretty horrific, the worst thing that can |
352
|
|
|
|
|
|
|
# happen is for it to fail due to some scalar SQL, which in turn will |
353
|
|
|
|
|
|
|
# result in a vocal exception. |
354
|
|
|
|
|
|
|
sub _resolve_aliastypes_from_select_args { |
355
|
1120
|
|
|
1120
|
|
2950
|
my ( $self, $attrs ) = @_; |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
$self->throw_exception ('Unable to analyze custom {from}') |
358
|
1120
|
50
|
|
|
|
4238
|
if ref $attrs->{from} ne 'ARRAY'; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# what we will return |
361
|
1120
|
|
|
|
|
2465
|
my $aliases_by_type; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# see what aliases are there to work with |
364
|
|
|
|
|
|
|
# and record who is a multiplier and who is premultiplied |
365
|
|
|
|
|
|
|
my $alias_list; |
366
|
1120
|
|
|
|
|
2041
|
for my $node (@{$attrs->{from}}) { |
|
1120
|
|
|
|
|
3258
|
|
367
|
|
|
|
|
|
|
|
368
|
2880
|
|
|
|
|
4873
|
my $j = $node; |
369
|
2880
|
100
|
|
|
|
7121
|
$j = $j->[0] if ref $j eq 'ARRAY'; |
370
|
|
|
|
|
|
|
my $al = $j->{-alias} |
371
|
2880
|
100
|
|
|
|
7117
|
or next; |
372
|
|
|
|
|
|
|
|
373
|
2872
|
|
|
|
|
5878
|
$alias_list->{$al} = $j; |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
$aliases_by_type->{multiplying}{$al} ||= { -parents => $j->{-join_path}||[] } |
376
|
|
|
|
|
|
|
# not array == {from} head == can't be multiplying |
377
|
2872
|
100
|
50
|
|
|
15522
|
if ref($node) eq 'ARRAY' and ! $j->{-is_single}; |
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
$aliases_by_type->{premultiplied}{$al} ||= { -parents => $j->{-join_path}||[] } |
380
|
|
|
|
|
|
|
# parts of the path that are not us but are multiplying |
381
|
833
|
|
|
|
|
4234
|
if grep { $aliases_by_type->{multiplying}{$_} } |
382
|
2675
|
|
|
|
|
7874
|
grep { $_ ne $al } |
383
|
2675
|
|
|
|
|
7442
|
map { values %$_ } |
384
|
2872
|
100
|
50
|
|
|
4460
|
@{ $j->{-join_path}||[] } |
|
2872
|
100
|
50
|
|
|
10646
|
|
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# get a column to source/alias map (including unambiguous unqualified ones) |
388
|
1120
|
|
|
|
|
4667
|
my $colinfo = $self->_resolve_column_info ($attrs->{from}); |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# set up a botched SQLA |
391
|
1120
|
|
|
|
|
26741
|
my $sql_maker = $self->sql_maker; |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# these are throw away results, do not pollute the bind stack |
394
|
1120
|
|
|
|
|
4153
|
local $sql_maker->{where_bind}; |
395
|
1120
|
|
|
|
|
2615
|
local $sql_maker->{group_bind}; |
396
|
1120
|
|
|
|
|
2472
|
local $sql_maker->{having_bind}; |
397
|
1120
|
|
|
|
|
2335
|
local $sql_maker->{from_bind}; |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# we can't scan properly without any quoting (\b doesn't cut it |
400
|
|
|
|
|
|
|
# everywhere), so unless there is proper quoting set - use our |
401
|
|
|
|
|
|
|
# own weird impossible character. |
402
|
|
|
|
|
|
|
# Also in the case of no quoting, we need to explicitly disable |
403
|
|
|
|
|
|
|
# name_sep, otherwise sorry nasty legacy syntax like |
404
|
|
|
|
|
|
|
# { 'count(foo.id)' => { '>' => 3 } } will stop working >:( |
405
|
1120
|
|
|
|
|
3055
|
local $sql_maker->{quote_char} = $sql_maker->{quote_char}; |
406
|
1120
|
|
|
|
|
2944
|
local $sql_maker->{name_sep} = $sql_maker->{name_sep}; |
407
|
|
|
|
|
|
|
|
408
|
1120
|
100
|
66
|
|
|
4214
|
unless (defined $sql_maker->{quote_char} and length $sql_maker->{quote_char}) { |
409
|
995
|
|
|
|
|
2785
|
$sql_maker->{quote_char} = ["\x00", "\xFF"]; |
410
|
|
|
|
|
|
|
# if we don't unset it we screw up retarded but unfortunately working |
411
|
|
|
|
|
|
|
# 'MAX(foo.bar)' => { '>', 3 } |
412
|
995
|
|
|
|
|
2128
|
$sql_maker->{name_sep} = ''; |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
1120
|
|
|
|
|
4807
|
my ($lquote, $rquote, $sep) = map { quotemeta $_ } ($sql_maker->_quote_chars, $sql_maker->name_sep); |
|
3360
|
|
|
|
|
20745
|
|
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
# generate sql chunks |
418
|
|
|
|
|
|
|
my $to_scan = { |
419
|
|
|
|
|
|
|
restricting => [ |
420
|
|
|
|
|
|
|
($sql_maker->_recurse_where ($attrs->{where}))[0], |
421
|
|
|
|
|
|
|
$sql_maker->_parse_rs_attrs ({ having => $attrs->{having} }), |
422
|
|
|
|
|
|
|
], |
423
|
|
|
|
|
|
|
grouping => [ |
424
|
|
|
|
|
|
|
$sql_maker->_parse_rs_attrs ({ group_by => $attrs->{group_by} }), |
425
|
|
|
|
|
|
|
], |
426
|
|
|
|
|
|
|
joining => [ |
427
|
|
|
|
|
|
|
$sql_maker->_recurse_from ( |
428
|
|
|
|
|
|
|
ref $attrs->{from}[0] eq 'ARRAY' ? $attrs->{from}[0][0] : $attrs->{from}[0], |
429
|
1120
|
|
|
|
|
4303
|
@{$attrs->{from}}[1 .. $#{$attrs->{from}}], |
|
1120
|
|
|
|
|
3096
|
|
430
|
|
|
|
|
|
|
), |
431
|
|
|
|
|
|
|
], |
432
|
|
|
|
|
|
|
selecting => [ |
433
|
5306
|
|
|
|
|
111680
|
map { ($sql_maker->_recurse_fields($_))[0] } @{$attrs->{select}}, |
|
1120
|
|
|
|
|
4081
|
|
434
|
|
|
|
|
|
|
], |
435
|
|
|
|
|
|
|
ordering => [ |
436
|
1120
|
100
|
|
|
|
5980
|
map { $_->[0] } $self->_extract_order_criteria ($attrs->{order_by}, $sql_maker), |
|
877
|
|
|
|
|
3604
|
|
437
|
|
|
|
|
|
|
], |
438
|
|
|
|
|
|
|
}; |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# throw away empty chunks and all 2-value arrayrefs: the thinking is that these are |
441
|
|
|
|
|
|
|
# bind value specs left in by the sloppy renderer above. It is ok to do this |
442
|
|
|
|
|
|
|
# at this point, since we are going to end up rewriting this crap anyway |
443
|
1120
|
|
|
|
|
7428
|
for my $v (values %$to_scan) { |
444
|
5600
|
|
|
|
|
7708
|
my @nv; |
445
|
5600
|
|
|
|
|
9293
|
for (@$v) { |
446
|
|
|
|
|
|
|
next if ( |
447
|
10663
|
50
|
0
|
|
|
26729
|
! defined $_ |
|
|
|
33
|
|
|
|
|
|
|
|
66
|
|
|
|
|
448
|
|
|
|
|
|
|
or |
449
|
|
|
|
|
|
|
( |
450
|
|
|
|
|
|
|
ref $_ eq 'ARRAY' |
451
|
|
|
|
|
|
|
and |
452
|
|
|
|
|
|
|
( @$_ == 0 or @$_ == 2 ) |
453
|
|
|
|
|
|
|
) |
454
|
|
|
|
|
|
|
); |
455
|
|
|
|
|
|
|
|
456
|
10204
|
50
|
|
|
|
17222
|
if (ref $_) { |
457
|
0
|
|
|
|
|
0
|
require Data::Dumper::Concise; |
458
|
0
|
|
|
|
|
0
|
$self->throw_exception("Unexpected ref in scan-plan: " . Data::Dumper::Concise::Dumper($v) ); |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
|
461
|
10204
|
|
|
|
|
17455
|
push @nv, $_; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
5600
|
|
|
|
|
11288
|
$v = \@nv; |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
# kill all selectors which look like a proper subquery |
468
|
|
|
|
|
|
|
# this is a sucky heuristic *BUT* - if we get it wrong the query will simply |
469
|
|
|
|
|
|
|
# fail to run, so we are relatively safe |
470
|
|
|
|
|
|
|
$to_scan->{selecting} = [ grep { |
471
|
5306
|
|
|
|
|
11944
|
$_ !~ / \A \s* \( \s* SELECT \s+ .+? \s+ FROM \s+ .+? \) \s* \z /xsi |
472
|
1120
|
50
|
|
|
|
2261
|
} @{ $to_scan->{selecting} || [] } ]; |
|
1120
|
|
|
|
|
4034
|
|
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# first see if we have any exact matches (qualified or unqualified) |
475
|
1120
|
|
|
|
|
3996
|
for my $type (keys %$to_scan) { |
476
|
5600
|
|
|
|
|
7540
|
for my $piece (@{$to_scan->{$type}}) { |
|
5600
|
|
|
|
|
10043
|
|
477
|
10174
|
100
|
66
|
|
|
24413
|
if ($colinfo->{$piece} and my $alias = $colinfo->{$piece}{-source_alias}) { |
478
|
655
|
|
100
|
|
|
4332
|
$aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] }; |
|
|
|
100
|
|
|
|
|
479
|
655
|
|
|
|
|
2279
|
$aliases_by_type->{$type}{$alias}{-seen_columns}{$colinfo->{$piece}{-fq_colname}} = $piece; |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
# now loop through all fully qualified columns and get the corresponding |
485
|
|
|
|
|
|
|
# alias (should work even if they are in scalarrefs) |
486
|
1120
|
|
|
|
|
4331
|
for my $alias (keys %$alias_list) { |
487
|
2872
|
|
|
|
|
90195
|
my $al_re = qr/ |
488
|
|
|
|
|
|
|
$lquote $alias $rquote $sep (?: $lquote ([^$rquote]+) $rquote )? |
489
|
|
|
|
|
|
|
| |
490
|
|
|
|
|
|
|
\b $alias \. ([^\s\)\($rquote]+)? |
491
|
|
|
|
|
|
|
/x; |
492
|
|
|
|
|
|
|
|
493
|
2872
|
|
|
|
|
10916
|
for my $type (keys %$to_scan) { |
494
|
14360
|
|
|
|
|
21394
|
for my $piece (@{$to_scan->{$type}}) { |
|
14360
|
|
|
|
|
25931
|
|
495
|
27896
|
100
|
|
|
|
180957
|
if (my @matches = $piece =~ /$al_re/g) { |
496
|
8978
|
|
100
|
|
|
44015
|
$aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] }; |
|
|
|
100
|
|
|
|
|
497
|
|
|
|
|
|
|
$aliases_by_type->{$type}{$alias}{-seen_columns}{"$alias.$_"} = "$alias.$_" |
498
|
8978
|
|
|
|
|
15197
|
for grep { defined $_ } @matches; |
|
26990
|
|
|
|
|
75096
|
|
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
} |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
# now loop through unqualified column names, and try to locate them within |
505
|
|
|
|
|
|
|
# the chunks |
506
|
1120
|
|
|
|
|
6931
|
for my $col (keys %$colinfo) { |
507
|
21996
|
100
|
|
|
|
47389
|
next if $col =~ / \. /x; # if column is qualified it was caught by the above |
508
|
|
|
|
|
|
|
|
509
|
9394
|
|
|
|
|
83646
|
my $col_re = qr/ $lquote ($col) $rquote /x; |
510
|
|
|
|
|
|
|
|
511
|
9394
|
|
|
|
|
25837
|
for my $type (keys %$to_scan) { |
512
|
46970
|
|
|
|
|
64842
|
for my $piece (@{$to_scan->{$type}}) { |
|
46970
|
|
|
|
|
78467
|
|
513
|
87253
|
100
|
|
|
|
269710
|
if ( my @matches = $piece =~ /$col_re/g) { |
514
|
1534
|
|
|
|
|
3595
|
my $alias = $colinfo->{$col}{-source_alias}; |
515
|
1534
|
|
100
|
|
|
5135
|
$aliases_by_type->{$type}{$alias} ||= { -parents => $alias_list->{$alias}{-join_path}||[] }; |
|
|
|
100
|
|
|
|
|
516
|
|
|
|
|
|
|
$aliases_by_type->{$type}{$alias}{-seen_columns}{"$alias.$_"} = $_ |
517
|
1534
|
|
|
|
|
3092
|
for grep { defined $_ } @matches; |
|
1986
|
|
|
|
|
8778
|
|
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# Add any non-left joins to the restriction list (such joins are indeed restrictions) |
524
|
1120
|
|
|
|
|
4213
|
for my $j (values %$alias_list) { |
525
|
2872
|
50
|
|
|
|
7322
|
my $alias = $j->{-alias} or next; |
526
|
|
|
|
|
|
|
$aliases_by_type->{restricting}{$alias} ||= { -parents => $j->{-join_path}||[] } if ( |
527
|
|
|
|
|
|
|
(not $j->{-join_type}) |
528
|
|
|
|
|
|
|
or |
529
|
2872
|
100
|
100
|
|
|
18787
|
($j->{-join_type} !~ /^left (?: \s+ outer)? $/xi) |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
530
|
|
|
|
|
|
|
); |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
1120
|
|
|
|
|
4190
|
for (keys %$aliases_by_type) { |
534
|
4504
|
100
|
|
|
|
6175
|
delete $aliases_by_type->{$_} unless keys %{$aliases_by_type->{$_}}; |
|
4504
|
|
|
|
|
11506
|
|
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
1120
|
|
|
|
|
20646
|
return $aliases_by_type; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# This is the engine behind { distinct => 1 } and the general |
541
|
|
|
|
|
|
|
# complex prefetch grouper |
542
|
|
|
|
|
|
|
sub _group_over_selection { |
543
|
110
|
|
|
110
|
|
1607
|
my ($self, $attrs) = @_; |
544
|
|
|
|
|
|
|
|
545
|
110
|
|
|
|
|
473
|
my $colinfos = $self->_resolve_column_info ($attrs->{from}); |
546
|
|
|
|
|
|
|
|
547
|
110
|
|
|
|
|
307
|
my (@group_by, %group_index); |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
# the logic is: if it is a { func => val } we assume an aggregate, |
550
|
|
|
|
|
|
|
# otherwise if \'...' or \[...] we assume the user knows what is |
551
|
|
|
|
|
|
|
# going on thus group over it |
552
|
110
|
|
|
|
|
230
|
for (@{$attrs->{select}}) { |
|
110
|
|
|
|
|
389
|
|
553
|
413
|
100
|
100
|
|
|
1158
|
if (! ref($_) or ref ($_) ne 'HASH' ) { |
554
|
399
|
|
|
|
|
717
|
push @group_by, $_; |
555
|
399
|
|
|
|
|
803
|
$group_index{$_}++; |
556
|
399
|
50
|
66
|
|
|
2195
|
if ($colinfos->{$_} and $_ !~ /\./ ) { |
557
|
|
|
|
|
|
|
# add a fully qualified version as well |
558
|
0
|
|
|
|
|
0
|
$group_index{"$colinfos->{$_}{-source_alias}.$_"}++; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
my @order_by = $self->_extract_order_criteria($attrs->{order_by}) |
564
|
110
|
100
|
|
|
|
585
|
or return (\@group_by, $attrs->{order_by}); |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
# add any order_by parts that are not already present in the group_by |
567
|
|
|
|
|
|
|
# to maintain SQL cross-compatibility and general sanity |
568
|
|
|
|
|
|
|
# |
569
|
|
|
|
|
|
|
# also in case the original selection is *not* unique, or in case part |
570
|
|
|
|
|
|
|
# of the ORDER BY refers to a multiplier - we will need to replace the |
571
|
|
|
|
|
|
|
# skipped order_by elements with their MIN/MAX equivalents as to maintain |
572
|
|
|
|
|
|
|
# the proper overall order without polluting the group criteria (and |
573
|
|
|
|
|
|
|
# possibly changing the outcome entirely) |
574
|
|
|
|
|
|
|
|
575
|
66
|
|
|
|
|
242
|
my ($leftovers, $sql_maker, @new_order_by, $order_chunks, $aliastypes); |
576
|
|
|
|
|
|
|
|
577
|
66
|
|
|
|
|
461
|
my $group_already_unique = $self->_columns_comprise_identifying_set($colinfos, \@group_by); |
578
|
|
|
|
|
|
|
|
579
|
66
|
|
|
|
|
271
|
for my $o_idx (0 .. $#order_by) { |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# if the chunk is already a min/max function - there is nothing left to touch |
582
|
166
|
50
|
|
|
|
518
|
next if $order_by[$o_idx][0] =~ /^ (?: min | max ) \s* \( .+ \) $/ix; |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
# only consider real columns (for functions the user got to do an explicit group_by) |
585
|
166
|
|
|
|
|
251
|
my $chunk_ci; |
586
|
166
|
100
|
100
|
|
|
241
|
if ( |
|
|
|
66
|
|
|
|
|
587
|
166
|
|
|
|
|
758
|
@{$order_by[$o_idx]} != 1 |
588
|
|
|
|
|
|
|
or |
589
|
|
|
|
|
|
|
# only declare an unknown *plain* identifier as "leftover" if we are called with |
590
|
|
|
|
|
|
|
# aliastypes to examine. If there are none - we are still in _resolve_attrs, and |
591
|
|
|
|
|
|
|
# can just assume the user knows what they want |
592
|
|
|
|
|
|
|
( ! ( $chunk_ci = $colinfos->{$order_by[$o_idx][0]} ) and $attrs->{_aliastypes} ) |
593
|
|
|
|
|
|
|
) { |
594
|
1
|
|
|
|
|
4
|
push @$leftovers, $order_by[$o_idx][0]; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
166
|
100
|
|
|
|
400
|
next unless $chunk_ci; |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# no duplication of group criteria |
600
|
163
|
100
|
|
|
|
509
|
next if $group_index{$chunk_ci->{-fq_colname}}; |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
$aliastypes ||= ( |
603
|
|
|
|
|
|
|
$attrs->{_aliastypes} |
604
|
|
|
|
|
|
|
or |
605
|
|
|
|
|
|
|
$self->_resolve_aliastypes_from_select_args({ |
606
|
|
|
|
|
|
|
from => $attrs->{from}, |
607
|
|
|
|
|
|
|
order_by => $attrs->{order_by}, |
608
|
|
|
|
|
|
|
}) |
609
|
75
|
100
|
66
|
|
|
492
|
) if $group_already_unique; |
|
|
|
66
|
|
|
|
|
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# check that we are not ordering by a multiplier (if a check is requested at all) |
612
|
75
|
100
|
100
|
|
|
623
|
if ( |
|
|
|
100
|
|
|
|
|
613
|
|
|
|
|
|
|
$group_already_unique |
614
|
|
|
|
|
|
|
and |
615
|
|
|
|
|
|
|
! $aliastypes->{multiplying}{$chunk_ci->{-source_alias}} |
616
|
|
|
|
|
|
|
and |
617
|
|
|
|
|
|
|
! $aliastypes->{premultiplied}{$chunk_ci->{-source_alias}} |
618
|
|
|
|
|
|
|
) { |
619
|
16
|
|
|
|
|
60
|
push @group_by, $chunk_ci->{-fq_colname}; |
620
|
16
|
|
|
|
|
65
|
$group_index{$chunk_ci->{-fq_colname}}++ |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
else { |
623
|
|
|
|
|
|
|
# We need to order by external columns without adding them to the group |
624
|
|
|
|
|
|
|
# (eiehter a non-unique selection, or a multi-external) |
625
|
|
|
|
|
|
|
# |
626
|
|
|
|
|
|
|
# This doesn't really make sense in SQL, however from DBICs point |
627
|
|
|
|
|
|
|
# of view is rather valid (e.g. order the leftmost objects by whatever |
628
|
|
|
|
|
|
|
# criteria and get the offset/rows many). There is a way around |
629
|
|
|
|
|
|
|
# this however in SQL - we simply tae the direction of each piece |
630
|
|
|
|
|
|
|
# of the external order and convert them to MIN(X) for ASC or MAX(X) |
631
|
|
|
|
|
|
|
# for DESC, and group_by the root columns. The end result should be |
632
|
|
|
|
|
|
|
# exactly what we expect |
633
|
|
|
|
|
|
|
# |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
# both populated on the first loop over $o_idx |
636
|
59
|
|
66
|
|
|
1262
|
$sql_maker ||= $self->sql_maker; |
637
|
|
|
|
|
|
|
$order_chunks ||= [ |
638
|
145
|
100
|
|
|
|
4090
|
map { ref $_ eq 'ARRAY' ? $_ : [ $_ ] } $sql_maker->_order_by_chunks($attrs->{order_by}) |
639
|
59
|
|
100
|
|
|
389
|
]; |
640
|
|
|
|
|
|
|
|
641
|
59
|
|
|
|
|
502
|
my ($chunk, $is_desc) = $sql_maker->_split_order_chunk($order_chunks->[$o_idx][0]); |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
$new_order_by[$o_idx] = \[ |
644
|
|
|
|
|
|
|
sprintf( '%s( %s )%s', |
645
|
|
|
|
|
|
|
$self->_minmax_operator_for_datatype($chunk_ci->{data_type}, $is_desc), |
646
|
|
|
|
|
|
|
$chunk, |
647
|
|
|
|
|
|
|
($is_desc ? ' DESC' : ''), |
648
|
|
|
|
|
|
|
), |
649
|
59
|
100
|
|
|
|
471
|
@ {$order_chunks->[$o_idx]} [ 1 .. $#{$order_chunks->[$o_idx]} ] |
|
59
|
|
|
|
|
285
|
|
|
59
|
|
|
|
|
182
|
|
650
|
|
|
|
|
|
|
]; |
651
|
|
|
|
|
|
|
} |
652
|
|
|
|
|
|
|
} |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
$self->throw_exception ( sprintf |
655
|
|
|
|
|
|
|
'Unable to programatically derive a required group_by from the supplied ' |
656
|
|
|
|
|
|
|
. 'order_by criteria. To proceed either add an explicit group_by, or ' |
657
|
|
|
|
|
|
|
. 'simplify your order_by to only include plain columns ' |
658
|
|
|
|
|
|
|
. '(supplied order_by: %s)', |
659
|
66
|
100
|
|
|
|
249
|
join ', ', map { "'$_'" } @$leftovers, |
|
1
|
|
|
|
|
51
|
|
660
|
|
|
|
|
|
|
) if $leftovers; |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
# recreate the untouched order parts |
663
|
65
|
100
|
|
|
|
204
|
if (@new_order_by) { |
664
|
46
|
|
100
|
|
|
372
|
$new_order_by[$_] ||= \ $order_chunks->[$_] for ( 0 .. $#$order_chunks ); |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
return ( |
668
|
|
|
|
|
|
|
\@group_by, |
669
|
65
|
100
|
|
|
|
1390
|
(@new_order_by ? \@new_order_by : $attrs->{order_by} ), # same ref as original == unchanged |
670
|
|
|
|
|
|
|
); |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
sub _minmax_operator_for_datatype { |
674
|
|
|
|
|
|
|
#my ($self, $datatype, $want_max) = @_; |
675
|
|
|
|
|
|
|
|
676
|
57
|
100
|
|
57
|
|
413
|
$_[2] ? 'MAX' : 'MIN'; |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
sub _resolve_ident_sources { |
680
|
8136
|
|
|
8136
|
|
15166
|
my ($self, $ident) = @_; |
681
|
|
|
|
|
|
|
|
682
|
8136
|
|
|
|
|
14389
|
my $alias2source = {}; |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
# the reason this is so contrived is that $ident may be a {from} |
685
|
|
|
|
|
|
|
# structure, specifying multiple tables to join |
686
|
8136
|
50
|
33
|
|
|
38191
|
if ( blessed $ident && $ident->isa("DBIx::Class::ResultSource") ) { |
|
|
100
|
|
|
|
|
|
687
|
|
|
|
|
|
|
# this is compat mode for insert/update/delete which do not deal with aliases |
688
|
0
|
|
|
|
|
0
|
$alias2source->{me} = $ident; |
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
elsif (ref $ident eq 'ARRAY') { |
691
|
|
|
|
|
|
|
|
692
|
8135
|
|
|
|
|
18119
|
for (@$ident) { |
693
|
11406
|
|
|
|
|
16237
|
my $tabinfo; |
694
|
11406
|
100
|
|
|
|
25199
|
if (ref $_ eq 'HASH') { |
695
|
8269
|
|
|
|
|
12915
|
$tabinfo = $_; |
696
|
|
|
|
|
|
|
} |
697
|
11406
|
100
|
66
|
|
|
31273
|
if (ref $_ eq 'ARRAY' and ref $_->[0] eq 'HASH') { |
698
|
3137
|
|
|
|
|
5094
|
$tabinfo = $_->[0]; |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
$alias2source->{$tabinfo->{-alias}} = $tabinfo->{-rsrc} |
702
|
11406
|
100
|
|
|
|
44910
|
if ($tabinfo->{-rsrc}); |
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
|
706
|
8136
|
|
|
|
|
16019
|
return $alias2source; |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
# Takes $ident, \@column_names |
710
|
|
|
|
|
|
|
# |
711
|
|
|
|
|
|
|
# returns { $column_name => \%column_info, ... } |
712
|
|
|
|
|
|
|
# also note: this adds -result_source => $rsrc to the column info |
713
|
|
|
|
|
|
|
# |
714
|
|
|
|
|
|
|
# If no columns_names are supplied returns info about *all* columns |
715
|
|
|
|
|
|
|
# for all sources |
716
|
|
|
|
|
|
|
sub _resolve_column_info { |
717
|
8136
|
|
|
8136
|
|
19468
|
my ($self, $ident, $colnames) = @_; |
718
|
|
|
|
|
|
|
|
719
|
8136
|
50
|
66
|
|
|
20999
|
return {} if $colnames and ! @$colnames; |
720
|
|
|
|
|
|
|
|
721
|
8136
|
|
|
|
|
21990
|
my $alias2src = $self->_resolve_ident_sources($ident); |
722
|
|
|
|
|
|
|
|
723
|
8136
|
|
|
|
|
14461
|
my (%seen_cols, @auto_colnames); |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
# compile a global list of column names, to be able to properly |
726
|
|
|
|
|
|
|
# disambiguate unqualified column names (if at all possible) |
727
|
8136
|
|
|
|
|
23792
|
for my $alias (keys %$alias2src) { |
728
|
11391
|
|
|
|
|
20362
|
my $rsrc = $alias2src->{$alias}; |
729
|
11391
|
|
|
|
|
35017
|
for my $colname ($rsrc->columns) { |
730
|
57954
|
|
|
|
|
78476
|
push @{$seen_cols{$colname}}, $alias; |
|
57954
|
|
|
|
|
118912
|
|
731
|
57954
|
100
|
|
|
|
157522
|
push @auto_colnames, "$alias.$colname" unless $colnames; |
732
|
|
|
|
|
|
|
} |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
$colnames ||= [ |
736
|
|
|
|
|
|
|
@auto_colnames, |
737
|
8136
|
|
100
|
|
|
37047
|
grep { @{$seen_cols{$_}} == 1 } (keys %seen_cols), |
|
54487
|
|
|
|
|
71898
|
|
|
54487
|
|
|
|
|
128140
|
|
738
|
|
|
|
|
|
|
]; |
739
|
|
|
|
|
|
|
|
740
|
8136
|
|
|
|
|
16530
|
my (%return, $colinfos); |
741
|
8136
|
|
|
|
|
16882
|
foreach my $col (@$colnames) { |
742
|
109684
|
|
|
|
|
466680
|
my ($source_alias, $colname) = $col =~ m/^ (?: ([^\.]+) \. )? (.+) $/x; |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
# if the column was seen exactly once - we know which rsrc it came from |
745
|
|
|
|
|
|
|
$source_alias ||= $seen_cols{$colname}[0] |
746
|
109684
|
100
|
66
|
|
|
266955
|
if ($seen_cols{$colname} and @{$seen_cols{$colname}} == 1); |
|
109666
|
|
100
|
|
|
428477
|
|
747
|
|
|
|
|
|
|
|
748
|
109684
|
100
|
|
|
|
196526
|
next unless $source_alias; |
749
|
|
|
|
|
|
|
|
750
|
109666
|
50
|
|
|
|
208573
|
my $rsrc = $alias2src->{$source_alias} |
751
|
|
|
|
|
|
|
or next; |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
$return{$col} = { |
754
|
|
|
|
|
|
|
%{ |
755
|
109666
|
50
|
66
|
|
|
143053
|
( $colinfos->{$source_alias} ||= $rsrc->columns_info )->{$colname} |
|
109666
|
100
|
|
|
|
643462
|
|
756
|
|
|
|
|
|
|
|| |
757
|
|
|
|
|
|
|
$self->throw_exception( |
758
|
|
|
|
|
|
|
"No such column '$colname' on source " . $rsrc->source_name |
759
|
|
|
|
|
|
|
); |
760
|
|
|
|
|
|
|
}, |
761
|
|
|
|
|
|
|
-result_source => $rsrc, |
762
|
|
|
|
|
|
|
-source_alias => $source_alias, |
763
|
|
|
|
|
|
|
-fq_colname => $col eq $colname ? "$source_alias.$col" : $col, |
764
|
|
|
|
|
|
|
-colname => $colname, |
765
|
|
|
|
|
|
|
}; |
766
|
|
|
|
|
|
|
|
767
|
109666
|
100
|
|
|
|
365873
|
$return{"$source_alias.$colname"} = $return{$col} if $col eq $colname; |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
|
770
|
8136
|
|
|
|
|
97827
|
return \%return; |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
# The DBIC relationship chaining implementation is pretty simple - every |
774
|
|
|
|
|
|
|
# new related_relationship is pushed onto the {from} stack, and the {select} |
775
|
|
|
|
|
|
|
# window simply slides further in. This means that when we count somewhere |
776
|
|
|
|
|
|
|
# in the middle, we got to make sure that everything in the join chain is an |
777
|
|
|
|
|
|
|
# actual inner join, otherwise the count will come back with unpredictable |
778
|
|
|
|
|
|
|
# results (a resultset may be generated with _some_ rows regardless of if |
779
|
|
|
|
|
|
|
# the relation which the $rs currently selects has rows or not). E.g. |
780
|
|
|
|
|
|
|
# $artist_rs->cds->count - normally generates: |
781
|
|
|
|
|
|
|
# SELECT COUNT( * ) FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid |
782
|
|
|
|
|
|
|
# which actually returns the number of artists * (number of cds || 1) |
783
|
|
|
|
|
|
|
# |
784
|
|
|
|
|
|
|
# So what we do here is crawl {from}, determine if the current alias is at |
785
|
|
|
|
|
|
|
# the top of the stack, and if not - make sure the chain is inner-joined down |
786
|
|
|
|
|
|
|
# to the root. |
787
|
|
|
|
|
|
|
# |
788
|
|
|
|
|
|
|
sub _inner_join_to_node { |
789
|
216
|
|
|
216
|
|
3656
|
my ($self, $from, $alias) = @_; |
790
|
|
|
|
|
|
|
|
791
|
216
|
|
|
|
|
1036
|
my $switch_branch = $self->_find_join_path_to_node($from, $alias); |
792
|
|
|
|
|
|
|
|
793
|
216
|
50
|
|
|
|
435
|
return $from unless @{$switch_branch||[]}; |
|
216
|
50
|
|
|
|
809
|
|
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
# So it looks like we will have to switch some stuff around. |
796
|
|
|
|
|
|
|
# local() is useless here as we will be leaving the scope |
797
|
|
|
|
|
|
|
# anyway, and deep cloning is just too fucking expensive |
798
|
|
|
|
|
|
|
# So replace the first hashref in the node arrayref manually |
799
|
216
|
|
|
|
|
645
|
my @new_from = ($from->[0]); |
800
|
216
|
|
|
|
|
573
|
my $sw_idx = { map { (values %$_), 1 } @$switch_branch }; #there's one k/v per join-path |
|
247
|
|
|
|
|
1264
|
|
801
|
|
|
|
|
|
|
|
802
|
216
|
|
|
|
|
721
|
for my $j (@{$from}[1 .. $#$from]) { |
|
216
|
|
|
|
|
723
|
|
803
|
277
|
|
|
|
|
591
|
my $jalias = $j->[0]{-alias}; |
804
|
|
|
|
|
|
|
|
805
|
277
|
100
|
|
|
|
722
|
if ($sw_idx->{$jalias}) { |
806
|
247
|
|
|
|
|
441
|
my %attrs = %{$j->[0]}; |
|
247
|
|
|
|
|
1469
|
|
807
|
247
|
|
|
|
|
669
|
delete $attrs{-join_type}; |
808
|
|
|
|
|
|
|
push @new_from, [ |
809
|
|
|
|
|
|
|
\%attrs, |
810
|
247
|
|
|
|
|
746
|
@{$j}[ 1 .. $#$j ], |
|
247
|
|
|
|
|
888
|
|
811
|
|
|
|
|
|
|
]; |
812
|
|
|
|
|
|
|
} |
813
|
|
|
|
|
|
|
else { |
814
|
30
|
|
|
|
|
67
|
push @new_from, $j; |
815
|
|
|
|
|
|
|
} |
816
|
|
|
|
|
|
|
} |
817
|
|
|
|
|
|
|
|
818
|
216
|
|
|
|
|
1257
|
return \@new_from; |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
sub _find_join_path_to_node { |
822
|
259
|
|
|
259
|
|
700
|
my ($self, $from, $target_alias) = @_; |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
# subqueries and other oddness are naturally not supported |
825
|
|
|
|
|
|
|
return undef if ( |
826
|
|
|
|
|
|
|
ref $from ne 'ARRAY' |
827
|
|
|
|
|
|
|
|| |
828
|
|
|
|
|
|
|
ref $from->[0] ne 'HASH' |
829
|
|
|
|
|
|
|
|| |
830
|
|
|
|
|
|
|
! defined $from->[0]{-alias} |
831
|
259
|
50
|
33
|
|
|
2680
|
); |
|
|
|
33
|
|
|
|
|
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
# no path - the head is the alias |
834
|
259
|
100
|
|
|
|
953
|
return [] if $from->[0]{-alias} eq $target_alias; |
835
|
|
|
|
|
|
|
|
836
|
218
|
|
|
|
|
801
|
for my $i (1 .. $#$from) { |
837
|
270
|
100
|
50
|
|
|
1411
|
return $from->[$i][0]{-join_path} if ( ($from->[$i][0]{-alias}||'') eq $target_alias ); |
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
# something else went quite wrong |
841
|
0
|
|
|
|
|
0
|
return undef; |
842
|
|
|
|
|
|
|
} |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
sub _extract_order_criteria { |
845
|
2039
|
|
|
2039
|
|
40963
|
my ($self, $order_by, $sql_maker) = @_; |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
my $parser = sub { |
848
|
2039
|
|
|
2039
|
|
4868
|
my ($sql_maker, $order_by, $orig_quote_chars) = @_; |
849
|
|
|
|
|
|
|
|
850
|
2039
|
100
|
|
|
|
5338
|
return scalar $sql_maker->_order_by_chunks ($order_by) |
851
|
|
|
|
|
|
|
unless wantarray; |
852
|
|
|
|
|
|
|
|
853
|
2017
|
100
|
|
|
|
8357
|
my ($lq, $rq, $sep) = map { quotemeta($_) } ( |
|
6051
|
|
|
|
|
41507
|
|
854
|
|
|
|
|
|
|
($orig_quote_chars ? @$orig_quote_chars : $sql_maker->_quote_chars), |
855
|
|
|
|
|
|
|
$sql_maker->name_sep |
856
|
|
|
|
|
|
|
); |
857
|
|
|
|
|
|
|
|
858
|
2017
|
|
|
|
|
4541
|
my @chunks; |
859
|
2017
|
|
|
|
|
8309
|
for ($sql_maker->_order_by_chunks ($order_by) ) { |
860
|
1238
|
100
|
|
|
|
44708
|
my $chunk = ref $_ ? [ @$_ ] : [ $_ ]; |
861
|
1238
|
|
|
|
|
4119
|
($chunk->[0]) = $sql_maker->_split_order_chunk($chunk->[0]); |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
# order criteria may have come back pre-quoted (literals and whatnot) |
864
|
|
|
|
|
|
|
# this is fragile, but the best we can currently do |
865
|
1238
|
100
|
|
|
|
15940
|
$chunk->[0] =~ s/^ $lq (.+?) $rq $sep $lq (.+?) $rq $/"$1.$2"/xe |
|
395
|
|
|
|
|
1813
|
|
866
|
|
|
|
|
|
|
or $chunk->[0] =~ s/^ $lq (.+) $rq $/$1/x; |
867
|
|
|
|
|
|
|
|
868
|
1238
|
|
|
|
|
3573
|
push @chunks, $chunk; |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
2017
|
|
|
|
|
102931
|
return @chunks; |
872
|
2039
|
|
|
|
|
12314
|
}; |
873
|
|
|
|
|
|
|
|
874
|
2039
|
100
|
|
|
|
5948
|
if ($sql_maker) { |
875
|
1120
|
|
|
|
|
3057
|
return $parser->($sql_maker, $order_by); |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
else { |
878
|
919
|
|
|
|
|
18453
|
$sql_maker = $self->sql_maker; |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
# pass these in to deal with literals coming from |
881
|
|
|
|
|
|
|
# the user or the deep guts of prefetch |
882
|
919
|
|
|
|
|
4671
|
my $orig_quote_chars = [$sql_maker->_quote_chars]; |
883
|
|
|
|
|
|
|
|
884
|
919
|
|
|
|
|
2659
|
local $sql_maker->{quote_char}; |
885
|
919
|
|
|
|
|
2720
|
return $parser->($sql_maker, $order_by, $orig_quote_chars); |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
sub _order_by_is_stable { |
890
|
30
|
|
|
30
|
|
555
|
my ($self, $ident, $order_by, $where) = @_; |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
my @cols = ( |
893
|
50
|
|
|
|
|
210
|
( map { $_->[0] } $self->_extract_order_criteria($order_by) ), |
894
|
30
|
100
|
|
|
|
111
|
( $where ? keys %{ $self->_extract_fixed_condition_columns($where) } : () ), |
|
27
|
50
|
|
|
|
151
|
|
895
|
|
|
|
|
|
|
) or return 0; |
896
|
|
|
|
|
|
|
|
897
|
30
|
|
|
|
|
173
|
my $colinfo = $self->_resolve_column_info($ident, \@cols); |
898
|
|
|
|
|
|
|
|
899
|
30
|
50
|
|
|
|
259
|
return keys %$colinfo |
900
|
|
|
|
|
|
|
? $self->_columns_comprise_identifying_set( $colinfo, \@cols ) |
901
|
|
|
|
|
|
|
: 0 |
902
|
|
|
|
|
|
|
; |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
sub _columns_comprise_identifying_set { |
906
|
96
|
|
|
96
|
|
292
|
my ($self, $colinfo, $columns) = @_; |
907
|
|
|
|
|
|
|
|
908
|
96
|
|
|
|
|
182
|
my $cols_per_src; |
909
|
|
|
|
|
|
|
$cols_per_src -> {$_->{-source_alias}} -> {$_->{-colname}} = $_ |
910
|
96
|
|
|
|
|
213
|
for grep { defined $_ } @{$colinfo}{@$columns}; |
|
316
|
|
|
|
|
1186
|
|
|
96
|
|
|
|
|
371
|
|
911
|
|
|
|
|
|
|
|
912
|
96
|
|
|
|
|
415
|
for (values %$cols_per_src) { |
913
|
87
|
|
|
|
|
401
|
my $src = (values %$_)[0]->{-result_source}; |
914
|
87
|
100
|
|
|
|
584
|
return 1 if $src->_identifying_column_set($_); |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
|
917
|
19
|
|
|
|
|
69
|
return 0; |
918
|
|
|
|
|
|
|
} |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
# this is almost similar to _order_by_is_stable, except it takes |
921
|
|
|
|
|
|
|
# a single rsrc, and will succeed only if the first portion of the order |
922
|
|
|
|
|
|
|
# by is stable. |
923
|
|
|
|
|
|
|
# returns that portion as a colinfo hashref on success |
924
|
|
|
|
|
|
|
sub _extract_colinfo_of_stable_main_source_order_by_portion { |
925
|
43
|
|
|
43
|
|
894
|
my ($self, $attrs) = @_; |
926
|
|
|
|
|
|
|
|
927
|
43
|
|
|
|
|
254
|
my $nodes = $self->_find_join_path_to_node($attrs->{from}, $attrs->{alias}); |
928
|
|
|
|
|
|
|
|
929
|
43
|
50
|
|
|
|
142
|
return unless defined $nodes; |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
my @ord_cols = map |
932
|
124
|
|
|
|
|
323
|
{ $_->[0] } |
933
|
43
|
|
|
|
|
171
|
( $self->_extract_order_criteria($attrs->{order_by}) ) |
934
|
|
|
|
|
|
|
; |
935
|
43
|
50
|
|
|
|
180
|
return unless @ord_cols; |
936
|
|
|
|
|
|
|
|
937
|
45
|
|
|
|
|
167
|
my $valid_aliases = { map { $_ => 1 } ( |
938
|
|
|
|
|
|
|
$attrs->{from}[0]{-alias}, |
939
|
43
|
|
|
|
|
162
|
map { values %$_ } @$nodes, |
|
2
|
|
|
|
|
10
|
|
940
|
|
|
|
|
|
|
) }; |
941
|
|
|
|
|
|
|
|
942
|
43
|
|
|
|
|
206
|
my $colinfos = $self->_resolve_column_info($attrs->{from}); |
943
|
|
|
|
|
|
|
|
944
|
43
|
|
|
|
|
123
|
my ($colinfos_to_return, $seen_main_src_cols); |
945
|
|
|
|
|
|
|
|
946
|
43
|
|
|
|
|
115
|
for my $col (@ord_cols) { |
947
|
|
|
|
|
|
|
# if order criteria is unresolvable - there is nothing we can do |
948
|
111
|
100
|
|
|
|
284
|
my $colinfo = $colinfos->{$col} or last; |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
# if we reached the end of the allowed aliases - also nothing we can do |
951
|
94
|
100
|
|
|
|
228
|
last unless $valid_aliases->{$colinfo->{-source_alias}}; |
952
|
|
|
|
|
|
|
|
953
|
86
|
|
|
|
|
158
|
$colinfos_to_return->{$col} = $colinfo; |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
$seen_main_src_cols->{$colinfo->{-colname}} = 1 |
956
|
86
|
100
|
|
|
|
257
|
if $colinfo->{-source_alias} eq $attrs->{alias}; |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
# FIXME the condition may be singling out things on its own, so we |
960
|
|
|
|
|
|
|
# conceivable could come back wi "stable-ordered by nothing" |
961
|
|
|
|
|
|
|
# not confient enough in the parser yet, so punt for the time being |
962
|
43
|
100
|
|
|
|
293
|
return unless $seen_main_src_cols; |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
my $main_src_fixed_cols_from_cond = [ $attrs->{where} |
965
|
|
|
|
|
|
|
? ( |
966
|
|
|
|
|
|
|
map |
967
|
|
|
|
|
|
|
{ |
968
|
|
|
|
|
|
|
( $colinfos->{$_} and $colinfos->{$_}{-source_alias} eq $attrs->{alias} ) |
969
|
|
|
|
|
|
|
? $colinfos->{$_}{-colname} |
970
|
|
|
|
|
|
|
: () |
971
|
26
|
100
|
66
|
|
|
152
|
} |
972
|
35
|
100
|
|
|
|
108
|
keys %{ $self->_extract_fixed_condition_columns($attrs->{where}) } |
|
19
|
|
|
|
|
102
|
|
973
|
|
|
|
|
|
|
) |
974
|
|
|
|
|
|
|
: () |
975
|
|
|
|
|
|
|
]; |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
return $attrs->{result_source}->_identifying_column_set([ |
978
|
35
|
50
|
|
|
|
288
|
keys %$seen_main_src_cols, |
979
|
|
|
|
|
|
|
@$main_src_fixed_cols_from_cond, |
980
|
|
|
|
|
|
|
]) ? $colinfos_to_return : (); |
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
# Attempts to flatten a passed in SQLA condition as much as possible towards |
984
|
|
|
|
|
|
|
# a plain hashref, *without* altering its semantics. Required by |
985
|
|
|
|
|
|
|
# create/populate being able to extract definitive conditions from preexisting |
986
|
|
|
|
|
|
|
# resultset {where} stacks |
987
|
|
|
|
|
|
|
# |
988
|
|
|
|
|
|
|
# FIXME - while relatively robust, this is still imperfect, one of the first |
989
|
|
|
|
|
|
|
# things to tackle with DQ |
990
|
|
|
|
|
|
|
sub _collapse_cond { |
991
|
30134
|
|
|
30134
|
|
2011903
|
my ($self, $where, $where_is_anded_array) = @_; |
992
|
|
|
|
|
|
|
|
993
|
30134
|
|
|
|
|
42434
|
my $fin; |
994
|
|
|
|
|
|
|
|
995
|
30134
|
100
|
100
|
|
|
122509
|
if (! $where) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
996
|
142
|
|
|
|
|
247
|
return; |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
elsif ($where_is_anded_array or ref $where eq 'HASH') { |
999
|
|
|
|
|
|
|
|
1000
|
26226
|
|
|
|
|
40442
|
my @pairs; |
1001
|
|
|
|
|
|
|
|
1002
|
26226
|
100
|
|
|
|
56961
|
my @pieces = $where_is_anded_array ? @$where : $where; |
1003
|
26226
|
|
|
|
|
55351
|
while (@pieces) { |
1004
|
30718
|
|
|
|
|
50869
|
my $chunk = shift @pieces; |
1005
|
|
|
|
|
|
|
|
1006
|
30718
|
100
|
|
|
|
62829
|
if (ref $chunk eq 'HASH') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1007
|
29593
|
|
|
|
|
91735
|
for (sort keys %$chunk) { |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
# Match SQLA 1.79 behavior |
1010
|
33779
|
100
|
|
|
|
67748
|
if ($_ eq '') { |
1011
|
128
|
100
|
|
|
|
443
|
is_literal_value($chunk->{$_}) |
1012
|
|
|
|
|
|
|
? carp 'Hash-pairs consisting of an empty string with a literal are deprecated, use -and => [ $literal ] instead' |
1013
|
|
|
|
|
|
|
: $self->throw_exception("Supplying an empty left hand side argument is not supported in hash-pairs") |
1014
|
|
|
|
|
|
|
; |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
|
1017
|
33711
|
|
|
|
|
108335
|
push @pairs, $_ => $chunk->{$_}; |
1018
|
|
|
|
|
|
|
} |
1019
|
|
|
|
|
|
|
} |
1020
|
|
|
|
|
|
|
elsif (ref $chunk eq 'ARRAY') { |
1021
|
502
|
100
|
|
|
|
1974
|
push @pairs, -or => $chunk |
1022
|
|
|
|
|
|
|
if @$chunk; |
1023
|
|
|
|
|
|
|
} |
1024
|
|
|
|
|
|
|
elsif ( ! length ref $chunk) { |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
# Match SQLA 1.79 behavior |
1027
|
442
|
100
|
100
|
|
|
2214
|
$self->throw_exception("Supplying an empty left hand side argument is not supported in array-pairs") |
|
|
|
66
|
|
|
|
|
1028
|
|
|
|
|
|
|
if $where_is_anded_array and (! defined $chunk or $chunk eq ''); |
1029
|
|
|
|
|
|
|
|
1030
|
432
|
|
|
|
|
1084
|
push @pairs, $chunk, shift @pieces; |
1031
|
|
|
|
|
|
|
} |
1032
|
|
|
|
|
|
|
else { |
1033
|
181
|
|
|
|
|
489
|
push @pairs, '', $chunk; |
1034
|
|
|
|
|
|
|
} |
1035
|
|
|
|
|
|
|
} |
1036
|
|
|
|
|
|
|
|
1037
|
26148
|
100
|
|
|
|
56277
|
return unless @pairs; |
1038
|
|
|
|
|
|
|
|
1039
|
25841
|
100
|
|
|
|
62593
|
my @conds = $self->_collapse_cond_unroll_pairs(\@pairs) |
1040
|
|
|
|
|
|
|
or return; |
1041
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
# Consolidate various @conds back into something more compact |
1043
|
25291
|
|
|
|
|
49125
|
for my $c (@conds) { |
1044
|
34264
|
50
|
|
|
|
68570
|
if (ref $c ne 'HASH') { |
1045
|
0
|
|
|
|
|
0
|
push @{$fin->{-and}}, $c; |
|
0
|
|
|
|
|
0
|
|
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
else { |
1048
|
34264
|
|
|
|
|
88619
|
for my $col (sort keys %$c) { |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
# consolidate all -and nodes |
1051
|
40162
|
100
|
|
|
|
131574
|
if ($col =~ /^\-and$/i) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1052
|
618
|
|
|
|
|
2054
|
push @{$fin->{-and}}, |
1053
|
618
|
|
|
|
|
1758
|
ref $c->{$col} eq 'ARRAY' ? @{$c->{$col}} |
1054
|
0
|
|
|
|
|
0
|
: ref $c->{$col} eq 'HASH' ? %{$c->{$col}} |
1055
|
618
|
0
|
|
|
|
899
|
: { $col => $c->{$col} } |
|
|
50
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
; |
1057
|
|
|
|
|
|
|
} |
1058
|
|
|
|
|
|
|
elsif ($col =~ /^\-/) { |
1059
|
1651
|
|
|
|
|
2264
|
push @{$fin->{-and}}, { $col => $c->{$col} }; |
|
1651
|
|
|
|
|
6761
|
|
1060
|
|
|
|
|
|
|
} |
1061
|
|
|
|
|
|
|
elsif (exists $fin->{$col}) { |
1062
|
|
|
|
|
|
|
$fin->{$col} = [ -and => map { |
1063
|
|
|
|
|
|
|
(ref $_ eq 'ARRAY' and ($_->[0]||'') =~ /^\-and$/i ) |
1064
|
3196
|
100
|
100
|
|
|
15732
|
? @{$_}[1..$#$_] |
|
80
|
|
|
|
|
231
|
|
1065
|
|
|
|
|
|
|
: $_ |
1066
|
|
|
|
|
|
|
; |
1067
|
1598
|
|
|
|
|
4248
|
} ($fin->{$col}, $c->{$col}) ]; |
1068
|
|
|
|
|
|
|
} |
1069
|
|
|
|
|
|
|
else { |
1070
|
36295
|
|
|
|
|
113532
|
$fin->{$col} = $c->{$col}; |
1071
|
|
|
|
|
|
|
} |
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
} |
1076
|
|
|
|
|
|
|
elsif (ref $where eq 'ARRAY') { |
1077
|
|
|
|
|
|
|
# we are always at top-level here, it is safe to dump empty *standalone* pieces |
1078
|
3407
|
|
|
|
|
5000
|
my $fin_idx; |
1079
|
|
|
|
|
|
|
|
1080
|
3407
|
|
|
|
|
8843
|
for (my $i = 0; $i <= $#$where; $i++ ) { |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
# Match SQLA 1.79 behavior |
1083
|
5676
|
100
|
100
|
|
|
40767
|
$self->throw_exception( |
1084
|
|
|
|
|
|
|
"Supplying an empty left hand side argument is not supported in array-pairs" |
1085
|
|
|
|
|
|
|
) if (! defined $where->[$i] or ! length $where->[$i]); |
1086
|
|
|
|
|
|
|
|
1087
|
5520
|
|
100
|
|
|
26823
|
my $logic_mod = lc ( ($where->[$i] =~ /^(\-(?:and|or))$/i)[0] || '' ); |
1088
|
|
|
|
|
|
|
|
1089
|
5520
|
100
|
|
|
|
14175
|
if ($logic_mod) { |
|
|
100
|
|
|
|
|
|
1090
|
1153
|
|
|
|
|
1832
|
$i++; |
1091
|
1153
|
50
|
66
|
|
|
4145
|
$self->throw_exception("Unsupported top-level op/arg pair: [ $logic_mod => $where->[$i] ]") |
1092
|
|
|
|
|
|
|
unless ref $where->[$i] eq 'HASH' or ref $where->[$i] eq 'ARRAY'; |
1093
|
|
|
|
|
|
|
|
1094
|
1153
|
100
|
|
|
|
3961
|
my $sub_elt = $self->_collapse_cond({ $logic_mod => $where->[$i] }) |
1095
|
|
|
|
|
|
|
or next; |
1096
|
|
|
|
|
|
|
|
1097
|
918
|
|
|
|
|
2652
|
my @keys = keys %$sub_elt; |
1098
|
918
|
100
|
100
|
|
|
3867
|
if ( @keys == 1 and $keys[0] !~ /^\-/ ) { |
1099
|
369
|
|
|
|
|
1485
|
$fin_idx->{ "COL_$keys[0]_" . serialize $sub_elt } = $sub_elt; |
1100
|
|
|
|
|
|
|
} |
1101
|
|
|
|
|
|
|
else { |
1102
|
549
|
|
|
|
|
1938
|
$fin_idx->{ "SER_" . serialize $sub_elt } = $sub_elt; |
1103
|
|
|
|
|
|
|
} |
1104
|
|
|
|
|
|
|
} |
1105
|
|
|
|
|
|
|
elsif (! length ref $where->[$i] ) { |
1106
|
3000
|
100
|
|
|
|
4848
|
my $sub_elt = $self->_collapse_cond({ @{$where}[$i, $i+1] }) |
|
3000
|
|
|
|
|
10316
|
|
1107
|
|
|
|
|
|
|
or next; |
1108
|
|
|
|
|
|
|
|
1109
|
2999
|
|
|
|
|
12869
|
$fin_idx->{ "COL_$where->[$i]_" . serialize $sub_elt } = $sub_elt; |
1110
|
2999
|
|
|
|
|
118730
|
$i++; |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
else { |
1113
|
1367
|
|
100
|
|
|
3796
|
$fin_idx->{ "SER_" . serialize $where->[$i] } = $self->_collapse_cond( $where->[$i] ) || next; |
1114
|
|
|
|
|
|
|
} |
1115
|
|
|
|
|
|
|
} |
1116
|
|
|
|
|
|
|
|
1117
|
3073
|
100
|
|
|
|
62692
|
if (! $fin_idx) { |
|
|
100
|
|
|
|
|
|
1118
|
553
|
|
|
|
|
1589
|
return; |
1119
|
|
|
|
|
|
|
} |
1120
|
|
|
|
|
|
|
elsif ( keys %$fin_idx == 1 ) { |
1121
|
1365
|
|
|
|
|
4560
|
$fin = (values %$fin_idx)[0]; |
1122
|
|
|
|
|
|
|
} |
1123
|
|
|
|
|
|
|
else { |
1124
|
1155
|
|
|
|
|
1708
|
my @or; |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
# at this point everything is at most one level deep - unroll if needed |
1127
|
1155
|
|
|
|
|
4082
|
for (sort keys %$fin_idx) { |
1128
|
3260
|
100
|
66
|
|
|
7465
|
if ( ref $fin_idx->{$_} eq 'HASH' and keys %{$fin_idx->{$_}} == 1 ) { |
|
3260
|
|
|
|
|
8472
|
|
1129
|
3244
|
|
|
|
|
4306
|
my ($l, $r) = %{$fin_idx->{$_}}; |
|
3244
|
|
|
|
|
7269
|
|
1130
|
|
|
|
|
|
|
|
1131
|
3244
|
100
|
66
|
|
|
12783
|
if ( |
|
|
50
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
1132
|
|
|
|
|
|
|
ref $r eq 'ARRAY' |
1133
|
|
|
|
|
|
|
and |
1134
|
|
|
|
|
|
|
( |
1135
|
|
|
|
|
|
|
( @$r == 1 and $l =~ /^\-and$/i ) |
1136
|
|
|
|
|
|
|
or |
1137
|
|
|
|
|
|
|
$l =~ /^\-or$/i |
1138
|
|
|
|
|
|
|
) |
1139
|
|
|
|
|
|
|
) { |
1140
|
122
|
|
|
|
|
292
|
push @or, @$r |
1141
|
|
|
|
|
|
|
} |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
elsif ( |
1144
|
|
|
|
|
|
|
ref $r eq 'HASH' |
1145
|
|
|
|
|
|
|
and |
1146
|
|
|
|
|
|
|
keys %$r == 1 |
1147
|
|
|
|
|
|
|
and |
1148
|
|
|
|
|
|
|
$l =~ /^\-(?:and|or)$/i |
1149
|
|
|
|
|
|
|
) { |
1150
|
0
|
|
|
|
|
0
|
push @or, %$r; |
1151
|
|
|
|
|
|
|
} |
1152
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
else { |
1154
|
3122
|
|
|
|
|
7630
|
push @or, $l, $r; |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
else { |
1158
|
16
|
|
|
|
|
52
|
push @or, $fin_idx->{$_}; |
1159
|
|
|
|
|
|
|
} |
1160
|
|
|
|
|
|
|
} |
1161
|
|
|
|
|
|
|
|
1162
|
1155
|
|
|
|
|
5296
|
$fin->{-or} = \@or; |
1163
|
|
|
|
|
|
|
} |
1164
|
|
|
|
|
|
|
} |
1165
|
|
|
|
|
|
|
else { |
1166
|
|
|
|
|
|
|
# not a hash not an array |
1167
|
359
|
|
|
|
|
1005
|
$fin = { -and => [ $where ] }; |
1168
|
|
|
|
|
|
|
} |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# unroll single-element -and's |
1171
|
28170
|
|
100
|
|
|
73944
|
while ( |
1172
|
|
|
|
|
|
|
$fin->{-and} |
1173
|
|
|
|
|
|
|
and |
1174
|
2503
|
|
|
|
|
6820
|
@{$fin->{-and}} < 2 |
1175
|
|
|
|
|
|
|
) { |
1176
|
2137
|
|
|
|
|
4196
|
my $and = delete $fin->{-and}; |
1177
|
2137
|
50
|
|
|
|
4416
|
last if @$and == 0; |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
# at this point we have @$and == 1 |
1180
|
2137
|
100
|
66
|
|
|
5381
|
if ( |
1181
|
|
|
|
|
|
|
ref $and->[0] eq 'HASH' |
1182
|
|
|
|
|
|
|
and |
1183
|
1379
|
|
|
|
|
4525
|
! grep { exists $fin->{$_} } keys %{$and->[0]} |
|
1379
|
|
|
|
|
3586
|
|
1184
|
|
|
|
|
|
|
) { |
1185
|
|
|
|
|
|
|
$fin = { |
1186
|
1379
|
|
|
|
|
2582
|
%$fin, %{$and->[0]} |
|
1379
|
|
|
|
|
5574
|
|
1187
|
|
|
|
|
|
|
}; |
1188
|
|
|
|
|
|
|
} |
1189
|
|
|
|
|
|
|
else { |
1190
|
758
|
|
|
|
|
1174
|
$fin->{-and} = $and; |
1191
|
758
|
|
|
|
|
1130
|
last; |
1192
|
|
|
|
|
|
|
} |
1193
|
|
|
|
|
|
|
} |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
# compress same-column conds found in $fin |
1196
|
28170
|
|
|
|
|
64256
|
for my $col ( grep { $_ !~ /^\-/ } keys %$fin ) { |
|
41601
|
|
|
|
|
113151
|
|
1197
|
37621
|
100
|
100
|
|
|
109726
|
next unless ref $fin->{$col} eq 'ARRAY' and ($fin->{$col}[0]||'') =~ /^\-and$/i; |
|
|
|
100
|
|
|
|
|
1198
|
|
|
|
|
|
|
my $val_bag = { map { |
1199
|
5430
|
100
|
66
|
|
|
104284
|
(! defined $_ ) ? ( UNDEF => undef ) |
|
|
100
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
: ( ! length ref $_ or is_plain_value $_ ) ? ( "VAL_$_" => $_ ) |
1201
|
|
|
|
|
|
|
: ( ( 'SER_' . serialize $_ ) => $_ ) |
1202
|
2652
|
|
|
|
|
5092
|
} @{$fin->{$col}}[1 .. $#{$fin->{$col}}] }; |
|
2652
|
|
|
|
|
5713
|
|
|
2652
|
|
|
|
|
5947
|
|
1203
|
|
|
|
|
|
|
|
1204
|
2652
|
100
|
|
|
|
63871
|
if (keys %$val_bag == 1 ) { |
1205
|
358
|
|
|
|
|
1419
|
($fin->{$col}) = values %$val_bag; |
1206
|
|
|
|
|
|
|
} |
1207
|
|
|
|
|
|
|
else { |
1208
|
2294
|
|
|
|
|
7471
|
$fin->{$col} = [ -and => map { $val_bag->{$_} } sort keys %$val_bag ]; |
|
4670
|
|
|
|
|
15086
|
|
1209
|
|
|
|
|
|
|
} |
1210
|
|
|
|
|
|
|
} |
1211
|
|
|
|
|
|
|
|
1212
|
28170
|
50
|
|
|
|
100134
|
return keys %$fin ? $fin : (); |
1213
|
|
|
|
|
|
|
} |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
sub _collapse_cond_unroll_pairs { |
1216
|
26605
|
|
|
26605
|
|
47038
|
my ($self, $pairs) = @_; |
1217
|
|
|
|
|
|
|
|
1218
|
26605
|
|
|
|
|
36958
|
my @conds; |
1219
|
|
|
|
|
|
|
|
1220
|
26605
|
|
|
|
|
52002
|
while (@$pairs) { |
1221
|
35802
|
|
|
|
|
75562
|
my ($lhs, $rhs) = splice @$pairs, 0, 2; |
1222
|
|
|
|
|
|
|
|
1223
|
35802
|
100
|
|
|
|
139142
|
if ($lhs eq '') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1224
|
241
|
|
|
|
|
537
|
push @conds, $self->_collapse_cond($rhs); |
1225
|
|
|
|
|
|
|
} |
1226
|
|
|
|
|
|
|
elsif ( $lhs =~ /^\-and$/i ) { |
1227
|
5300
|
|
|
|
|
17693
|
push @conds, $self->_collapse_cond($rhs, (ref $rhs eq 'ARRAY')); |
1228
|
|
|
|
|
|
|
} |
1229
|
|
|
|
|
|
|
elsif ( $lhs =~ /^\-or$/i ) { |
1230
|
|
|
|
|
|
|
push @conds, $self->_collapse_cond( |
1231
|
2280
|
100
|
|
|
|
9130
|
(ref $rhs eq 'HASH') ? [ map { $_ => $rhs->{$_} } sort keys %$rhs ] : $rhs |
|
595
|
|
|
|
|
1758
|
|
1232
|
|
|
|
|
|
|
); |
1233
|
|
|
|
|
|
|
} |
1234
|
|
|
|
|
|
|
else { |
1235
|
27981
|
100
|
100
|
|
|
266119
|
if (ref $rhs eq 'HASH' and ! keys %$rhs) { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
1236
|
|
|
|
|
|
|
# FIXME - SQLA seems to be doing... nothing...? |
1237
|
|
|
|
|
|
|
} |
1238
|
|
|
|
|
|
|
# normalize top level -ident, for saner extract_fixed_condition_columns code |
1239
|
|
|
|
|
|
|
elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-ident}) { |
1240
|
322
|
|
|
|
|
1324
|
push @conds, { $lhs => { '=', $rhs } }; |
1241
|
|
|
|
|
|
|
} |
1242
|
|
|
|
|
|
|
elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{-value} and is_plain_value $rhs->{-value}) { |
1243
|
33
|
|
|
|
|
235
|
push @conds, { $lhs => $rhs->{-value} }; |
1244
|
|
|
|
|
|
|
} |
1245
|
|
|
|
|
|
|
elsif (ref $rhs eq 'HASH' and keys %$rhs == 1 and exists $rhs->{'='}) { |
1246
|
1460
|
100
|
100
|
|
|
6139
|
if ( length ref $rhs->{'='} and is_literal_value $rhs->{'='} ) { |
1247
|
696
|
|
|
|
|
7572
|
push @conds, { $lhs => $rhs }; |
1248
|
|
|
|
|
|
|
} |
1249
|
|
|
|
|
|
|
else { |
1250
|
764
|
|
|
|
|
4295
|
for my $p ($self->_collapse_cond_unroll_pairs([ $lhs => $rhs->{'='} ])) { |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
# extra sanity check |
1253
|
769
|
50
|
|
|
|
2569
|
if (keys %$p > 1) { |
1254
|
0
|
|
|
|
|
0
|
require Data::Dumper::Concise; |
1255
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Deepcopy = 1; |
1256
|
|
|
|
|
|
|
$self->throw_exception( |
1257
|
|
|
|
|
|
|
"Internal error: unexpected collapse unroll:" |
1258
|
0
|
|
|
|
|
0
|
. Data::Dumper::Concise::Dumper { in => { $lhs => $rhs }, out => $p } |
|
769
|
|
|
|
|
1936
|
|
1259
|
|
|
|
|
|
|
); |
1260
|
|
|
|
|
|
|
} |
1261
|
|
|
|
|
|
|
|
1262
|
769
|
|
|
|
|
6018
|
my ($l, $r) = %$p; |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
push @conds, ( |
1265
|
|
|
|
|
|
|
! length ref $r |
1266
|
|
|
|
|
|
|
or |
1267
|
|
|
|
|
|
|
# the unroller recursion may return a '=' prepended value already |
1268
|
266
|
100
|
100
|
|
|
1215
|
ref $r eq 'HASH' and keys %$rhs == 1 and exists $rhs->{'='} |
1269
|
|
|
|
|
|
|
or |
1270
|
|
|
|
|
|
|
is_plain_value($r) |
1271
|
|
|
|
|
|
|
) |
1272
|
|
|
|
|
|
|
? { $l => $r } |
1273
|
|
|
|
|
|
|
: { $l => { '=' => $r } } |
1274
|
|
|
|
|
|
|
; |
1275
|
|
|
|
|
|
|
} |
1276
|
|
|
|
|
|
|
} |
1277
|
|
|
|
|
|
|
} |
1278
|
|
|
|
|
|
|
elsif (ref $rhs eq 'ARRAY') { |
1279
|
|
|
|
|
|
|
# some of these conditionals encounter multi-values - roll them out using |
1280
|
|
|
|
|
|
|
# an unshift, which will cause extra looping in the while{} above |
1281
|
60
|
100
|
100
|
|
|
186
|
if (! @$rhs ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1282
|
154
|
|
|
|
|
386
|
push @conds, { $lhs => [] }; |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
elsif ( ($rhs->[0]||'') =~ /^\-(?:and|or)$/i ) { |
1285
|
154
|
50
|
|
|
|
447
|
$self->throw_exception("Value modifier not followed by any values: $lhs => [ $rhs->[0] ] ") |
1286
|
|
|
|
|
|
|
if @$rhs == 1; |
1287
|
|
|
|
|
|
|
|
1288
|
144
|
100
|
|
|
|
367
|
if( $rhs->[0] =~ /^\-and$/i ) { |
|
|
100
|
|
|
|
|
|
1289
|
317
|
|
|
|
|
906
|
unshift @$pairs, map { $lhs => $_ } @{$rhs}[1..$#$rhs]; |
|
144
|
|
|
|
|
320
|
|
|
5
|
|
|
|
|
22
|
|
1290
|
|
|
|
|
|
|
} |
1291
|
|
|
|
|
|
|
# if not an AND then it's an OR |
1292
|
|
|
|
|
|
|
elsif(@$rhs == 2) { |
1293
|
5
|
|
|
|
|
14
|
unshift @$pairs, $lhs => $rhs->[1]; |
1294
|
|
|
|
|
|
|
} |
1295
|
|
|
|
|
|
|
else { |
1296
|
5
|
|
|
|
|
20
|
push @conds, { $lhs => [ @{$rhs}[1..$#$rhs] ] }; |
|
15
|
|
|
|
|
99
|
|
1297
|
|
|
|
|
|
|
} |
1298
|
|
|
|
|
|
|
} |
1299
|
|
|
|
|
|
|
elsif (@$rhs == 1) { |
1300
|
37
|
|
|
|
|
186
|
unshift @$pairs, $lhs => $rhs->[0]; |
1301
|
|
|
|
|
|
|
} |
1302
|
|
|
|
|
|
|
else { |
1303
|
7
|
|
|
|
|
119
|
push @conds, { $lhs => $rhs }; |
1304
|
|
|
|
|
|
|
} |
1305
|
|
|
|
|
|
|
} |
1306
|
|
|
|
|
|
|
# unroll func + { -value => ... } |
1307
|
|
|
|
|
|
|
elsif ( |
1308
|
|
|
|
|
|
|
ref $rhs eq 'HASH' |
1309
|
|
|
|
|
|
|
and |
1310
|
|
|
|
|
|
|
( my ($subop) = keys %$rhs ) == 1 |
1311
|
|
|
|
|
|
|
and |
1312
|
|
|
|
|
|
|
length ref ((values %$rhs)[0]) |
1313
|
|
|
|
|
|
|
and |
1314
|
|
|
|
|
|
|
my $vref = is_plain_value( (values %$rhs)[0] ) |
1315
|
|
|
|
|
|
|
) { |
1316
|
25887
|
|
|
|
|
95628
|
push @conds, { $lhs => { $subop => $$vref } } |
1317
|
|
|
|
|
|
|
} |
1318
|
|
|
|
|
|
|
else { |
1319
|
26267
|
|
|
|
|
79397
|
push @conds, { $lhs => $rhs }; |
1320
|
|
|
|
|
|
|
} |
1321
|
|
|
|
|
|
|
} |
1322
|
|
|
|
|
|
|
} |
1323
|
|
|
|
|
|
|
|
1324
|
0
|
|
|
|
|
0
|
return @conds; |
1325
|
|
|
|
|
|
|
} |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
# Analyzes a given condition and attempts to extract all columns |
1328
|
|
|
|
|
|
|
# with a definitive fixed-condition criteria. Returns a hashref |
1329
|
|
|
|
|
|
|
# of k/v pairs suitable to be passed to set_columns(), with a |
1330
|
|
|
|
|
|
|
# MAJOR CAVEAT - multi-value (contradictory) equalities are still |
1331
|
|
|
|
|
|
|
# represented as a reference to the UNRESOVABLE_CONDITION constant |
1332
|
|
|
|
|
|
|
# The reason we do this is that some codepaths only care about the |
1333
|
|
|
|
|
|
|
# codition being stable, as opposed to actually making sense |
1334
|
|
|
|
|
|
|
# |
1335
|
|
|
|
|
|
|
# The normal mode is used to figure out if a resultset is constrained |
1336
|
|
|
|
|
|
|
# to a column which is part of a unique constraint, which in turn |
1337
|
|
|
|
|
|
|
# allows us to better predict how ordering will behave etc. |
1338
|
|
|
|
|
|
|
# |
1339
|
|
|
|
|
|
|
# With the optional "consider_nulls" boolean argument, the function |
1340
|
|
|
|
|
|
|
# is instead used to infer inambiguous values from conditions |
1341
|
|
|
|
|
|
|
# (e.g. the inheritance of resultset conditions on new_result) |
1342
|
|
|
|
|
|
|
# |
1343
|
|
|
|
|
|
|
sub _extract_fixed_condition_columns { |
1344
|
11489
|
|
|
11489
|
|
3992737
|
my ($self, $where, $consider_nulls) = @_; |
1345
|
11489
|
|
|
|
|
34662
|
my $where_hash = $self->_collapse_cond($_[1]); |
1346
|
|
|
|
|
|
|
|
1347
|
11489
|
|
|
|
|
20733
|
my $res = {}; |
1348
|
11489
|
|
|
|
|
19918
|
my ($c, $v); |
1349
|
11489
|
|
|
|
|
22979
|
for $c (keys %$where_hash) { |
1350
|
12708
|
|
|
|
|
19706
|
my $vals; |
1351
|
|
|
|
|
|
|
|
1352
|
12708
|
100
|
100
|
|
|
59455
|
if (!defined ($v = $where_hash->{$c}) ) { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
1353
|
242
|
100
|
|
|
|
738
|
$vals->{UNDEF} = $v if $consider_nulls |
1354
|
|
|
|
|
|
|
} |
1355
|
|
|
|
|
|
|
elsif ( |
1356
|
|
|
|
|
|
|
ref $v eq 'HASH' |
1357
|
|
|
|
|
|
|
and |
1358
|
|
|
|
|
|
|
keys %$v == 1 |
1359
|
|
|
|
|
|
|
) { |
1360
|
477
|
100
|
100
|
|
|
3020
|
if (exists $v->{-value}) { |
|
|
100
|
66
|
|
|
|
|
1361
|
12
|
50
|
|
|
|
29
|
if (defined $v->{-value}) { |
|
|
0
|
|
|
|
|
|
1362
|
|
|
|
|
|
|
$vals->{"VAL_$v->{-value}"} = $v->{-value} |
1363
|
12
|
|
|
|
|
50
|
} |
1364
|
|
|
|
|
|
|
elsif( $consider_nulls ) { |
1365
|
0
|
|
|
|
|
0
|
$vals->{UNDEF} = $v->{-value}; |
1366
|
|
|
|
|
|
|
} |
1367
|
|
|
|
|
|
|
} |
1368
|
|
|
|
|
|
|
# do not need to check for plain values - _collapse_cond did it for us |
1369
|
|
|
|
|
|
|
elsif( |
1370
|
|
|
|
|
|
|
length ref $v->{'='} |
1371
|
|
|
|
|
|
|
and |
1372
|
|
|
|
|
|
|
( |
1373
|
|
|
|
|
|
|
( ref $v->{'='} eq 'HASH' and keys %{$v->{'='}} == 1 and exists $v->{'='}{-ident} ) |
1374
|
|
|
|
|
|
|
or |
1375
|
|
|
|
|
|
|
is_literal_value($v->{'='}) |
1376
|
|
|
|
|
|
|
) |
1377
|
|
|
|
|
|
|
) { |
1378
|
374
|
|
|
|
|
3271
|
$vals->{ 'SER_' . serialize $v->{'='} } = $v->{'='}; |
1379
|
|
|
|
|
|
|
} |
1380
|
|
|
|
|
|
|
} |
1381
|
|
|
|
|
|
|
elsif ( |
1382
|
|
|
|
|
|
|
! length ref $v |
1383
|
|
|
|
|
|
|
or |
1384
|
|
|
|
|
|
|
is_plain_value ($v) |
1385
|
|
|
|
|
|
|
) { |
1386
|
11222
|
|
|
|
|
38937
|
$vals->{"VAL_$v"} = $v; |
1387
|
|
|
|
|
|
|
} |
1388
|
|
|
|
|
|
|
elsif (ref $v eq 'ARRAY' and ($v->[0]||'') eq '-and') { |
1389
|
81
|
|
|
|
|
819
|
for ( @{$v}[1..$#$v] ) { |
|
81
|
|
|
|
|
186
|
|
1390
|
186
|
|
|
|
|
1015
|
my $subval = $self->_extract_fixed_condition_columns({ $c => $_ }, 'consider nulls'); # always fish nulls out on recursion |
1391
|
186
|
100
|
|
|
|
463
|
next unless exists $subval->{$c}; # didn't find anything |
1392
|
|
|
|
|
|
|
$vals->{ |
1393
|
|
|
|
|
|
|
! defined $subval->{$c} ? 'UNDEF' |
1394
|
|
|
|
|
|
|
: ( ! length ref $subval->{$c} or is_plain_value $subval->{$c} ) ? "VAL_$subval->{$c}" |
1395
|
|
|
|
|
|
|
: ( 'SER_' . serialize $subval->{$c} ) |
1396
|
135
|
100
|
66
|
|
|
615
|
} = $subval->{$c}; |
|
|
100
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
} |
1398
|
|
|
|
|
|
|
} |
1399
|
|
|
|
|
|
|
|
1400
|
12708
|
100
|
|
|
|
61693
|
if (keys %$vals == 1) { |
|
|
100
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
($res->{$c}) = (values %$vals) |
1402
|
11743
|
100
|
100
|
|
|
60292
|
unless !$consider_nulls and exists $vals->{UNDEF}; |
1403
|
|
|
|
|
|
|
} |
1404
|
|
|
|
|
|
|
elsif (keys %$vals > 1) { |
1405
|
54
|
|
|
|
|
139
|
$res->{$c} = UNRESOLVABLE_CONDITION; |
1406
|
|
|
|
|
|
|
} |
1407
|
|
|
|
|
|
|
} |
1408
|
|
|
|
|
|
|
|
1409
|
11489
|
|
|
|
|
40886
|
$res; |
1410
|
|
|
|
|
|
|
} |
1411
|
|
|
|
|
|
|
|
1412
|
|
|
|
|
|
|
1; |