line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package DBR::Interface::Where; |
2
|
|
|
|
|
|
|
|
3
|
18
|
|
|
18
|
|
107
|
use strict; |
|
18
|
|
|
|
|
39
|
|
|
18
|
|
|
|
|
688
|
|
4
|
18
|
|
|
18
|
|
100
|
use Carp; |
|
18
|
|
|
|
|
38
|
|
|
18
|
|
|
|
|
1287
|
|
5
|
18
|
|
|
18
|
|
108
|
use DBR::Query::Part; |
|
18
|
|
|
|
|
44
|
|
|
18
|
|
|
|
|
464
|
|
6
|
18
|
|
|
18
|
|
16680
|
use Clone; |
|
18
|
|
|
|
|
108107
|
|
|
18
|
|
|
|
|
1269
|
|
7
|
18
|
|
|
18
|
|
189
|
use Digest::MD5 qw(md5_base64); |
|
18
|
|
|
|
|
41
|
|
|
18
|
|
|
|
|
1010
|
|
8
|
18
|
|
|
18
|
|
10553
|
use DBR::Misc::General; |
|
18
|
|
|
|
|
52
|
|
|
18
|
|
|
|
|
52032
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub new { |
11
|
31
|
|
|
31
|
0
|
210
|
my( $package ) = shift; |
12
|
31
|
|
|
|
|
188
|
my %params = @_; |
13
|
|
|
|
|
|
|
|
14
|
31
|
|
|
|
|
75
|
my $self = {}; |
15
|
|
|
|
|
|
|
|
16
|
31
|
50
|
|
|
|
178
|
$self->{session} = $params{session} or croak "session is required"; |
17
|
31
|
50
|
|
|
|
228
|
$self->{instance} = $params{instance} or croak "instance is required"; |
18
|
31
|
50
|
|
|
|
456
|
$self->{table} = $params{primary_table} or croak "primary_table is required"; |
19
|
|
|
|
|
|
|
|
20
|
31
|
50
|
|
|
|
140
|
croak('primary_table object must be specified') unless ref($self->{table}) eq 'DBR::Config::Table'; |
21
|
|
|
|
|
|
|
|
22
|
31
|
|
|
|
|
125
|
bless( $self, $package ); |
23
|
|
|
|
|
|
|
|
24
|
31
|
|
|
|
|
250
|
$self->{tables} = [$self->{table}]; |
25
|
31
|
|
|
|
|
87
|
$self->{aliascount} = 0; |
26
|
|
|
|
|
|
|
|
27
|
31
|
|
|
|
|
244
|
return( $self ); |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
|
30
|
12
|
|
|
12
|
0
|
89
|
sub tables { shift->{tables} } |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub _andify{ |
33
|
64
|
|
|
64
|
|
101
|
my $self = shift; |
34
|
64
|
100
|
|
|
|
319
|
return $_[0] if (@_ == 1); |
35
|
25
|
|
|
|
|
209
|
return DBR::Query::Part::And->new( @_ ); |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# fast way to discern the difference between one where clause |
39
|
|
|
|
|
|
|
# and another without actually doing the work of assembling everything |
40
|
|
|
|
|
|
|
sub digest{ |
41
|
150010
|
|
|
150010
|
0
|
878718
|
my $self = shift; |
42
|
150010
|
|
|
|
|
201859
|
md5_base64( join ( "\0|", map {_expandstr($_)} @{ shift() } ) ); |
|
600020
|
|
|
|
|
1514568
|
|
|
150010
|
|
|
|
|
278759
|
|
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
sub digest_clear{ |
45
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
46
|
0
|
|
|
|
|
0
|
join ( "\0|", map {_expandstr($_)} @{ shift() } ); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
sub build{ |
49
|
47
|
|
|
47
|
0
|
265
|
my $self = shift; |
50
|
47
|
|
|
|
|
77
|
my @input = @{shift()}; # Make a shallow copy |
|
47
|
|
|
|
|
186
|
|
51
|
47
|
50
|
|
|
|
276
|
scalar (@input) || croak "input is required"; |
52
|
|
|
|
|
|
|
|
53
|
47
|
|
|
|
|
177
|
my $pendgroup = { table => $self->{table} }; # prime the pump. |
54
|
|
|
|
|
|
|
|
55
|
47
|
|
|
|
|
92
|
my @andparts = (); # Storage for finished query part objects |
56
|
47
|
|
|
|
|
72
|
my $pendct; |
57
|
47
|
|
|
|
|
142
|
while (@input){ # Iterate over key/value pairs |
58
|
88
|
|
|
|
|
170
|
my $next = shift @input; |
59
|
88
|
100
|
|
|
|
288
|
if(ref($next) eq 'DBR::_LOP'){ # Logical OPerator |
60
|
22
|
|
|
|
|
99
|
my $op = $next->operator; |
61
|
22
|
50
|
66
|
|
|
114
|
scalar(@andparts) || $pendct || croak('Cannot use an operator without a preceeding comparison'); |
62
|
|
|
|
|
|
|
|
63
|
22
|
100
|
|
|
|
152
|
if ($op eq 'And'){ |
|
|
50
|
|
|
|
|
|
64
|
11
|
100
|
|
|
|
43
|
if( $next->only_contains_and ){ |
65
|
|
|
|
|
|
|
# This is an optomisation to prevent unnecessary recusion, |
66
|
|
|
|
|
|
|
# and to avoid duplication of subqueries when possible. |
67
|
|
|
|
|
|
|
# Because: A and ( B and C ) is equivelant to A and B and C... |
68
|
|
|
|
|
|
|
# We are able to collapse the contents of the AND into the current context, |
69
|
|
|
|
|
|
|
# provided the sequence is maintained. Thus unshift, not push |
70
|
8
|
|
|
|
|
23
|
unshift @input, @{$next->value}; |
|
8
|
|
|
|
|
31
|
|
71
|
|
|
|
|
|
|
}else{ |
72
|
|
|
|
|
|
|
# We have to recurse to handle this situation properly |
73
|
|
|
|
|
|
|
# A AND (B OR C) is not equivelant to A AND B OR C |
74
|
3
|
|
|
|
|
16
|
push @andparts, $self->build( $next->value ); |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
} elsif ( $op eq 'Or' ){ |
77
|
11
|
100
|
|
|
|
40
|
if($pendct){ |
78
|
9
|
|
|
|
|
33
|
push @andparts, $self->_reljoin( $pendgroup ); # Everything before me (pending)... |
79
|
|
|
|
|
|
|
} |
80
|
11
|
|
|
|
|
36
|
my $A = $self->_andify( @andparts ); |
81
|
11
|
|
|
|
|
38
|
my $B = $self->build( $next->value ); # Compared to everything inside |
82
|
|
|
|
|
|
|
|
83
|
11
|
|
|
|
|
74
|
@andparts = ( DBR::Query::Part::Or->new( $A, $B ) ); # Russian dolls... Get in mahh belly |
84
|
|
|
|
|
|
|
|
85
|
11
|
|
|
|
|
43
|
$pendgroup = { table => $self->{table} }; # Reset |
86
|
11
|
|
|
|
|
40
|
$pendct = 0; # Reset |
87
|
|
|
|
|
|
|
}else{ |
88
|
0
|
|
|
|
|
0
|
confess "Sanity error. Invalid operator." |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
22
|
|
|
|
|
71
|
next; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
66
|
|
|
|
|
105
|
my $rawval = shift @input; |
95
|
66
|
|
|
|
|
107
|
$pendct++; |
96
|
66
|
|
|
|
|
230
|
$self->_process_comparison($next, $rawval, $pendgroup); # add it to the hopper |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
47
|
50
|
|
|
|
140
|
scalar(@input) and croak('Odd number of arguments in where parameters'); # I hate leftovers |
101
|
|
|
|
|
|
|
|
102
|
47
|
|
|
|
|
180
|
push @andparts, $self->_reljoin( $pendgroup ); |
103
|
|
|
|
|
|
|
|
104
|
47
|
100
|
|
|
|
208
|
return wantarray?(@andparts):$self->_andify(@andparts); # don't wrap it in an and if we want an array |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# Process ONE comparison. |
109
|
|
|
|
|
|
|
# Walk the relation.relation.relation.field chain and set up the heirarchical hash structure for reljoin. |
110
|
|
|
|
|
|
|
sub _process_comparison{ |
111
|
66
|
|
|
66
|
|
96
|
my $self = shift; |
112
|
|
|
|
|
|
|
|
113
|
66
|
|
|
|
|
129
|
my $key = shift; |
114
|
66
|
|
|
|
|
87
|
my $rawval = shift; |
115
|
66
|
|
|
|
|
90
|
my $ref = shift; |
116
|
|
|
|
|
|
|
|
117
|
66
|
|
|
|
|
351
|
$key =~ /^\s+|\s+$/g; # trim junk |
118
|
66
|
|
|
|
|
334
|
my @parts = split(/\s*\.\s*/,$key); # Break down each key into parts |
119
|
|
|
|
|
|
|
|
120
|
66
|
|
|
|
|
121
|
my $tablect; |
121
|
|
|
|
|
|
|
|
122
|
66
|
|
|
|
|
207
|
my $cur_table = $self->{table}; # Start |
123
|
|
|
|
|
|
|
|
124
|
66
|
|
|
|
|
231
|
while ( my $part = shift @parts ){ |
125
|
78
|
100
|
|
|
|
209
|
my $last = (scalar(@parts) == 0)?1:0; |
126
|
|
|
|
|
|
|
|
127
|
78
|
100
|
|
|
|
181
|
if($last){ # The last part should always be a field |
128
|
66
|
50
|
|
|
|
247
|
croak ('Duplicate field ' .$part ) if $ref->{fields}->{$part}; |
129
|
|
|
|
|
|
|
|
130
|
66
|
50
|
|
|
|
363
|
my $field = $cur_table->get_field( $part ) or croak("invalid field $part"); |
131
|
66
|
50
|
|
|
|
345
|
my $value = $field->makevalue( $rawval ) or croak("failed to build value object for $part"); |
132
|
|
|
|
|
|
|
|
133
|
66
|
50
|
|
|
|
406
|
my $out = DBR::Query::Part::Compare->new( field => $field, value => $value ) or confess('failed to create compare object'); |
134
|
66
|
|
|
|
|
297
|
my $conn = $self->{instance}->connect; |
135
|
|
|
|
|
|
|
|
136
|
66
|
|
|
|
|
399
|
$ref->{fields}->{$part} = $out; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
}else{ |
139
|
|
|
|
|
|
|
#test for relation? |
140
|
12
|
|
100
|
|
|
91
|
$ref = $ref->{kids}->{$part} ||= {}; # step deeper into the tree |
141
|
|
|
|
|
|
|
|
142
|
12
|
100
|
|
|
|
41
|
if( $ref->{been_here} ){ # Dejavu - merge any common paths together |
143
|
|
|
|
|
|
|
|
144
|
3
|
|
|
|
|
12
|
$cur_table = $ref->{table}; # next! |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
}else{ |
147
|
|
|
|
|
|
|
|
148
|
9
|
50
|
|
|
|
54
|
my $relation = $cur_table->get_relation($part) or croak("invalid relationship $part"); |
149
|
9
|
50
|
|
|
|
37
|
my $maptable = $relation->maptable or confess("failed to get maptable"); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Any to_one relationship results in a join. we'll need some table aliases for later. |
152
|
|
|
|
|
|
|
# Do them now so everything is in sync. I originally assigned the alias in _reljoin, |
153
|
|
|
|
|
|
|
# but it didn't always alias the fields that needed to be aliased due to the order of execution. |
154
|
9
|
50
|
33
|
|
|
39
|
if( $relation->is_same_schema && $relation->is_to_one ){ |
155
|
9
|
50
|
|
|
|
43
|
croak ('No more than 25 tables allowed in a join') if $self->{aliascount} > 24; |
156
|
|
|
|
|
|
|
|
157
|
9
|
100
|
|
|
|
54
|
$cur_table ->alias() || $cur_table ->alias( chr(97 + $self->{aliascount}++) ); # might be doing this one again |
158
|
9
|
|
|
|
|
44
|
$maptable ->alias( chr(97 + $self->{aliascount}++) ); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
9
|
|
|
|
|
28
|
$ref->{relation} = $relation; |
162
|
9
|
|
|
|
|
20
|
$ref->{prevtable} = $cur_table; |
163
|
9
|
|
|
|
|
19
|
$ref->{table} = $maptable; |
164
|
9
|
|
|
|
|
18
|
$ref->{been_here} = 1; |
165
|
|
|
|
|
|
|
|
166
|
9
|
|
|
|
|
61
|
$cur_table = $maptable; # next! |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
sub _reljoin{ |
173
|
65
|
|
|
65
|
|
105
|
my $self = shift; |
174
|
65
|
|
|
|
|
169
|
my $ref = shift; |
175
|
65
|
|
66
|
|
|
527
|
my $tables = shift || $self->{tables}; # Allow override of table list for subqueries |
176
|
|
|
|
|
|
|
|
177
|
65
|
50
|
|
|
|
290
|
confess ('ref must be hash') unless ref($ref) eq 'HASH'; |
178
|
|
|
|
|
|
|
|
179
|
65
|
|
|
|
|
86
|
my @and; |
180
|
|
|
|
|
|
|
|
181
|
65
|
100
|
|
|
|
270
|
if($ref->{kids}){ |
182
|
9
|
|
|
|
|
15
|
foreach my $key (sort keys %{$ref->{kids}}){ # sort for consistent sql ordering |
|
9
|
|
|
|
|
50
|
|
183
|
9
|
|
|
|
|
75
|
my $kid = $ref->{kids}->{ $key }; |
184
|
9
|
|
|
|
|
18
|
my $relation = $kid->{relation}; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# it's important we use the same table objects to preserve aliases |
187
|
|
|
|
|
|
|
|
188
|
9
|
50
|
|
|
|
32
|
my $table = $kid->{table} or confess("failed to get table"); |
189
|
9
|
50
|
|
|
|
39
|
my $prevtable = $kid->{prevtable} or confess("failed to get prev_table"); |
190
|
|
|
|
|
|
|
|
191
|
9
|
50
|
|
|
|
47
|
my $field = $relation->mapfield or confess('Failed to fetch field'); |
192
|
9
|
50
|
|
|
|
46
|
my $prevfield = $relation->field or confess('Failed to fetch prevfield'); |
193
|
|
|
|
|
|
|
|
194
|
9
|
|
|
|
|
34
|
my $prevalias = $prevtable ->alias(); |
195
|
9
|
|
|
|
|
28
|
my $alias = $table ->alias(); |
196
|
|
|
|
|
|
|
|
197
|
9
|
50
|
|
|
|
70
|
$prevfield ->table_alias( $prevalias ) if $prevalias; |
198
|
9
|
50
|
|
|
|
41
|
$field ->table_alias( $alias ) if $alias; |
199
|
|
|
|
|
|
|
|
200
|
9
|
50
|
33
|
|
|
29
|
if ($relation->is_same_schema && $relation->is_to_one) { # Do a join |
201
|
|
|
|
|
|
|
|
202
|
9
|
50
|
|
|
|
25
|
$prevalias or die('Sanity error: prevtable alias is required'); |
203
|
9
|
50
|
|
|
|
23
|
$alias or die('Sanity error: table alias is required'); |
204
|
|
|
|
|
|
|
|
205
|
9
|
|
|
|
|
24
|
push @$tables, $table; |
206
|
|
|
|
|
|
|
|
207
|
9
|
50
|
|
|
|
72
|
my $where = $self->_reljoin( $kid, $tables ) or confess('_reljoin failed'); |
208
|
9
|
|
|
|
|
20
|
push @and, $where; |
209
|
|
|
|
|
|
|
|
210
|
9
|
50
|
|
|
|
56
|
my $join = DBR::Query::Part::Join->new($field,$prevfield) or confess('failed to create join object'); |
211
|
9
|
|
|
|
|
37
|
push @and, $join; |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
}else{ # if it's a to_many relationship ( or cross schema ), then subqery |
214
|
0
|
|
|
|
|
0
|
my @tables = $table; |
215
|
0
|
0
|
|
|
|
0
|
my $where = $self->_reljoin( $kid, \@tables ) or confess('_reljoin failed'); |
216
|
|
|
|
|
|
|
|
217
|
0
|
|
|
|
|
0
|
my $instance = $self->{instance}; |
218
|
0
|
0
|
|
|
|
0
|
unless ( $relation->is_same_schema ){ |
219
|
0
|
0
|
|
|
|
0
|
$instance = $table->schema->get_instance( $instance->class ) or return $self->_error('Failed to retrieve db instance for subquery table'); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
0
|
0
|
|
|
|
0
|
my $query = DBR::Query::Select->new( |
223
|
|
|
|
|
|
|
instance => $instance, |
224
|
|
|
|
|
|
|
session => $self->{session}, |
225
|
|
|
|
|
|
|
fields => [$field], |
226
|
|
|
|
|
|
|
tables => \@tables, |
227
|
|
|
|
|
|
|
where => $where, |
228
|
|
|
|
|
|
|
) or confess('failed to create query object'); |
229
|
|
|
|
|
|
|
|
230
|
0
|
|
|
|
|
0
|
my $runflag = ! $relation->is_same_schema; |
231
|
0
|
0
|
|
|
|
0
|
my $subquery = DBR::Query::Part::Subquery->new($prevfield, $query, $runflag) or confess ('failed to create subquery object'); |
232
|
0
|
|
|
|
|
0
|
push @and, $subquery; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# It's important that fields are evaluated after all relationships are processed for this node |
239
|
65
|
100
|
|
|
|
197
|
if($ref->{fields}){ |
240
|
48
|
|
|
|
|
301
|
my $alias = $ref->{table}->alias; |
241
|
|
|
|
|
|
|
|
242
|
48
|
|
|
|
|
101
|
foreach my $key (sort keys %{$ref->{fields}}){ |
|
48
|
|
|
|
|
571
|
|
243
|
66
|
|
|
|
|
138
|
my $compare = $ref->{fields}->{ $key }; |
244
|
66
|
100
|
|
|
|
189
|
$compare->field->table_alias( $alias ) if $alias; |
245
|
66
|
|
|
|
|
212
|
push @and, $compare; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
65
|
100
|
|
|
|
301
|
return wantarray?(@and):$self->_andify(@and); # don't wrap it in an and if we want an array |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
1; |