line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package UR::Context; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Methods related to the import iterator (part of the loading process). |
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# They are broken out here for readability purposes. The methods still live |
6
|
|
|
|
|
|
|
# in the UR::Context namespace. |
7
|
|
|
|
|
|
|
|
8
|
266
|
|
|
266
|
|
998
|
use strict; |
|
266
|
|
|
|
|
337
|
|
|
266
|
|
|
|
|
6661
|
|
9
|
266
|
|
|
266
|
|
906
|
use warnings; |
|
266
|
|
|
|
|
303
|
|
|
266
|
|
|
|
|
739412
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# A wrapper around the method of the same name in UR::DataSource::* to iterate over the |
15
|
|
|
|
|
|
|
# possible data sources involved in a query. The easy case (a query against a single data source) |
16
|
|
|
|
|
|
|
# will return the $primary_template data structure. If the query involves more than one data source, |
17
|
|
|
|
|
|
|
# then this method also returns a list containing triples (@addl_loading_info) where each member is: |
18
|
|
|
|
|
|
|
# 1) The secondary data source name |
19
|
|
|
|
|
|
|
# 2) a listref of delegated properties joining the primary class to the secondary class |
20
|
|
|
|
|
|
|
# 3) a rule template applicable against the secondary data source |
21
|
|
|
|
|
|
|
sub _resolve_query_plan_for_ds_and_bxt { |
22
|
2045
|
|
|
2045
|
|
3207
|
my($self,$primary_data_source,$rule_template) = @_; |
23
|
|
|
|
|
|
|
|
24
|
2045
|
|
|
|
|
7023
|
my $primary_query_plan = $primary_data_source->_resolve_query_plan($rule_template); |
25
|
|
|
|
|
|
|
|
26
|
2045
|
100
|
|
|
|
6093
|
unless ($primary_query_plan->{'joins_across_data_sources'}) { |
27
|
|
|
|
|
|
|
# Common, easy case |
28
|
2031
|
|
|
|
|
4589
|
return $primary_query_plan; |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
14
|
|
|
|
|
18
|
my @addl_loading_info; |
32
|
14
|
|
|
|
|
21
|
foreach my $secondary_data_source_id ( keys %{$primary_query_plan->{'joins_across_data_sources'}} ) { |
|
14
|
|
|
|
|
50
|
|
33
|
14
|
|
|
|
|
25
|
my $this_ds_delegations = $primary_query_plan->{'joins_across_data_sources'}->{$secondary_data_source_id}; |
34
|
|
|
|
|
|
|
|
35
|
14
|
|
|
|
|
25
|
my %seen_properties; |
36
|
14
|
|
|
|
|
27
|
foreach my $delegated_property ( @$this_ds_delegations ) { |
37
|
14
|
|
|
|
|
41
|
my $delegated_property_name = $delegated_property->property_name; |
38
|
14
|
50
|
|
|
|
51
|
next if ($seen_properties{$delegated_property_name}++); |
39
|
|
|
|
|
|
|
|
40
|
14
|
|
|
|
|
59
|
my $operator = $rule_template->operator_for($delegated_property_name); |
41
|
14
|
|
50
|
|
|
36
|
$operator ||= '='; # FIXME - shouldn't the template return this for us? |
42
|
14
|
|
|
|
|
42
|
my @secondary_params = ($delegated_property->to . ' ' . $operator); |
43
|
|
|
|
|
|
|
|
44
|
14
|
|
|
|
|
64
|
my $class_meta = UR::Object::Type->get($delegated_property->class_name); |
45
|
14
|
|
|
|
|
102
|
my $relation_property = $class_meta->property_meta_for_name($delegated_property->via); |
46
|
|
|
|
|
|
|
|
47
|
14
|
|
|
|
|
44
|
my $secondary_class = $relation_property->data_type; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# we can also add in any properties in the property's joins that also appear in the rule |
50
|
14
|
|
|
|
|
49
|
my @property_pairs = $relation_property->get_property_name_pairs_for_join(); |
51
|
14
|
|
|
|
|
28
|
foreach my $pair ( @property_pairs ) { |
52
|
14
|
|
|
|
|
29
|
my($primary_property, $secondary_property) = @$pair; |
53
|
14
|
50
|
|
|
|
49
|
next if ($seen_properties{$primary_property}++); |
54
|
14
|
50
|
|
|
|
53
|
next unless ($rule_template->specifies_value_for($primary_property)); |
55
|
|
|
|
|
|
|
|
56
|
0
|
|
|
|
|
0
|
my $operator = $rule_template->operator_for($primary_property); |
57
|
0
|
|
0
|
|
|
0
|
$operator ||= '='; |
58
|
0
|
|
|
|
|
0
|
push @secondary_params, "$secondary_property $operator"; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
14
|
|
|
|
|
57
|
my $secondary_rule_template = UR::BoolExpr::Template->resolve($secondary_class, @secondary_params); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# FIXME there should be a way to collect all the requests for the same datasource together... |
64
|
|
|
|
|
|
|
# FIXME - currently in the process of switching to object-based instead of class-based data sources |
65
|
|
|
|
|
|
|
# For now, data sources are still singleton objects, so this get() will work. When we're fully on |
66
|
|
|
|
|
|
|
# regular-object-based data sources, then it'll probably change to UR::DataSource->get($secondary_data_source_id); |
67
|
14
|
|
33
|
|
|
52
|
my $secondary_data_source = UR::DataSource->get($secondary_data_source_id) || $secondary_data_source_id->get(); |
68
|
14
|
|
|
|
|
66
|
push @addl_loading_info, |
69
|
|
|
|
|
|
|
$secondary_data_source, |
70
|
|
|
|
|
|
|
[$delegated_property], |
71
|
|
|
|
|
|
|
$secondary_rule_template; |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
14
|
|
|
|
|
42
|
return ($primary_query_plan, @addl_loading_info); |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# Used by _create_secondary_loading_comparators to convert a rule against the primary data source |
80
|
|
|
|
|
|
|
# to a rule that can be used against a secondary data source |
81
|
|
|
|
|
|
|
# FIXME this might be made simpler be leaning on infer_property_value_from_rule()? |
82
|
|
|
|
|
|
|
sub _create_secondary_rule_from_primary { |
83
|
14
|
|
|
14
|
|
41
|
my($self,$primary_rule, $delegated_properties, $secondary_rule_template) = @_; |
84
|
|
|
|
|
|
|
|
85
|
14
|
|
|
|
|
15
|
my @secondary_values; |
86
|
|
|
|
|
|
|
my %seen_properties; # FIXME - we've already been over this list in _resolve_query_plan_for_ds_and_bxt()... |
87
|
|
|
|
|
|
|
# FIXME - is there ever a case where @$delegated_properties will be more than one item? |
88
|
14
|
|
|
|
|
27
|
foreach my $property ( @$delegated_properties ) { |
89
|
14
|
|
|
|
|
52
|
my $value = $primary_rule->value_for($property->property_name); |
90
|
|
|
|
|
|
|
|
91
|
14
|
|
|
|
|
37
|
my $secondary_property_name = $property->to; |
92
|
14
|
|
|
|
|
38
|
my $pos = $secondary_rule_template->value_position_for_property_name($secondary_property_name); |
93
|
14
|
|
|
|
|
30
|
$secondary_values[$pos] = $value; |
94
|
14
|
|
|
|
|
39
|
$seen_properties{$property->property_name}++; |
95
|
|
|
|
|
|
|
|
96
|
14
|
|
|
|
|
53
|
my $class_meta = $property->class_meta; |
97
|
14
|
|
|
|
|
39
|
my $via_property = $class_meta->property_meta_for_name($property->via); |
98
|
14
|
|
|
|
|
55
|
my @pairs = $via_property->get_property_name_pairs_for_join(); |
99
|
14
|
|
|
|
|
25
|
foreach my $pair ( @pairs ) { |
100
|
14
|
|
|
|
|
28
|
my($primary_property_name, $secondary_property_name) = @$pair; |
101
|
|
|
|
|
|
|
|
102
|
14
|
50
|
|
|
|
39
|
next if ($seen_properties{$primary_property_name}++); |
103
|
14
|
|
|
|
|
39
|
$value = $primary_rule->value_for($primary_property_name); |
104
|
14
|
50
|
|
|
|
52
|
next unless $value; |
105
|
|
|
|
|
|
|
|
106
|
0
|
|
|
|
|
0
|
$pos = $secondary_rule_template->value_position_for_property_name($secondary_property_name); |
107
|
0
|
|
|
|
|
0
|
$secondary_values[$pos] = $value; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
14
|
|
|
|
|
61
|
my $secondary_rule = $secondary_rule_template->get_rule_for_values(@secondary_values); |
112
|
|
|
|
|
|
|
|
113
|
14
|
|
|
|
|
34
|
return $secondary_rule; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# Since we'll be appending more "columns" of data to the listrefs returned by |
118
|
|
|
|
|
|
|
# the primary datasource's query, we need to apply fixups to the column positions |
119
|
|
|
|
|
|
|
# to all the secondary loading templates |
120
|
|
|
|
|
|
|
# The column_position and object_num offsets needed for the next call of this method |
121
|
|
|
|
|
|
|
# are returned |
122
|
|
|
|
|
|
|
sub _fixup_secondary_loading_template_column_positions { |
123
|
14
|
|
|
14
|
|
25
|
my($self,$primary_loading_templates, $secondary_loading_templates, $column_position_offset, $object_num_offset) = @_; |
124
|
|
|
|
|
|
|
|
125
|
14
|
50
|
33
|
|
|
50
|
if (! defined($column_position_offset) or ! defined($object_num_offset)) { |
126
|
14
|
|
|
|
|
16
|
$column_position_offset = 0; |
127
|
14
|
|
|
|
|
14
|
foreach my $tmpl ( @{$primary_loading_templates} ) { |
|
14
|
|
|
|
|
32
|
|
128
|
14
|
|
|
|
|
14
|
$column_position_offset += scalar(@{$tmpl->{'column_positions'}}); |
|
14
|
|
|
|
|
35
|
|
129
|
|
|
|
|
|
|
} |
130
|
14
|
|
|
|
|
12
|
$object_num_offset = scalar(@{$primary_loading_templates}); |
|
14
|
|
|
|
|
27
|
|
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
14
|
|
|
|
|
16
|
my $this_template_column_count; |
134
|
14
|
|
|
|
|
23
|
foreach my $tmpl ( @$secondary_loading_templates ) { |
135
|
14
|
|
|
|
|
17
|
foreach ( @{$tmpl->{'column_positions'}} ) { |
|
14
|
|
|
|
|
27
|
|
136
|
32
|
|
|
|
|
66
|
$_ += $column_position_offset; |
137
|
|
|
|
|
|
|
} |
138
|
14
|
|
|
|
|
18
|
foreach ( @{$tmpl->{'id_column_positions'}} ) { |
|
14
|
|
|
|
|
25
|
|
139
|
14
|
|
|
|
|
19
|
$_ += $column_position_offset; |
140
|
|
|
|
|
|
|
} |
141
|
14
|
|
|
|
|
22
|
$tmpl->{'object_num'} += $object_num_offset; |
142
|
|
|
|
|
|
|
|
143
|
14
|
|
|
|
|
13
|
$this_template_column_count += scalar(@{$tmpl->{'column_positions'}}); |
|
14
|
|
|
|
|
29
|
|
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
14
|
|
|
|
|
32
|
return ($column_position_offset + $this_template_column_count, |
148
|
|
|
|
|
|
|
$object_num_offset + scalar(@$secondary_loading_templates) ); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# For queries that have to hit multiple data sources, this method creates two lists of |
152
|
|
|
|
|
|
|
# closures. The first is a list of object fabricators, where the loading templates |
153
|
|
|
|
|
|
|
# have been given fixups to the column positions (see _fixup_secondary_loading_template_column_positions()) |
154
|
|
|
|
|
|
|
# The second is a list of closures for each data source (the @addl_loading_info stuff |
155
|
|
|
|
|
|
|
# from _resolve_query_plan_for_ds_and_bxt) that's able to compare the row loaded from the |
156
|
|
|
|
|
|
|
# primary data source and see if it joins to a row from this secondary datasource's database |
157
|
|
|
|
|
|
|
sub _create_secondary_loading_closures { |
158
|
14
|
|
|
14
|
|
33
|
my($self, $primary_template, $rule, @addl_loading_info) = @_; |
159
|
|
|
|
|
|
|
|
160
|
14
|
|
|
|
|
27
|
my $loading_templates = $primary_template->{'loading_templates'}; |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Make a mapping of property name to column positions returned by the primary query |
163
|
14
|
|
|
|
|
23
|
my %primary_query_column_positions; |
164
|
14
|
|
|
|
|
28
|
foreach my $tmpl ( @$loading_templates ) { |
165
|
14
|
|
|
|
|
20
|
my $property_name_count = scalar(@{$tmpl->{'property_names'}}); |
|
14
|
|
|
|
|
26
|
|
166
|
14
|
|
|
|
|
48
|
for (my $i = 0; $i < $property_name_count; $i++) { |
167
|
50
|
|
|
|
|
53
|
my $property_name = $tmpl->{'property_names'}->[$i]; |
168
|
50
|
|
|
|
|
48
|
my $pos = $tmpl->{'column_positions'}->[$i]; |
169
|
50
|
|
|
|
|
110
|
$primary_query_column_positions{$property_name} = $pos; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
14
|
|
|
|
|
17
|
my @secondary_object_importers; |
174
|
|
|
|
|
|
|
my @addl_join_comparators; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# used to shift the apparent column position of the secondary loading template info |
177
|
0
|
|
|
|
|
0
|
my ($column_position_offset,$object_num_offset); |
178
|
|
|
|
|
|
|
|
179
|
14
|
|
|
|
|
36
|
while (@addl_loading_info) { |
180
|
14
|
|
|
|
|
24
|
my $secondary_data_source = shift @addl_loading_info; |
181
|
14
|
|
|
|
|
30
|
my $this_ds_delegations = shift @addl_loading_info; |
182
|
14
|
|
|
|
|
24
|
my $secondary_rule_template = shift @addl_loading_info; |
183
|
|
|
|
|
|
|
|
184
|
14
|
|
|
|
|
62
|
my $secondary_rule = $self->_create_secondary_rule_from_primary ( |
185
|
|
|
|
|
|
|
$rule, |
186
|
|
|
|
|
|
|
$this_ds_delegations, |
187
|
|
|
|
|
|
|
$secondary_rule_template, |
188
|
|
|
|
|
|
|
); |
189
|
14
|
|
|
|
|
54
|
$secondary_data_source = $secondary_data_source->resolve_data_sources_for_rule($secondary_rule); |
190
|
14
|
|
|
|
|
38
|
my $secondary_template = $self->_resolve_query_plan_for_ds_and_bxt($secondary_data_source,$secondary_rule_template); |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# sets of triples where the first in the triple is the column index in the |
193
|
|
|
|
|
|
|
# $secondary_db_row (in the join_comparator closure below), the second is the |
194
|
|
|
|
|
|
|
# index in the $next_db_row. And the last is a flag indicating if we should |
195
|
|
|
|
|
|
|
# perform a numeric comparison. This way we can preserve the order the comparisons |
196
|
|
|
|
|
|
|
# should be done in |
197
|
14
|
|
|
|
|
24
|
my @join_comparison_info; |
198
|
14
|
|
|
|
|
33
|
foreach my $property ( @$this_ds_delegations ) { |
199
|
|
|
|
|
|
|
# first, map column names in the joined class to column names in the primary class |
200
|
14
|
|
|
|
|
19
|
my %foreign_property_name_map; |
201
|
14
|
|
|
|
|
56
|
my @this_property_joins = $property->_resolve_join_chain(); |
202
|
14
|
|
|
|
|
33
|
foreach my $join ( @this_property_joins ) { |
203
|
28
|
100
|
66
|
|
|
237
|
last if ($join->{foreign_class}->isa('UR::Value') and $join eq $this_property_joins[-1]); |
204
|
14
|
|
|
|
|
20
|
my @source_names = @{$join->{'source_property_names'}}; |
|
14
|
|
|
|
|
37
|
|
205
|
14
|
|
|
|
|
43
|
my @foreign_names = @{$join->{'foreign_property_names'}}; |
|
14
|
|
|
|
|
35
|
|
206
|
14
|
|
|
|
|
39
|
@foreign_property_name_map{@foreign_names} = @source_names; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# Now, find out which numbered column in the result query maps to those names |
210
|
14
|
|
|
|
|
27
|
my $secondary_loading_templates = $secondary_template->{'loading_templates'}; |
211
|
14
|
|
|
|
|
31
|
foreach my $tmpl ( @$secondary_loading_templates ) { |
212
|
14
|
|
|
|
|
18
|
my $property_name_count = scalar(@{$tmpl->{'property_names'}}); |
|
14
|
|
|
|
|
30
|
|
213
|
14
|
|
|
|
|
46
|
for (my $i = 0; $i < $property_name_count; $i++) { |
214
|
32
|
|
|
|
|
44
|
my $property_name = $tmpl->{'property_names'}->[$i]; |
215
|
32
|
100
|
|
|
|
99
|
if ($foreign_property_name_map{$property_name}) { |
216
|
|
|
|
|
|
|
# This is the one we're interested in... Where does it come from in the primary query? |
217
|
17
|
|
|
|
|
27
|
my $column_position = $tmpl->{'column_positions'}->[$i]; |
218
|
|
|
|
|
|
|
# What are the types involved? |
219
|
17
|
|
|
|
|
27
|
my $primary_query_column_name = $foreign_property_name_map{$property_name}; |
220
|
17
|
|
|
|
|
71
|
my $primary_property_class_meta = $primary_template->{'class_name'}->__meta__; |
221
|
17
|
|
|
|
|
57
|
my $primary_property_meta = $primary_property_class_meta->property_meta_for_name($primary_query_column_name); |
222
|
17
|
50
|
|
|
|
42
|
unless ($primary_property_meta) { |
223
|
0
|
|
|
|
|
0
|
Carp::croak("Can't resolve property metadata for property '$primary_query_column_name' of class ".$primary_template->{'class_name'}); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
17
|
|
|
|
|
56
|
my $secondary_class_meta = $secondary_template->{'class_name'}->__meta__; |
227
|
17
|
|
|
|
|
50
|
my $secondary_property_meta = $secondary_class_meta->property_meta_for_name($property_name); |
228
|
17
|
50
|
|
|
|
43
|
unless ($secondary_property_meta) { |
229
|
0
|
|
|
|
|
0
|
Carp::croak("Can't resolve property metadata for property '$property_name' of class ".$secondary_template->{'class_name'}); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
17
|
|
|
|
|
23
|
my $comparison_type; |
233
|
17
|
100
|
100
|
|
|
53
|
if ($primary_property_meta->is_numeric && $secondary_property_meta->is_numeric) { |
234
|
11
|
|
|
|
|
14
|
$comparison_type = 1; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
17
|
|
|
|
|
29
|
my $comparison_position; |
238
|
17
|
50
|
|
|
|
46
|
if (exists $primary_query_column_positions{$primary_query_column_name} ) { |
239
|
17
|
|
|
|
|
22
|
$comparison_position = $primary_query_column_positions{$primary_query_column_name}; |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
} else { |
242
|
|
|
|
|
|
|
# This isn't a real column we can get from the data source. Maybe it's |
243
|
|
|
|
|
|
|
# in the constant_property_names of the primary_loading_template? |
244
|
0
|
0
|
|
|
|
0
|
unless (grep { $_ eq $primary_query_column_name} |
|
0
|
|
|
|
|
0
|
|
245
|
0
|
|
|
|
|
0
|
@{$loading_templates->[0]->{'constant_property_names'}}) { |
246
|
|
|
|
|
|
|
die sprintf("Can't resolve datasource comparison to join %s::%s to %s:%s", |
247
|
|
|
|
|
|
|
$primary_template->{'class_name'}, $primary_query_column_name, |
248
|
0
|
|
|
|
|
0
|
$secondary_template->{'class_name'}, $property_name); |
249
|
|
|
|
|
|
|
} |
250
|
0
|
|
|
|
|
0
|
my $comparison_value = $rule->value_for($primary_query_column_name); |
251
|
0
|
0
|
|
|
|
0
|
unless (defined $comparison_value) { |
252
|
0
|
|
|
|
|
0
|
$comparison_value = $self->infer_property_value_from_rule($primary_query_column_name, $rule); |
253
|
|
|
|
|
|
|
} |
254
|
0
|
|
|
|
|
0
|
$comparison_position = \$comparison_value; |
255
|
|
|
|
|
|
|
} |
256
|
17
|
|
|
|
|
69
|
push @join_comparison_info, $column_position, |
257
|
|
|
|
|
|
|
$comparison_position, |
258
|
|
|
|
|
|
|
$comparison_type; |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
} |
265
|
14
|
|
|
|
|
68
|
my $secondary_db_iterator = $secondary_data_source->create_iterator_closure_for_rule($secondary_rule); |
266
|
|
|
|
|
|
|
|
267
|
14
|
|
|
|
|
25
|
my $secondary_db_row; |
268
|
|
|
|
|
|
|
# For this closure, pass in the row we just loaded from the primary DB query. |
269
|
|
|
|
|
|
|
# This one will return the data from this secondary DB's row if the passed-in |
270
|
|
|
|
|
|
|
# row successfully joins to this secondary db iterator. It returns an empty list |
271
|
|
|
|
|
|
|
# if there were no matches, and returns false if there is no more data from the query |
272
|
|
|
|
|
|
|
my $join_comparator = sub { |
273
|
32
|
|
|
32
|
|
40
|
my $next_db_row = shift; # From the primary DB |
274
|
|
|
|
|
|
|
READ_DB_ROW: |
275
|
32
|
|
|
|
|
30
|
while(1) { |
276
|
39
|
50
|
|
|
|
72
|
return unless ($secondary_db_iterator); |
277
|
39
|
100
|
|
|
|
55
|
unless ($secondary_db_row) { |
278
|
21
|
|
|
|
|
43
|
($secondary_db_row) = $secondary_db_iterator->(); |
279
|
21
|
100
|
|
|
|
51
|
unless($secondary_db_row) { |
280
|
|
|
|
|
|
|
# No more data to load |
281
|
8
|
|
|
|
|
12
|
$secondary_db_iterator = undef; |
282
|
8
|
|
|
|
|
35
|
return; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
31
|
|
|
|
|
106
|
for (my $i = 0; $i < @join_comparison_info; $i += 3) { |
287
|
34
|
|
|
|
|
37
|
my $secondary_column = $join_comparison_info[$i]; |
288
|
34
|
|
|
|
|
40
|
my $primary_column = $join_comparison_info[$i+1]; |
289
|
34
|
|
|
|
|
34
|
my $is_numeric = $join_comparison_info[$i+2]; |
290
|
|
|
|
|
|
|
|
291
|
34
|
|
|
|
|
28
|
my $comparison; |
292
|
34
|
50
|
|
|
|
54
|
if (ref $primary_column) { |
293
|
|
|
|
|
|
|
# This was one of those constant value items |
294
|
0
|
0
|
|
|
|
0
|
if ($is_numeric) { |
295
|
0
|
|
|
|
|
0
|
$comparison = $secondary_db_row->[$secondary_column] <=> $$primary_column; |
296
|
|
|
|
|
|
|
} else { |
297
|
0
|
|
|
|
|
0
|
$comparison = $secondary_db_row->[$secondary_column] cmp $$primary_column; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
} else { |
300
|
34
|
100
|
|
|
|
66
|
if ($join_comparison_info[$i+2]) { |
301
|
28
|
|
|
|
|
52
|
$comparison = $secondary_db_row->[$secondary_column] <=> $next_db_row->[$primary_column]; |
302
|
|
|
|
|
|
|
} else { |
303
|
6
|
|
|
|
|
10
|
$comparison = $secondary_db_row->[$secondary_column] cmp $next_db_row->[$primary_column]; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
} |
306
|
34
|
100
|
|
|
|
96
|
if ($comparison < 0) { |
|
|
100
|
|
|
|
|
|
307
|
|
|
|
|
|
|
# less than, get the next row from the secondary DB |
308
|
7
|
|
|
|
|
10
|
$secondary_db_row = undef; |
309
|
7
|
|
|
|
|
11
|
redo READ_DB_ROW; |
310
|
|
|
|
|
|
|
} elsif ($comparison == 0) { |
311
|
|
|
|
|
|
|
# This one was the same, keep looking at the others |
312
|
|
|
|
|
|
|
} else { |
313
|
|
|
|
|
|
|
# greater-than, there's no match for this primary DB row |
314
|
11
|
|
|
|
|
23
|
return 0; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
# All the joined columns compared equal, return the data |
318
|
13
|
|
|
|
|
22
|
return $secondary_db_row; |
319
|
|
|
|
|
|
|
} |
320
|
14
|
|
|
|
|
71
|
}; |
321
|
14
|
|
|
|
|
92
|
Sub::Name::subname('UR::Context::__join_comparator(closure)__', $join_comparator); |
322
|
14
|
|
|
|
|
26
|
push @addl_join_comparators, $join_comparator; |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# And for the object importer/fabricator, here's where we need to shift the column order numbers |
326
|
|
|
|
|
|
|
# over, because these closures will be called after all the db iterators' rows are concatenated |
327
|
|
|
|
|
|
|
# together. We also need to make a copy of the loading_templates list so as to not mess up the |
328
|
|
|
|
|
|
|
# class' notion of where the columns are |
329
|
|
|
|
|
|
|
# FIXME - it seems wasteful that we need to re-created this each time. Look into some way of using |
330
|
|
|
|
|
|
|
# the original copy that lives in $primary_template->{'loading_templates'}? Somewhere else? |
331
|
14
|
|
|
|
|
21
|
my @secondary_loading_templates; |
332
|
14
|
|
|
|
|
18
|
foreach my $tmpl ( @{$secondary_template->{'loading_templates'}} ) { |
|
14
|
|
|
|
|
43
|
|
333
|
14
|
|
|
|
|
18
|
my %copy; |
334
|
14
|
|
|
|
|
64
|
foreach my $key ( keys %$tmpl ) { |
335
|
126
|
|
|
|
|
104
|
my $value_to_copy = $tmpl->{$key}; |
336
|
126
|
100
|
|
|
|
190
|
if (ref($value_to_copy) eq 'ARRAY') { |
|
|
50
|
|
|
|
|
|
337
|
56
|
|
|
|
|
111
|
$copy{$key} = [ @$value_to_copy ]; |
338
|
|
|
|
|
|
|
} elsif (ref($value_to_copy) eq 'HASH') { |
339
|
0
|
|
|
|
|
0
|
$copy{$key} = { %$value_to_copy }; |
340
|
|
|
|
|
|
|
} else { |
341
|
70
|
|
|
|
|
109
|
$copy{$key} = $value_to_copy; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
} |
344
|
14
|
|
|
|
|
37
|
push @secondary_loading_templates, \%copy; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
($column_position_offset,$object_num_offset) = |
347
|
14
|
|
|
|
|
87
|
$self->_fixup_secondary_loading_template_column_positions($primary_template->{'loading_templates'}, |
348
|
|
|
|
|
|
|
\@secondary_loading_templates, |
349
|
|
|
|
|
|
|
$column_position_offset,$object_num_offset); |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
#my($secondary_rule_template,@secondary_values) = $secondary_rule->get_template_and_values(); |
352
|
14
|
|
|
|
|
49
|
my @secondary_values = $secondary_rule->values(); |
353
|
14
|
|
|
|
|
27
|
foreach my $secondary_loading_template ( @secondary_loading_templates ) { |
354
|
14
|
|
|
|
|
82
|
my $secondary_object_importer = UR::Context::ObjectFabricator->create_for_loading_template( |
355
|
|
|
|
|
|
|
$self, |
356
|
|
|
|
|
|
|
$secondary_loading_template, |
357
|
|
|
|
|
|
|
$secondary_template, |
358
|
|
|
|
|
|
|
$secondary_rule, |
359
|
|
|
|
|
|
|
$secondary_rule_template, |
360
|
|
|
|
|
|
|
\@secondary_values, |
361
|
|
|
|
|
|
|
$secondary_data_source |
362
|
|
|
|
|
|
|
); |
363
|
14
|
50
|
|
|
|
41
|
next unless $secondary_object_importer; |
364
|
14
|
|
|
|
|
63
|
push @secondary_object_importers, $secondary_object_importer; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
14
|
|
|
|
|
54
|
return (\@secondary_object_importers, \@addl_join_comparators); |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
# This returns an iterator that is used to bring objects in from an underlying |
375
|
|
|
|
|
|
|
# context into this context. |
376
|
|
|
|
|
|
|
sub _create_import_iterator_for_underlying_context { |
377
|
2054
|
|
|
2054
|
|
3270
|
my ($self, $rule, $dsx, $this_get_serial) = @_; |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# TODO: instead of taking a data source, resolve this internally. |
380
|
|
|
|
|
|
|
# The underlying context itself should be responsible for its data sources. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
# Make an iterator for the primary data source. |
383
|
|
|
|
|
|
|
# Primary here meaning the one for the class we're explicitly requesting. |
384
|
|
|
|
|
|
|
# We may need to join to other data sources to complete the query. |
385
|
2054
|
|
|
|
|
10430
|
my ($db_iterator) |
386
|
|
|
|
|
|
|
= $dsx->create_iterator_closure_for_rule($rule); |
387
|
|
|
|
|
|
|
|
388
|
2031
|
|
|
|
|
7329
|
my ($rule_template, @values) = $rule->template_and_values(); |
389
|
2031
|
|
|
|
|
8801
|
my ($query_plan,@addl_loading_info) = $self->_resolve_query_plan_for_ds_and_bxt($dsx,$rule_template); |
390
|
2031
|
|
|
|
|
3840
|
my $class_name = $query_plan->{class_name}; |
391
|
|
|
|
|
|
|
|
392
|
2031
|
|
|
|
|
4788
|
my $group_by = $rule_template->group_by; |
393
|
2031
|
|
|
|
|
5112
|
my $order_by = $rule_template->order_by; |
394
|
2031
|
|
|
|
|
5833
|
my $aggregate = $rule_template->aggregate; |
395
|
|
|
|
|
|
|
|
396
|
2031
|
50
|
|
|
|
4405
|
if (my $sub_typing_property) { |
397
|
|
|
|
|
|
|
# When the rule has a property specified which indicates a specific sub-type, catch this and re-call |
398
|
|
|
|
|
|
|
# this method recursively with the specific subclass name. |
399
|
0
|
|
|
|
|
0
|
my ($rule_template, @values) = $rule->template_and_values(); |
400
|
0
|
|
|
|
|
0
|
my $rule_template_specifies_value_for_subtype = $query_plan->{rule_template_specifies_value_for_subtype}; |
401
|
0
|
|
|
|
|
0
|
my $class_table_name = $query_plan->{class_table_name}; |
402
|
|
|
|
|
|
|
|
403
|
0
|
|
|
|
|
0
|
warn "Implement me carefully"; |
404
|
|
|
|
|
|
|
|
405
|
0
|
0
|
|
|
|
0
|
if ($rule_template_specifies_value_for_subtype) { |
|
|
0
|
|
|
|
|
|
406
|
0
|
|
|
|
|
0
|
my $sub_classification_meta_class_name = $query_plan->{sub_classification_meta_class_name}; |
407
|
0
|
|
|
|
|
0
|
my $value = $rule->value_for($sub_typing_property); |
408
|
0
|
|
|
|
|
0
|
my $type_obj = $sub_classification_meta_class_name->get($value); |
409
|
0
|
0
|
|
|
|
0
|
if ($type_obj) { |
410
|
0
|
|
|
|
|
0
|
my $subclass_name = $type_obj->subclass_name($class_name); |
411
|
0
|
0
|
0
|
|
|
0
|
if ($subclass_name and $subclass_name ne $class_name) { |
412
|
|
|
|
|
|
|
#$rule = $subclass_name->define_boolexpr($rule->params_list, $sub_typing_property => $value); |
413
|
0
|
|
|
|
|
0
|
$rule = UR::BoolExpr->resolve_normalized($subclass_name, $rule->params_list, $sub_typing_property => $value); |
414
|
0
|
|
|
|
|
0
|
return $self->_create_import_iterator_for_underlying_context($rule,$dsx,$this_get_serial); |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
else { |
418
|
0
|
|
|
|
|
0
|
die "No $value for $class_name?\n"; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
elsif (not $class_table_name) { |
422
|
0
|
|
|
|
|
0
|
die "No longer supported!"; |
423
|
0
|
|
|
|
|
0
|
my $rule = UR::BoolExpr->resolve( |
424
|
|
|
|
|
|
|
$class_name, |
425
|
|
|
|
|
|
|
$rule_template->get_rule_for_values(@values)->params_list, |
426
|
|
|
|
|
|
|
); |
427
|
0
|
|
|
|
|
0
|
return $self->_create_import_iterator_for_underlying_context($rule,$dsx,$this_get_serial) |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
else { |
430
|
|
|
|
|
|
|
# continue normally |
431
|
|
|
|
|
|
|
# the logic below will handle sub-classifying each returned entity |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
|
436
|
2031
|
|
|
|
|
3648
|
my $loading_templates = $query_plan->{loading_templates}; |
437
|
2031
|
|
|
|
|
2818
|
my $sub_typing_property = $query_plan->{sub_typing_property}; |
438
|
2031
|
|
|
|
|
2271
|
my $next_db_row; |
439
|
2031
|
|
|
|
|
2480
|
my $rows = 0; # number of rows the query returned |
440
|
|
|
|
|
|
|
|
441
|
2031
|
|
|
|
|
2610
|
my $recursion_desc = $query_plan->{recursion_desc}; |
442
|
2031
|
|
|
|
|
2560
|
my($rule_template_without_recursion_desc, $rule_template_id_without_recursion); |
443
|
0
|
|
|
|
|
0
|
my($rule_without_recursion_desc, $rule_id_without_recursion); |
444
|
|
|
|
|
|
|
# These get set if you're doing a -recurse query, and the underlying data source doesn't support recursion |
445
|
0
|
|
|
|
|
0
|
my($by_hand_recursive_rule_template,$by_hand_recursive_source_property,@by_hand_recursive_source_values,$by_hand_recursing_iterator); |
446
|
2031
|
100
|
|
|
|
4297
|
if ($recursion_desc) { |
447
|
23
|
|
|
|
|
42
|
$rule_template_without_recursion_desc = $query_plan->{rule_template_without_recursion_desc}; |
448
|
23
|
|
|
|
|
57
|
$rule_template_id_without_recursion = $rule_template_without_recursion_desc->id; |
449
|
23
|
|
|
|
|
55
|
$rule_without_recursion_desc = $rule_template_without_recursion_desc->get_rule_for_values(@values); |
450
|
23
|
|
|
|
|
46
|
$rule_id_without_recursion = $rule_without_recursion_desc->id; |
451
|
|
|
|
|
|
|
|
452
|
23
|
50
|
|
|
|
60
|
if ($query_plan->{'recurse_resolution_by_iteration'}) { |
453
|
|
|
|
|
|
|
# The data source does not support a recursive query. Accomplish the same thing by |
454
|
|
|
|
|
|
|
# recursing back into _create_import_iterator_for_underlying_context for each level |
455
|
23
|
|
|
|
|
24
|
my $this; |
456
|
23
|
|
|
|
|
49
|
($this,$by_hand_recursive_source_property) = @$recursion_desc; |
457
|
|
|
|
|
|
|
|
458
|
23
|
|
|
|
|
22
|
my @extra; |
459
|
23
|
|
|
|
|
93
|
$by_hand_recursive_rule_template = UR::BoolExpr::Template->resolve($class_name, "$this in"); |
460
|
23
|
|
|
|
|
70
|
$by_hand_recursive_rule_template->recursion_desc($recursion_desc); |
461
|
23
|
50
|
33
|
|
|
44
|
if (!$by_hand_recursive_rule_template or @extra) { |
462
|
0
|
|
|
|
|
0
|
Carp::croak("Can't resolve recursive query: Class $class_name cannot filter by one or more properties: " |
463
|
|
|
|
|
|
|
. join(', ', @extra)); |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
|
468
|
2031
|
|
|
|
|
5270
|
my $rule_id = $rule->id; |
469
|
2031
|
|
|
|
|
4291
|
my $rule_template_id = $rule_template->id; |
470
|
|
|
|
|
|
|
|
471
|
2031
|
|
|
|
|
3086
|
my $needs_further_boolexpr_evaluation_after_loading = $query_plan->{'needs_further_boolexpr_evaluation_after_loading'}; |
472
|
|
|
|
|
|
|
|
473
|
2031
|
|
|
|
|
2446
|
my %subordinate_iterator_for_class; |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# TODO: move the creation of the fabricators into the query plan object initializer. |
476
|
|
|
|
|
|
|
# instead of making just one import iterator, we make one per loading template |
477
|
|
|
|
|
|
|
# we then have our primary iterator use these to fabricate objects for each db row |
478
|
|
|
|
|
|
|
my @object_fabricators; |
479
|
2031
|
100
|
|
|
|
4174
|
if ($group_by) { |
480
|
|
|
|
|
|
|
# returning sets for each sub-group instead of instance objects... |
481
|
27
|
|
|
|
|
46
|
my $division_point = scalar(@$group_by)-1; |
482
|
27
|
|
|
|
|
146
|
my $subset_template = $rule_template->_template_for_grouped_subsets(); |
483
|
27
|
|
|
|
|
53
|
my $set_class = $class_name . '::Set'; |
484
|
27
|
100
|
|
|
|
79
|
my @aggregate_properties = ($aggregate ? @$aggregate : ()); |
485
|
27
|
100
|
|
|
|
53
|
unshift(@aggregate_properties, 'count') unless (grep { $_ eq 'count' } @aggregate_properties); |
|
24
|
|
|
|
|
93
|
|
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
my $fab_subref = sub { |
488
|
32
|
|
|
32
|
|
38
|
my $row = $_[0]; |
489
|
32
|
|
|
|
|
95
|
my @group_values = @$row[0..$division_point]; |
490
|
32
|
|
|
|
|
149
|
my $ss_rule = $subset_template->get_rule_for_values(@values, @group_values); |
491
|
32
|
|
|
|
|
75
|
my $set = $set_class->get($ss_rule->id); |
492
|
32
|
50
|
|
|
|
122
|
unless ($set) { |
493
|
0
|
|
|
|
|
0
|
Carp::croak("Failed to fabricate $set_class for rule $ss_rule"); |
494
|
|
|
|
|
|
|
} |
495
|
32
|
|
100
|
|
|
160
|
my $aggregates = $set->{__aggregates} ||= {}; |
496
|
32
|
|
|
|
|
142
|
@$aggregates{@aggregate_properties} = @$row[$division_point+1..$#$row]; |
497
|
32
|
|
|
|
|
59
|
return $set; |
498
|
27
|
|
|
|
|
167
|
}; |
499
|
|
|
|
|
|
|
|
500
|
27
|
|
|
|
|
186
|
my $object_fabricator = UR::Context::ObjectFabricator->_create( |
501
|
|
|
|
|
|
|
fabricator => $fab_subref, |
502
|
|
|
|
|
|
|
context => $self, |
503
|
|
|
|
|
|
|
); |
504
|
27
|
|
|
|
|
67
|
unshift @object_fabricators, $object_fabricator; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
else { |
507
|
|
|
|
|
|
|
# regular instances |
508
|
2004
|
|
|
|
|
3786
|
for my $loading_template (@$loading_templates) { |
509
|
2164
|
|
|
|
|
12312
|
my $object_fabricator = |
510
|
|
|
|
|
|
|
UR::Context::ObjectFabricator->create_for_loading_template( |
511
|
|
|
|
|
|
|
$self, |
512
|
|
|
|
|
|
|
$loading_template, |
513
|
|
|
|
|
|
|
$query_plan, |
514
|
|
|
|
|
|
|
$rule, |
515
|
|
|
|
|
|
|
$rule_template, |
516
|
|
|
|
|
|
|
\@values, |
517
|
|
|
|
|
|
|
$dsx, |
518
|
|
|
|
|
|
|
); |
519
|
2164
|
50
|
|
|
|
5036
|
next unless $object_fabricator; |
520
|
2164
|
|
|
|
|
5562
|
unshift @object_fabricators, $object_fabricator; |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
# For joins across data sources, we need to create importers/fabricators for those |
525
|
|
|
|
|
|
|
# classes, as well as callbacks used to perform the equivalent of an SQL join in |
526
|
|
|
|
|
|
|
# UR-space |
527
|
2031
|
|
|
|
|
2372
|
my @addl_join_comparators; |
528
|
2031
|
100
|
|
|
|
4579
|
if (@addl_loading_info) { |
529
|
14
|
50
|
|
|
|
75
|
if ($group_by) { |
530
|
0
|
|
|
|
|
0
|
Carp::croak("cross-datasource group-by is not supported yet"); |
531
|
|
|
|
|
|
|
} |
532
|
14
|
|
|
|
|
79
|
my($addl_object_fabricators, $addl_join_comparators) = |
533
|
|
|
|
|
|
|
$self->_create_secondary_loading_closures( $query_plan, |
534
|
|
|
|
|
|
|
$rule, |
535
|
|
|
|
|
|
|
@addl_loading_info |
536
|
|
|
|
|
|
|
); |
537
|
|
|
|
|
|
|
|
538
|
14
|
|
|
|
|
35
|
unshift @object_fabricators, @$addl_object_fabricators; |
539
|
14
|
|
|
|
|
32
|
push @addl_join_comparators, @$addl_join_comparators; |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
# To avoid calling the useless method 'fabricate' on a fabricator object for each object of each resultset row |
543
|
2031
|
|
|
|
|
3521
|
my @object_fabricator_closures = map { $_->fabricator } @object_fabricators; |
|
2205
|
|
|
|
|
6865
|
|
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# Insert the key into all_objects_are_loaded to indicate that when we're done loading, we'll |
546
|
|
|
|
|
|
|
# have everything |
547
|
2031
|
100
|
100
|
|
|
7271
|
if ($query_plan->{'rule_matches_all'} and not $group_by) { |
548
|
151
|
|
|
|
|
736
|
$class_name->all_objects_are_loaded(undef); |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
#my $is_monitor_query = $self->monitor_query(); |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
# Make the iterator we'll return. |
554
|
2031
|
|
|
|
|
2583
|
my $next_object_to_return; |
555
|
|
|
|
|
|
|
my @object_ids_from_fabricators; |
556
|
|
|
|
|
|
|
my $underlying_context_iterator = sub { |
557
|
105324
|
100
|
|
105324
|
|
130752
|
return undef unless $db_iterator; |
558
|
|
|
|
|
|
|
|
559
|
104149
|
|
|
|
|
63313
|
my $primary_object_for_next_db_row; |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
LOAD_AN_OBJECT: |
562
|
104149
|
|
|
|
|
120399
|
until (defined $primary_object_for_next_db_row) { # note that we return directly when the db is out of data |
563
|
|
|
|
|
|
|
|
564
|
105693
|
|
|
|
|
67084
|
my ($next_db_row); |
565
|
105693
|
50
|
|
|
|
262598
|
($next_db_row) = $db_iterator->() if ($db_iterator); |
566
|
|
|
|
|
|
|
|
567
|
105693
|
100
|
100
|
|
|
190464
|
if (! $next_db_row and $by_hand_recursive_rule_template and @by_hand_recursive_source_values) { |
|
|
|
100
|
|
|
|
|
568
|
|
|
|
|
|
|
# DB is out of results for this query, we need to handle recursion here in the context |
569
|
|
|
|
|
|
|
# and there are values to recurse on |
570
|
22
|
100
|
|
|
|
47
|
unless ($by_hand_recursing_iterator) { |
571
|
|
|
|
|
|
|
# Do a new get() on the data source to recursively get more data |
572
|
10
|
|
|
|
|
43
|
my $recurse_rule = $by_hand_recursive_rule_template->get_rule_for_values(\@by_hand_recursive_source_values); |
573
|
10
|
|
|
|
|
40
|
$by_hand_recursing_iterator = $self->_create_import_iterator_for_underlying_context($recurse_rule,$dsx,$this_get_serial); |
574
|
|
|
|
|
|
|
} |
575
|
22
|
|
|
|
|
31
|
my $retval = $next_object_to_return; |
576
|
22
|
|
|
|
|
34
|
$next_object_to_return = $by_hand_recursing_iterator->(); |
577
|
22
|
100
|
|
|
|
49
|
unless ($next_object_to_return) { |
578
|
10
|
|
|
|
|
13
|
$by_hand_recursing_iterator = undef; |
579
|
10
|
|
|
|
|
274
|
$by_hand_recursive_rule_template = undef; |
580
|
|
|
|
|
|
|
} |
581
|
22
|
|
|
|
|
74
|
return $retval; |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
105671
|
100
|
|
|
|
132584
|
unless ($next_db_row) { |
585
|
1978
|
|
|
|
|
2586
|
$db_iterator = undef; |
586
|
|
|
|
|
|
|
|
587
|
1978
|
100
|
|
|
|
24463
|
if ($rows == 0) { |
588
|
|
|
|
|
|
|
# if we got no data at all from the sql then we give a status |
589
|
|
|
|
|
|
|
# message about it and we update all_params_loaded to indicate |
590
|
|
|
|
|
|
|
# that this set of parameters yielded 0 objects |
591
|
|
|
|
|
|
|
|
592
|
796
|
|
|
|
|
1725
|
my $rule_template_is_id_only = $query_plan->{rule_template_is_id_only}; |
593
|
796
|
100
|
|
|
|
1888
|
if ($rule_template_is_id_only) { |
594
|
93
|
|
|
|
|
401
|
my $id = $rule->value_for_id; |
595
|
93
|
|
|
|
|
352
|
$UR::Context::all_objects_loaded->{$class_name}->{$id} = undef; |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
else { |
598
|
703
|
|
|
|
|
2941
|
$UR::Context::all_params_loaded->{$rule_template_id}->{$rule_id} = 0; |
599
|
|
|
|
|
|
|
} |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
|
602
|
1978
|
100
|
|
|
|
5657
|
if ( $query_plan->{rule_matches_all} ) { |
603
|
|
|
|
|
|
|
# No parameters. We loaded the whole class. |
604
|
|
|
|
|
|
|
# Doing a load w/o a specific ID w/o custom SQL loads the whole class. |
605
|
|
|
|
|
|
|
# Set a flag so that certain optimizations can be made, such as |
606
|
|
|
|
|
|
|
# short-circuiting future loads of this class. |
607
|
|
|
|
|
|
|
# |
608
|
|
|
|
|
|
|
# If the key still exists in the all_objects_are_loaded hash, then |
609
|
|
|
|
|
|
|
# we can set it to true. This is needed in the case where the user |
610
|
|
|
|
|
|
|
# gets an iterator for all the objects of some class, but unloads |
611
|
|
|
|
|
|
|
# one or more of the instances (be calling unload or through the |
612
|
|
|
|
|
|
|
# cache pruner) before the iterator completes. If so, _abandon_object() |
613
|
|
|
|
|
|
|
# will have removed the key from the hash |
614
|
151
|
100
|
|
|
|
440
|
if (exists($UR::Context::all_objects_are_loaded->{$class_name})) { |
615
|
139
|
|
|
|
|
537
|
$class_name->all_objects_are_loaded(1); |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
|
619
|
1978
|
100
|
|
|
|
4409
|
if ($recursion_desc) { |
620
|
23
|
|
|
|
|
87
|
my @results = $class_name->is_loaded($rule_without_recursion_desc); |
621
|
23
|
|
|
|
|
60
|
$UR::Context::all_params_loaded->{$rule_template_id_without_recursion}{$rule_id_without_recursion} = scalar(@results); |
622
|
23
|
|
|
|
|
46
|
for my $object (@results) { |
623
|
19
|
|
|
|
|
42
|
$object->{__load}->{$rule_template_id_without_recursion}->{$rule_id_without_recursion}++; |
624
|
|
|
|
|
|
|
} |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# Apply changes to all_params_loaded that each importer has collected |
628
|
1978
|
|
|
|
|
3836
|
foreach (@object_fabricators) { |
629
|
2144
|
50
|
|
|
|
9864
|
$_->finalize if $_; |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
# If the SQL for the subclassed items was constructed properly, then each |
633
|
|
|
|
|
|
|
# of these iterators should be at the end, too. Call them one more time |
634
|
|
|
|
|
|
|
# so they'll finalize their object fabricators. |
635
|
1978
|
|
|
|
|
4688
|
foreach my $class ( keys %subordinate_iterator_for_class ) { |
636
|
21
|
|
|
|
|
69
|
my $obj = $subordinate_iterator_for_class{$class}->(); |
637
|
21
|
50
|
|
|
|
71
|
if ($obj) { |
638
|
|
|
|
|
|
|
# The last time this happened, it was because a get() was done on an abstract |
639
|
|
|
|
|
|
|
# base class with only 'id' as a param. When the subclassified rule was |
640
|
|
|
|
|
|
|
# turned into SQL in UR::DataSource::QueryPlan() |
641
|
|
|
|
|
|
|
# it removed that one 'id' filter, since it assummed any class with more than |
642
|
|
|
|
|
|
|
# one ID property (usually classes have a named whatever_id property, and an alias 'id' |
643
|
|
|
|
|
|
|
# property) will have a rule that covered both ID properties |
644
|
0
|
|
|
|
|
0
|
Carp::carp("Leftover objects in subordinate iterator for $class. This shouldn't happen, but it's not fatal..."); |
645
|
0
|
|
|
|
|
0
|
while ($obj = $subordinate_iterator_for_class{$class}->()) {1;} |
|
0
|
|
|
|
|
0
|
|
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
|
649
|
1978
|
|
|
|
|
2450
|
my $retval = $next_object_to_return; |
650
|
1978
|
|
|
|
|
2119
|
$next_object_to_return = undef; |
651
|
1978
|
|
|
|
|
4989
|
return $retval; |
652
|
|
|
|
|
|
|
} |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
# we count rows processed mainly for more concise sanity checking |
655
|
103693
|
|
|
|
|
80959
|
$rows++; |
656
|
|
|
|
|
|
|
# For multi-datasource queries, does this row successfully join with all the other datasources? |
657
|
|
|
|
|
|
|
# |
658
|
|
|
|
|
|
|
# Normally, the policy is for the data source query to return (possibly) more than what you |
659
|
|
|
|
|
|
|
# asked for, and then we'd cache everything that may have been loaded. In this case, we're |
660
|
|
|
|
|
|
|
# making the choice not to. Reason being that a join across databases is likely to involve |
661
|
|
|
|
|
|
|
# a lot of objects, and we don't want to be stuffing our object cache with a lot of things |
662
|
|
|
|
|
|
|
# we're not interested in. FIXME - in order for this to be true, then we could never query |
663
|
|
|
|
|
|
|
# these secondary data sources against, say, a calculated property because we're never turning |
664
|
|
|
|
|
|
|
# them into objects. FIXME - fix this by setting the $needs_further_boolexpr_evaluation_after_loading |
665
|
|
|
|
|
|
|
# flag maybe? |
666
|
103693
|
|
|
|
|
72100
|
my @secondary_data; |
667
|
103693
|
|
|
|
|
115980
|
foreach my $callback (@addl_join_comparators) { |
668
|
|
|
|
|
|
|
# FIXME - (no, not another one...) There's no mechanism for duplicating SQL join's |
669
|
|
|
|
|
|
|
# behavior where if a row from a table joins to 2 rows in the secondary table, the |
670
|
|
|
|
|
|
|
# first table's data will be in the result set twice. |
671
|
32
|
|
|
|
|
53
|
my $secondary_db_row = $callback->($next_db_row); |
672
|
32
|
100
|
|
|
|
55
|
unless (defined $secondary_db_row) { |
673
|
|
|
|
|
|
|
# That data source has no more data, so there can be no more joins even if the |
674
|
|
|
|
|
|
|
# primary data source has more data left to read |
675
|
8
|
|
|
|
|
10
|
$db_iterator = undef; |
676
|
8
|
|
|
|
|
49
|
$primary_object_for_next_db_row = undef; |
677
|
8
|
|
|
|
|
17
|
last LOAD_AN_OBJECT; |
678
|
|
|
|
|
|
|
} |
679
|
24
|
100
|
|
|
|
40
|
unless ($secondary_db_row) { |
680
|
|
|
|
|
|
|
# It returned 0 |
681
|
|
|
|
|
|
|
# didn't join (but there is still more data we can read later)... throw this row out. |
682
|
11
|
|
|
|
|
11
|
$primary_object_for_next_db_row = undef; |
683
|
11
|
|
|
|
|
22
|
redo LOAD_AN_OBJECT; |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
# $next_db_row is a read-only value from DBI, so we need to track our additional |
686
|
|
|
|
|
|
|
# data seperately and smash them together before the object importer is called |
687
|
13
|
|
|
|
|
32
|
push(@secondary_data, @$secondary_db_row); |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
# get one or more objects from this row of results |
691
|
103674
|
|
|
|
|
81148
|
my $re_iterate = 0; |
692
|
103674
|
|
|
|
|
65532
|
my @imported; |
693
|
103674
|
|
|
|
|
159971
|
for (my $i = 0; $i < @object_fabricator_closures; $i++) { |
694
|
104126
|
|
|
|
|
90193
|
my $object_fabricator = $object_fabricator_closures[$i]; |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
# The usual case is that the query is just against one data source, and so the importer |
697
|
|
|
|
|
|
|
# callback is just given the row returned from the DB query. For multiple data sources, |
698
|
|
|
|
|
|
|
# we need to smash together the primary and all the secondary lists |
699
|
104126
|
|
|
|
|
72177
|
my $imported_object; |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
#my $object_creation_time; |
702
|
|
|
|
|
|
|
#if ($is_monitor_query) { |
703
|
|
|
|
|
|
|
# $object_creation_time = Time::HiRes::time(); |
704
|
|
|
|
|
|
|
#} |
705
|
|
|
|
|
|
|
|
706
|
104126
|
100
|
|
|
|
117352
|
if (@secondary_data) { |
707
|
26
|
|
|
|
|
107
|
$imported_object = $object_fabricator->([@$next_db_row, @secondary_data]); |
708
|
|
|
|
|
|
|
} else { |
709
|
104100
|
|
|
|
|
178594
|
$imported_object = $object_fabricator->($next_db_row); |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
#if ($is_monitor_query) { |
713
|
|
|
|
|
|
|
# $self->_log_query_for_rule($class_name, $rule, sprintf("QUERY: object fabricator took %.4f s",Time::HiRes::time() - $object_creation_time)); |
714
|
|
|
|
|
|
|
#} |
715
|
|
|
|
|
|
|
|
716
|
104113
|
100
|
100
|
|
|
296893
|
if ($imported_object and not ref($imported_object)) { |
717
|
|
|
|
|
|
|
# object requires sub-classsification in a way which involves different db data. |
718
|
60
|
|
|
|
|
73
|
$re_iterate = 1; |
719
|
|
|
|
|
|
|
} |
720
|
104113
|
|
|
|
|
102842
|
push @imported, $imported_object; |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
# If the object ID for fabricator slot $i changes, then we can apply the |
723
|
|
|
|
|
|
|
# all_params_loaded changes from iterators 0 .. $i-1 because we know we've |
724
|
|
|
|
|
|
|
# loaded all the hangoff data related to the previous object |
725
|
|
|
|
|
|
|
# remember that the last fabricator in the list is for the primary object |
726
|
104113
|
100
|
100
|
|
|
275076
|
if (defined $imported_object and ref($imported_object)) { |
727
|
103911
|
100
|
|
|
|
230032
|
if (!defined $object_ids_from_fabricators[$i]) { |
|
|
100
|
|
|
|
|
|
728
|
1312
|
|
|
|
|
3422
|
$object_ids_from_fabricators[$i] = $imported_object->id; |
729
|
|
|
|
|
|
|
} elsif ($object_ids_from_fabricators[$i] ne $imported_object->id) { |
730
|
102370
|
|
|
|
|
145468
|
for (my $j = 0; $j < $i; $j++) { |
731
|
85
|
|
|
|
|
251
|
$object_fabricators[$j]->apply_all_params_loaded; |
732
|
|
|
|
|
|
|
} |
733
|
102370
|
|
|
|
|
132089
|
$object_ids_from_fabricators[$i] = $imported_object->id; |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
} |
737
|
|
|
|
|
|
|
|
738
|
103661
|
|
|
|
|
89895
|
$primary_object_for_next_db_row = $imported[-1]; |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
# The object importer will return undef for an object if no object |
741
|
|
|
|
|
|
|
# got created for that $next_db_row, and will return a string if the object |
742
|
|
|
|
|
|
|
# needs to be subclassed before being returned. Don't put serial numbers on |
743
|
|
|
|
|
|
|
# these |
744
|
103911
|
|
|
|
|
141035
|
map { $_->{'__get_serial'} = $this_get_serial } |
745
|
103661
|
100
|
|
|
|
100434
|
grep { defined && ref } |
|
104113
|
|
|
|
|
291298
|
|
746
|
|
|
|
|
|
|
@imported; |
747
|
|
|
|
|
|
|
|
748
|
103661
|
50
|
66
|
|
|
168077
|
if ($re_iterate and defined($primary_object_for_next_db_row) and ! ref($primary_object_for_next_db_row)) { |
|
|
|
66
|
|
|
|
|
749
|
|
|
|
|
|
|
# It is possible that one or more objects go into subclasses which require more |
750
|
|
|
|
|
|
|
# data than is on the results row. For each subclass (or set of subclasses), |
751
|
|
|
|
|
|
|
# we make a more specific, subordinate iterator to delegate-to. |
752
|
|
|
|
|
|
|
|
753
|
60
|
|
|
|
|
86
|
my $subclass_name = $primary_object_for_next_db_row; |
754
|
|
|
|
|
|
|
|
755
|
60
|
|
|
|
|
194
|
my $subclass_meta = UR::Object::Type->get(class_name => $subclass_name); |
756
|
60
|
|
|
|
|
283
|
my $table_subclass = $subclass_meta->most_specific_subclass_with_table(); |
757
|
60
|
|
|
|
|
89
|
my $sub_iterator = $subordinate_iterator_for_class{$table_subclass}; |
758
|
60
|
100
|
|
|
|
121
|
unless ($sub_iterator) { |
759
|
|
|
|
|
|
|
#print "parallel iteration for loading $subclass_name under $class_name!\n"; |
760
|
21
|
|
|
|
|
150
|
my $sub_classified_rule_template = $rule_template->sub_classify($subclass_name); |
761
|
21
|
|
|
|
|
83
|
my $sub_classified_rule = $sub_classified_rule_template->get_normalized_rule_for_values(@values); |
762
|
|
|
|
|
|
|
$sub_iterator |
763
|
21
|
|
|
|
|
100
|
= $subordinate_iterator_for_class{$table_subclass} |
764
|
|
|
|
|
|
|
= $self->_create_import_iterator_for_underlying_context($sub_classified_rule,$dsx,$this_get_serial); |
765
|
|
|
|
|
|
|
} |
766
|
60
|
|
|
|
|
128
|
($primary_object_for_next_db_row) = $sub_iterator->(); |
767
|
60
|
100
|
|
|
|
141
|
if (! defined $primary_object_for_next_db_row) { |
768
|
|
|
|
|
|
|
# the newly subclassed object |
769
|
13
|
|
|
|
|
43
|
redo LOAD_AN_OBJECT; |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
} # end of handling a possible subordinate iterator delegate |
773
|
|
|
|
|
|
|
|
774
|
103648
|
100
|
|
|
|
131997
|
unless (defined $primary_object_for_next_db_row) { |
775
|
|
|
|
|
|
|
#if (!$primary_object_for_next_db_row or $rule->evaluate($primary_object_for_next_db_row)) { |
776
|
121
|
|
|
|
|
197
|
redo LOAD_AN_OBJECT; |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
103527
|
50
|
100
|
|
|
309187
|
if ( !$group_by and (ref($primary_object_for_next_db_row) ne $class_name) and (not $primary_object_for_next_db_row->isa($class_name)) ) { |
|
|
|
66
|
|
|
|
|
780
|
0
|
|
|
|
|
0
|
$primary_object_for_next_db_row = undef; |
781
|
0
|
|
|
|
|
0
|
redo LOAD_AN_OBJECT; |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
103527
|
100
|
|
|
|
119656
|
if ($by_hand_recursive_source_property) { |
785
|
17
|
|
|
|
|
62
|
my @values = grep { defined } $primary_object_for_next_db_row->$by_hand_recursive_source_property; |
|
17
|
|
|
|
|
43
|
|
786
|
17
|
|
|
|
|
25
|
push @by_hand_recursive_source_values, @values; |
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
|
789
|
103527
|
100
|
100
|
|
|
511077
|
if (! defined($next_object_to_return) |
790
|
|
|
|
|
|
|
or (Scalar::Util::refaddr($next_object_to_return) == Scalar::Util::refaddr($primary_object_for_next_db_row)) |
791
|
|
|
|
|
|
|
) { |
792
|
|
|
|
|
|
|
# The first time through the iterator, we need to buffer the object until |
793
|
|
|
|
|
|
|
# $primary_object_for_next_db_row is something different. |
794
|
1399
|
|
|
|
|
1411
|
$next_object_to_return = $primary_object_for_next_db_row; |
795
|
1399
|
|
|
|
|
1700
|
$primary_object_for_next_db_row = undef; |
796
|
1399
|
|
|
|
|
2683
|
redo LOAD_AN_OBJECT; |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
} # end of loop until we have a defined object to return |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
#foreach my $object_fabricator ( @object_fabricators ) { |
803
|
|
|
|
|
|
|
# # Don't apply all_params_loaded for primary fab until it's all done |
804
|
|
|
|
|
|
|
# next if ($object_fabricator eq $object_fabricators[-1]); |
805
|
|
|
|
|
|
|
# $object_fabricator->apply_all_params_loaded; |
806
|
|
|
|
|
|
|
#} |
807
|
|
|
|
|
|
|
|
808
|
102136
|
|
|
|
|
84558
|
my $retval = $next_object_to_return; |
809
|
102136
|
|
|
|
|
68545
|
$next_object_to_return = $primary_object_for_next_db_row; |
810
|
102136
|
|
|
|
|
165153
|
return $retval; |
811
|
2031
|
|
|
|
|
17371
|
}; |
812
|
|
|
|
|
|
|
|
813
|
2031
|
|
|
|
|
10837
|
Sub::Name::subname('UR::Context::__underlying_context_iterator(closure)__', $underlying_context_iterator); |
814
|
2031
|
|
|
|
|
9629
|
return $underlying_context_iterator; |
815
|
|
|
|
|
|
|
} |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
1; |