| 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; |