| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package UR::Context::LoadingIterator; |
|
2
|
|
|
|
|
|
|
|
|
3
|
266
|
|
|
266
|
|
1049
|
use strict; |
|
|
266
|
|
|
|
|
380
|
|
|
|
266
|
|
|
|
|
6907
|
|
|
4
|
266
|
|
|
266
|
|
921
|
use warnings; |
|
|
266
|
|
|
|
|
351
|
|
|
|
266
|
|
|
|
|
5744
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
266
|
|
|
266
|
|
909
|
use UR::Context; |
|
|
266
|
|
|
|
|
362
|
|
|
|
266
|
|
|
|
|
1280
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
266
|
|
|
266
|
|
5671
|
use List::MoreUtils qw(any); |
|
|
266
|
|
|
|
|
388
|
|
|
|
266
|
|
|
|
|
2416
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION; |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# A helper package for UR::Context to handling queries which require loading |
|
13
|
|
|
|
|
|
|
# data from outside the current context. It is responsible for collating |
|
14
|
|
|
|
|
|
|
# cached objects and incoming objects. When create_iterator() is used in |
|
15
|
|
|
|
|
|
|
# application code, this is the iterator that gets returned |
|
16
|
|
|
|
|
|
|
# |
|
17
|
|
|
|
|
|
|
# These are normal Perl objects, not UR objects, so they get regular |
|
18
|
|
|
|
|
|
|
# refcounting and scoping |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
our @CARP_NOT = qw( UR::Context ); |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# A boolean flag used in the loading iterator to control whether we need to |
|
23
|
|
|
|
|
|
|
# inject loaded objects into other loading iterators' cached lists |
|
24
|
|
|
|
|
|
|
my $is_multiple_loading_iterators = 0; |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
my %all_loading_iterators; |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# The set of objects returned by an iterator is initially determined when the |
|
30
|
|
|
|
|
|
|
# iterator is created, but the final determination of membership happens when |
|
31
|
|
|
|
|
|
|
# the object is about to be returned from the iterator's next() method. |
|
32
|
|
|
|
|
|
|
# In practice, this means that an object matches the BoolExpr at iterator |
|
33
|
|
|
|
|
|
|
# creation, and no longer matches when that object is about to be returned, |
|
34
|
|
|
|
|
|
|
# it will not be returned. |
|
35
|
|
|
|
|
|
|
# |
|
36
|
|
|
|
|
|
|
# If an object does not match the bx when the iterator is created, it will |
|
37
|
|
|
|
|
|
|
# not be returned even if it later changes to match before the iterator is |
|
38
|
|
|
|
|
|
|
# exhausted. |
|
39
|
|
|
|
|
|
|
# |
|
40
|
|
|
|
|
|
|
# If an object changes so that it's sort order changes after the iterator is |
|
41
|
|
|
|
|
|
|
# created but before it is returned by the iterator, the object will be |
|
42
|
|
|
|
|
|
|
# returned in the order it had at iterator creation time. |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# Finally, the LoadingIterator will throw an exception if an object matches |
|
45
|
|
|
|
|
|
|
# the BoolExpr at iterator creation time, but is deleted when next() is about |
|
46
|
|
|
|
|
|
|
# to return it (ie. isa UR::DeletedRef). Since DeletedRef's die any time you |
|
47
|
|
|
|
|
|
|
# try to use them, the object sorters can't sort them. Instead, we'll just |
|
48
|
|
|
|
|
|
|
# punt and throw an exception ourselves if we come across one. |
|
49
|
|
|
|
|
|
|
# |
|
50
|
|
|
|
|
|
|
# This seems like the least suprising thing to do, but there are other solutions: |
|
51
|
|
|
|
|
|
|
# 1) just plain don't return the deleted object |
|
52
|
|
|
|
|
|
|
# 2) use signal_change to register a callback which will remove objects being deleted |
|
53
|
|
|
|
|
|
|
# from all the in-process iterator @$cached lists (accomplishes the same as #1). |
|
54
|
|
|
|
|
|
|
# For completeness, this may imply that other signal_change callbacks would remove |
|
55
|
|
|
|
|
|
|
# objects that no longer match rules for in-process iterators, and that means that |
|
56
|
|
|
|
|
|
|
# next() returns things true at the time next() is called, not when the iterator |
|
57
|
|
|
|
|
|
|
# is created. |
|
58
|
|
|
|
|
|
|
# 3) Put in some additional infrastructure so we can pull out the ID of a deleted |
|
59
|
|
|
|
|
|
|
# object. That lets us call $next_object->id at the end of the closure, and return these |
|
60
|
|
|
|
|
|
|
# deleted objects back to the user. Problem being that the user then can't really |
|
61
|
|
|
|
|
|
|
# do anything with them. But it would be consistent about returning _all_ objects |
|
62
|
|
|
|
|
|
|
# that matched the rule at iterator creation time |
|
63
|
|
|
|
|
|
|
# 4) Like #3, but just always return the deleted object before any underlying_context |
|
64
|
|
|
|
|
|
|
# object, and then don't try to get its ID at the end if the iterator if it's deleted |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub _create { |
|
69
|
2023
|
|
|
2023
|
|
3884
|
my($class, $cached, $context, $normalized_rule, $data_source, $this_get_serial ) = @_; |
|
70
|
|
|
|
|
|
|
|
|
71
|
2023
|
|
|
|
|
5613
|
my $limit = $normalized_rule->template->limit; |
|
72
|
2023
|
|
|
|
|
4770
|
my $offset = $normalized_rule->template->offset; |
|
73
|
2023
|
|
|
|
|
3068
|
my $db_results_should_be_complete = 1; |
|
74
|
|
|
|
|
|
|
|
|
75
|
2023
|
100
|
100
|
|
|
14164
|
if (($offset or defined($limit)) |
|
|
|
100
|
100
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
76
|
|
|
|
|
|
|
and ( ! $data_source->does_support_limit_offset($normalized_rule) |
|
77
|
40
|
|
|
40
|
|
62
|
or any { $_->__changes__ } @$cached |
|
78
|
|
|
|
|
|
|
) |
|
79
|
|
|
|
|
|
|
) { |
|
80
|
|
|
|
|
|
|
# If there are any cached objects, then the asked-for offset may not necessarily |
|
81
|
|
|
|
|
|
|
# be the offset that applies to data in the database. And since the offset is not |
|
82
|
|
|
|
|
|
|
# meaningful, neither is the limit. Consider a query matching these objects with |
|
83
|
|
|
|
|
|
|
# limit => 2, offset => 1 |
|
84
|
|
|
|
|
|
|
# In memory: 1 2 |
|
85
|
|
|
|
|
|
|
# In DB : 3 4 5 6 |
|
86
|
|
|
|
|
|
|
# The result should be (2, 3). If we kept the -offset in the DB's SQL, we would |
|
87
|
|
|
|
|
|
|
# have missed object 3. |
|
88
|
|
|
|
|
|
|
# Similarly, if any DB rows exist as objects with changed data, then rows returnd |
|
89
|
|
|
|
|
|
|
# from the DB might not be included in the results, and the supplied -limit could |
|
90
|
|
|
|
|
|
|
# keep us from reading rows that should be returned |
|
91
|
14
|
|
|
|
|
89
|
my %filters = $normalized_rule->params_list; |
|
92
|
14
|
|
|
|
|
43
|
delete @filters{'-limit', '-offset'}; |
|
93
|
14
|
|
|
|
|
43
|
$normalized_rule = UR::BoolExpr->resolve_normalized($normalized_rule->subject_class_name, %filters); |
|
94
|
14
|
|
|
|
|
46
|
$db_results_should_be_complete = 0; |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
} elsif ($offset) { |
|
97
|
|
|
|
|
|
|
# Also apply the offset to the list of cached objects. |
|
98
|
5
|
100
|
|
|
|
19
|
if ($offset > @$cached) { |
|
99
|
2
|
|
|
|
|
5
|
@$cached = (); |
|
100
|
|
|
|
|
|
|
} else { |
|
101
|
3
|
|
|
|
|
8
|
splice(@$cached, 0, $offset); |
|
102
|
|
|
|
|
|
|
} |
|
103
|
5
|
|
|
|
|
8
|
undef($offset); # Now don't have to deal with offset below in the iterator |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
2023
|
|
|
|
|
8596
|
my $underlying_context_iterator = $context->_create_import_iterator_for_underlying_context( |
|
107
|
|
|
|
|
|
|
$normalized_rule, $data_source, $this_get_serial); |
|
108
|
|
|
|
|
|
|
|
|
109
|
2000
|
|
|
|
|
6295
|
my $is_monitor_query = $context->monitor_query; |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# These are captured by the closure... |
|
112
|
2000
|
|
|
|
|
2624
|
my($last_loaded_id, $next_obj_current_context, $next_obj_underlying_context); |
|
113
|
|
|
|
|
|
|
|
|
114
|
2000
|
|
|
|
|
5642
|
my $object_sorter = $normalized_rule->template->sorter(); |
|
115
|
|
|
|
|
|
|
|
|
116
|
2000
|
|
|
|
|
5352
|
my $bx_subject_class = $normalized_rule->subject_class_name; |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# Collection of object IDs that were read from the DB query. These objects are for-sure |
|
119
|
|
|
|
|
|
|
# not deleted, even though a cached object for it might have been turned into a ghost or |
|
120
|
|
|
|
|
|
|
# had its properties changed |
|
121
|
2000
|
|
|
|
|
2632
|
my %db_seen_ids_that_are_not_deleted; |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# Collection of object IDs that were read from the cached object list and haven't been |
|
124
|
|
|
|
|
|
|
# seen in the lsit of results from the database (yet). It could be missing from the DB |
|
125
|
|
|
|
|
|
|
# results because that row has been deleted, because the DB row still exists but has been |
|
126
|
|
|
|
|
|
|
# changed since we loaded it and now doesn't match the BoolExp, or because we're sorting |
|
127
|
|
|
|
|
|
|
# results by something other than just ID, that sorted property has been changed in the DB |
|
128
|
|
|
|
|
|
|
# and we haven't come across this row yet but will before. |
|
129
|
|
|
|
|
|
|
# |
|
130
|
|
|
|
|
|
|
# The short story is that if there is anything in this hash when the underlying context iterator |
|
131
|
|
|
|
|
|
|
# is exhausted, then the ID-ed object is really deleted, and should be an exception |
|
132
|
|
|
|
|
|
|
my %changed_objects_that_might_be_db_deleted; |
|
133
|
|
|
|
|
|
|
|
|
134
|
2000
|
|
|
|
|
2307
|
my $underlying_context_objects_count = 0; |
|
135
|
2000
|
|
|
|
|
2260
|
my $cached_objects_count = 0; |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# knowing if an object's changed properties are one of the rule's order-by |
|
138
|
|
|
|
|
|
|
# properties helps later on in the loading process of detecting deleted DB rows |
|
139
|
2000
|
|
|
|
|
2283
|
my %order_by_properties; |
|
140
|
2000
|
100
|
|
|
|
4130
|
if ($normalized_rule->template->order_by) { |
|
141
|
96
|
|
|
|
|
140
|
%order_by_properties = map { $_ => 1 } @{ $normalized_rule->template->order_by }; |
|
|
96
|
|
|
|
|
346
|
|
|
|
96
|
|
|
|
|
232
|
|
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
my $change_is_order_by_property = sub { |
|
144
|
41
|
|
|
41
|
|
190
|
foreach my $prop_name ( shift->_changed_property_names ) { |
|
145
|
43
|
100
|
|
|
|
259
|
return 1 if exists($order_by_properties{$prop_name}); |
|
146
|
|
|
|
|
|
|
} |
|
147
|
22
|
|
|
|
|
91
|
return; |
|
148
|
2000
|
|
|
|
|
8991
|
}; |
|
149
|
2000
|
|
|
|
|
5088
|
my %bx_filter_properties = map { $_ => 1 } $normalized_rule->template->_property_names; |
|
|
2995
|
|
|
|
|
7599
|
|
|
150
|
|
|
|
|
|
|
my $change_is_bx_filter_property = sub { |
|
151
|
6
|
|
|
6
|
|
15
|
foreach my $prop_name ( shift->_changed_property_names ) { |
|
152
|
6
|
50
|
|
|
|
46
|
return 1 if exists($bx_filter_properties{$prop_name}); |
|
153
|
|
|
|
|
|
|
} |
|
154
|
0
|
|
|
|
|
0
|
return; |
|
155
|
2000
|
|
|
|
|
6382
|
}; |
|
156
|
|
|
|
|
|
|
|
|
157
|
2000
|
|
|
|
|
2291
|
my $me_loading_iterator_as_string; # See note below the closure definition |
|
158
|
|
|
|
|
|
|
my $loading_iterator = sub { |
|
159
|
|
|
|
|
|
|
|
|
160
|
105297
|
100
|
100
|
105297
|
|
175660
|
return if (defined($limit) and !$limit); |
|
161
|
105279
|
|
|
|
|
63435
|
my $next_object; |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
PICK_NEXT_OBJECT_FOR_LOADING: |
|
164
|
105279
|
|
|
|
|
140660
|
while (! defined($next_object)) { |
|
165
|
105424
|
100
|
100
|
|
|
297563
|
if ($underlying_context_iterator && ! defined($next_obj_underlying_context)) { |
|
166
|
105221
|
|
|
|
|
160927
|
($next_obj_underlying_context) = $underlying_context_iterator->(1); |
|
167
|
|
|
|
|
|
|
|
|
168
|
105208
|
50
|
33
|
|
|
177552
|
$underlying_context_objects_count++ if ($is_monitor_query and defined($next_obj_underlying_context)); |
|
169
|
|
|
|
|
|
|
|
|
170
|
105208
|
100
|
|
|
|
142078
|
if (defined($next_obj_underlying_context)) { |
|
171
|
103248
|
100
|
100
|
|
|
377583
|
if ($next_obj_underlying_context->isa('UR::DeletedRef')) { |
|
|
|
100
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# This object is deleted in the current context and not yet committed |
|
173
|
|
|
|
|
|
|
# skip it and pick again |
|
174
|
1
|
|
|
|
|
124
|
$next_obj_underlying_context = undef; |
|
175
|
1
|
|
|
|
|
3
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
176
|
|
|
|
|
|
|
} elsif ($next_obj_underlying_context->__changes__ |
|
177
|
|
|
|
|
|
|
and |
|
178
|
|
|
|
|
|
|
$change_is_order_by_property->($next_obj_underlying_context) |
|
179
|
|
|
|
|
|
|
) { |
|
180
|
8
|
100
|
|
|
|
28
|
unless (delete $changed_objects_that_might_be_db_deleted{$next_obj_underlying_context->id}) { |
|
181
|
6
|
|
|
|
|
17
|
$db_seen_ids_that_are_not_deleted{$next_obj_underlying_context->id} = 1; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
8
|
|
|
|
|
16
|
$next_obj_underlying_context = undef; |
|
184
|
8
|
|
|
|
|
15
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
|
|
189
|
105402
|
100
|
|
|
|
145355
|
unless (defined $next_obj_current_context) { |
|
190
|
105219
|
|
|
|
|
111409
|
($next_obj_current_context) = shift @$cached; |
|
191
|
105219
|
50
|
33
|
|
|
159789
|
$cached_objects_count++ if ($is_monitor_query and $next_obj_current_context); |
|
192
|
|
|
|
|
|
|
} |
|
193
|
105402
|
100
|
100
|
|
|
157760
|
if (defined($next_obj_current_context) and $next_obj_current_context->isa('UR::DeletedRef')) { |
|
194
|
1
|
|
|
|
|
11
|
my $obj_to_complain_about = $next_obj_current_context; |
|
195
|
|
|
|
|
|
|
# undef it in case the user traps the exception, next time we'll pull another off the list |
|
196
|
1
|
|
|
|
|
1
|
$next_obj_current_context = undef; |
|
197
|
1
|
|
|
|
|
73
|
Carp::croak("Attempt to fetch an object which matched $normalized_rule when the iterator was created, " |
|
198
|
|
|
|
|
|
|
. "but was deleted in the meantime:\n" |
|
199
|
|
|
|
|
|
|
. Data::Dumper::Dumper($obj_to_complain_about) ); |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
|
|
202
|
105401
|
100
|
66
|
|
|
281865
|
if (!defined($next_obj_underlying_context)) { |
|
|
|
50
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
|
|
204
|
2113
|
50
|
|
|
|
3967
|
if ($is_monitor_query) { |
|
205
|
0
|
|
|
|
|
0
|
$context->_log_query_for_rule($bx_subject_class, |
|
206
|
|
|
|
|
|
|
$normalized_rule, |
|
207
|
|
|
|
|
|
|
"QUERY: loaded $underlying_context_objects_count object(s) total from underlying context."); |
|
208
|
|
|
|
|
|
|
} |
|
209
|
2113
|
|
|
|
|
2299
|
$underlying_context_iterator = undef; |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Anything left in this hash when the DB iterator is exhausted are object we expected to |
|
212
|
|
|
|
|
|
|
# see by now and must be deleted. If any of these object have changes then |
|
213
|
|
|
|
|
|
|
# the __merge below will throw an exception |
|
214
|
2113
|
100
|
|
|
|
59249
|
if ($db_results_should_be_complete) { |
|
215
|
2110
|
|
|
|
|
5292
|
foreach my $problem_obj (values(%changed_objects_that_might_be_db_deleted)) { |
|
216
|
2
|
|
|
|
|
8
|
$context->__merge_db_data_with_existing_object($bx_subject_class, $problem_obj, undef, []); |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
} |
|
221
|
|
|
|
|
|
|
elsif (defined($last_loaded_id) |
|
222
|
|
|
|
|
|
|
and |
|
223
|
|
|
|
|
|
|
$last_loaded_id eq $next_obj_underlying_context->id) |
|
224
|
|
|
|
|
|
|
{ |
|
225
|
|
|
|
|
|
|
# during a get() with -hints or is_many+is_optional (ie. something with an |
|
226
|
|
|
|
|
|
|
# outer join), it's possible that the join can produce the same main object |
|
227
|
|
|
|
|
|
|
# as it's chewing through the (possibly) multiple objects joined to it. |
|
228
|
|
|
|
|
|
|
# Since the objects will be returned sorted by their IDs, we only have to |
|
229
|
|
|
|
|
|
|
# remember the last one we saw |
|
230
|
|
|
|
|
|
|
# FIXME - is this still true now that the underlying context iterator and/or |
|
231
|
|
|
|
|
|
|
# object fabricator hold off on returning any objects until all the related |
|
232
|
|
|
|
|
|
|
# joined data bas been loaded? |
|
233
|
0
|
|
|
|
|
0
|
$next_obj_underlying_context = undef; |
|
234
|
0
|
|
|
|
|
0
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
235
|
|
|
|
|
|
|
} |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# decide which pending object to return next |
|
238
|
|
|
|
|
|
|
# both the cached list and the list from the database are sorted separately but with |
|
239
|
|
|
|
|
|
|
# equivalent algorithms (we hope). |
|
240
|
|
|
|
|
|
|
# |
|
241
|
|
|
|
|
|
|
# we're collating these into one return stream here |
|
242
|
|
|
|
|
|
|
|
|
243
|
105399
|
|
|
|
|
104557
|
my $comparison_result = undef; |
|
244
|
105399
|
100
|
100
|
|
|
274196
|
if (defined($next_obj_underlying_context) && defined($next_obj_current_context)) { |
|
245
|
919
|
|
|
|
|
2306
|
$comparison_result = $object_sorter->($next_obj_underlying_context, $next_obj_current_context); |
|
246
|
|
|
|
|
|
|
} |
|
247
|
|
|
|
|
|
|
|
|
248
|
105399
|
|
|
|
|
68714
|
my $next_obj_underlying_context_id; |
|
249
|
105399
|
100
|
|
|
|
195078
|
$next_obj_underlying_context_id = $next_obj_underlying_context->id if (defined $next_obj_underlying_context); |
|
250
|
105399
|
|
|
|
|
84496
|
my $next_obj_current_context_id; |
|
251
|
105399
|
100
|
|
|
|
127773
|
$next_obj_current_context_id = $next_obj_current_context->id if (defined $next_obj_current_context); |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# This if() section is for when the in-memory and DB iterators return the same |
|
254
|
|
|
|
|
|
|
# object at the same time. |
|
255
|
105399
|
100
|
100
|
|
|
506567
|
if ( |
|
|
|
100
|
100
|
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
|
50
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
256
|
|
|
|
|
|
|
defined($next_obj_underlying_context) |
|
257
|
|
|
|
|
|
|
and defined($next_obj_current_context) |
|
258
|
|
|
|
|
|
|
and $comparison_result == 0 # $next_obj_underlying_context->id eq $next_obj_current_context->id |
|
259
|
|
|
|
|
|
|
) { |
|
260
|
|
|
|
|
|
|
# Both objects sort the same. Since the ID properties are always last in the sort order list, |
|
261
|
|
|
|
|
|
|
# this means both objects must be the same object. |
|
262
|
684
|
50
|
|
|
|
1217
|
$context->_log_query_for_rule($bx_subject_class, $normalized_rule, "QUERY: loaded object was already cached") if ($is_monitor_query); |
|
263
|
684
|
|
|
|
|
672
|
$next_object = $next_obj_current_context; |
|
264
|
684
|
|
|
|
|
636
|
$next_obj_current_context = undef; |
|
265
|
684
|
|
|
|
|
657
|
$next_obj_underlying_context = undef; |
|
266
|
|
|
|
|
|
|
} |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
# This if() section is for when the DB iterator's object sorts first |
|
269
|
|
|
|
|
|
|
elsif ( |
|
270
|
|
|
|
|
|
|
defined($next_obj_underlying_context) |
|
271
|
|
|
|
|
|
|
and ( |
|
272
|
|
|
|
|
|
|
(!defined($next_obj_current_context)) |
|
273
|
|
|
|
|
|
|
or |
|
274
|
|
|
|
|
|
|
($comparison_result < 0) # ($next_obj_underlying_context->id le $next_obj_current_context->id) |
|
275
|
|
|
|
|
|
|
) |
|
276
|
|
|
|
|
|
|
) { |
|
277
|
|
|
|
|
|
|
# db object sorts first |
|
278
|
|
|
|
|
|
|
# If we deleted it from memory the DB would not have given it back. |
|
279
|
|
|
|
|
|
|
# So it either failed to match the BX now, or one of the order-by parameters changed |
|
280
|
102553
|
100
|
|
|
|
128785
|
if ($next_obj_underlying_context->__changes__) { |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# See if one of the changes is an order-by property |
|
283
|
3
|
50
|
|
|
|
8
|
if ($change_is_order_by_property->($next_obj_underlying_context)) { |
|
|
|
50
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# If the object has changes, and one of the changes is one of the |
|
285
|
|
|
|
|
|
|
# order-by properties, then the object will: |
|
286
|
|
|
|
|
|
|
# 1) Already have appeared as $next_obj_current_context. |
|
287
|
|
|
|
|
|
|
# it will be in $changed_objects_that_might_be_db_deleted - remove it from that list |
|
288
|
|
|
|
|
|
|
# 2) Will appear later as $next_obj_current_context. |
|
289
|
|
|
|
|
|
|
# Mark here that it's not deleted |
|
290
|
0
|
0
|
|
|
|
0
|
unless (delete $changed_objects_that_might_be_db_deleted{$next_obj_underlying_context_id}) { |
|
291
|
0
|
|
|
|
|
0
|
$db_seen_ids_that_are_not_deleted{$next_obj_underlying_context_id} = 1; |
|
292
|
|
|
|
|
|
|
} |
|
293
|
|
|
|
|
|
|
} elsif ($change_is_bx_filter_property->($next_obj_underlying_context)) { |
|
294
|
|
|
|
|
|
|
# If the object has any changes, then it will appear in the cached object list in |
|
295
|
|
|
|
|
|
|
# $next_object_current_context at the appropriate time. For the case where the |
|
296
|
|
|
|
|
|
|
# object no longer matches the BoolExpr, then the appropriate time is never. |
|
297
|
|
|
|
|
|
|
# Discard this object from the DB and pick again |
|
298
|
3
|
|
|
|
|
4
|
$next_obj_underlying_context = undef; |
|
299
|
3
|
|
|
|
|
7
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
300
|
|
|
|
|
|
|
} else { |
|
301
|
|
|
|
|
|
|
# some other kind of change? |
|
302
|
0
|
|
|
|
|
0
|
$next_object = $next_obj_underlying_context; |
|
303
|
0
|
|
|
|
|
0
|
$next_obj_underlying_context = undef; |
|
304
|
0
|
|
|
|
|
0
|
next PICK_NEXT_OBJECT_FOR_LOADING; |
|
305
|
|
|
|
|
|
|
} |
|
306
|
|
|
|
|
|
|
} else { |
|
307
|
|
|
|
|
|
|
# If the object has no changes, it must be something newly brought into the system. |
|
308
|
102550
|
|
|
|
|
68596
|
$next_object = $next_obj_underlying_context; |
|
309
|
102550
|
|
|
|
|
70134
|
$next_obj_underlying_context = undef; |
|
310
|
102550
|
|
|
|
|
99456
|
next PICK_NEXT_OBJECT_FOR_LOADING; |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# This if() section is for when the in-memory iterator's object sorts first |
|
315
|
|
|
|
|
|
|
elsif ( |
|
316
|
|
|
|
|
|
|
defined($next_obj_current_context) |
|
317
|
|
|
|
|
|
|
and ( |
|
318
|
|
|
|
|
|
|
(!defined($next_obj_underlying_context)) |
|
319
|
|
|
|
|
|
|
or |
|
320
|
|
|
|
|
|
|
($comparison_result > 0) # ($next_obj_underlying_context->id ge $next_obj_current_context->id) |
|
321
|
|
|
|
|
|
|
) |
|
322
|
|
|
|
|
|
|
) { |
|
323
|
|
|
|
|
|
|
# The cached object sorts first |
|
324
|
|
|
|
|
|
|
# Either it was changed in memory, in the DB or both |
|
325
|
|
|
|
|
|
|
# In addition, the change could have been to an order-by property, one of the |
|
326
|
|
|
|
|
|
|
# properties in the BoolExpr, or both |
|
327
|
|
|
|
|
|
|
|
|
328
|
209
|
100
|
100
|
|
|
2219
|
if (! $next_obj_current_context->isa('UR::Object::Set') # Sets aren't really from the underlying context |
|
329
|
|
|
|
|
|
|
and |
|
330
|
|
|
|
|
|
|
$context->object_exists_in_underlying_context($next_obj_current_context) |
|
331
|
|
|
|
|
|
|
) { |
|
332
|
84
|
100
|
|
|
|
184
|
if ($next_obj_current_context->__changes__) { |
|
333
|
14
|
100
|
|
|
|
34
|
if ($change_is_order_by_property->($next_obj_current_context)) { |
|
|
|
50
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# This object is expected to exist in the underlying context, has changes, and at |
|
336
|
|
|
|
|
|
|
# least one of those changes is to an order-by property |
|
337
|
|
|
|
|
|
|
# |
|
338
|
|
|
|
|
|
|
# if it's in %db_seen_ids_that_are_not_deleted, then it was seen earlier |
|
339
|
|
|
|
|
|
|
# from the DB, and can now be removed from that hash. |
|
340
|
11
|
100
|
|
|
|
32
|
unless (delete $db_seen_ids_that_are_not_deleted{$next_obj_current_context_id}) { |
|
341
|
|
|
|
|
|
|
# If not in that list, then add it to the list of things we might see later |
|
342
|
|
|
|
|
|
|
# in the DB iterator. If we don't see it by the end if the iterator, it |
|
343
|
|
|
|
|
|
|
# must have been deleted from the DB. At that time, we'll throw an exception. |
|
344
|
|
|
|
|
|
|
# It's later than we'd like, since the caller has already gotten ahold of the |
|
345
|
|
|
|
|
|
|
# object, but better late than never. The alternative is to do an id-only |
|
346
|
|
|
|
|
|
|
# query right now, but that would be inefficient. |
|
347
|
|
|
|
|
|
|
# |
|
348
|
|
|
|
|
|
|
# We could avoid storing this if we could verify that the db_committed/db_saved_uncommitted |
|
349
|
|
|
|
|
|
|
# values did NOT match the BoolExpr, but this will suffice for now. |
|
350
|
8
|
|
|
|
|
17
|
$changed_objects_that_might_be_db_deleted{$next_obj_current_context_id} = $next_obj_current_context; |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
# In any case, return the cached object. |
|
353
|
11
|
|
|
|
|
15
|
$next_object = $next_obj_current_context; |
|
354
|
11
|
|
|
|
|
16
|
$next_obj_current_context = undef; |
|
355
|
11
|
|
|
|
|
20
|
next PICK_NEXT_OBJECT_FOR_LOADING; |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
elsif ($change_is_bx_filter_property->($next_obj_current_context)) { |
|
358
|
|
|
|
|
|
|
# The change was that the object originally did not the filter, but since being |
|
359
|
|
|
|
|
|
|
# loaded it's been changed so it now matches the filter. The DB iterator isn't |
|
360
|
|
|
|
|
|
|
# returning the object since the DB's copy doesn't match the filter. |
|
361
|
3
|
|
|
|
|
3
|
delete $db_seen_ids_that_are_not_deleted{$next_obj_current_context_id}; |
|
362
|
3
|
|
|
|
|
3
|
$next_object = $next_obj_current_context; |
|
363
|
3
|
|
|
|
|
1
|
$next_obj_current_context = undef; |
|
364
|
3
|
|
|
|
|
6
|
next PICK_NEXT_OBJECT_FOR_LOADING; |
|
365
|
|
|
|
|
|
|
} |
|
366
|
|
|
|
|
|
|
else { |
|
367
|
|
|
|
|
|
|
# The change is not an order-by property. This object must have been deleted |
|
368
|
|
|
|
|
|
|
# from the DB. The call to __merge below will throw an exception |
|
369
|
0
|
|
|
|
|
0
|
$context->__merge_db_data_with_existing_object($bx_subject_class, $next_obj_current_context, undef, []); |
|
370
|
0
|
|
|
|
|
0
|
$next_obj_current_context = undef; |
|
371
|
0
|
|
|
|
|
0
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
372
|
|
|
|
|
|
|
} |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
} else { |
|
375
|
|
|
|
|
|
|
# This cached object has no changes, so the database must have changed. |
|
376
|
|
|
|
|
|
|
# It could be deleted, no longer match the BoolExpr, or have changes in an order-by property |
|
377
|
|
|
|
|
|
|
|
|
378
|
70
|
50
|
|
|
|
242
|
if (delete $db_seen_ids_that_are_not_deleted{$next_obj_current_context_id}) { |
|
|
|
100
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# We saw this already on the DB iterator. It's not deleted. Go ahead and return it |
|
380
|
0
|
|
|
|
|
0
|
$next_object = $next_obj_current_context; |
|
381
|
0
|
|
|
|
|
0
|
$next_obj_current_context = undef; |
|
382
|
0
|
|
|
|
|
0
|
next PICK_NEXT_OBJECT_FOR_LOADING; |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
} |
|
385
|
|
|
|
|
|
|
elsif ($normalized_rule->is_id_only) { |
|
386
|
|
|
|
|
|
|
# If the query is id-only, and we didn't see the DB object at the same time, then |
|
387
|
|
|
|
|
|
|
# the DB row must have been deleted. Changing the PK columns in the DB are logically |
|
388
|
|
|
|
|
|
|
# the same as deleting the old object and creating/defineing a new one in UR. |
|
389
|
|
|
|
|
|
|
# |
|
390
|
|
|
|
|
|
|
# The __merge will delete the cached object, then pick again |
|
391
|
21
|
|
|
|
|
98
|
$context->__merge_db_data_with_existing_object($bx_subject_class, $next_obj_current_context, undef, []); |
|
392
|
21
|
|
|
|
|
28
|
$next_obj_current_context = undef; |
|
393
|
21
|
|
|
|
|
44
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
} else { |
|
396
|
|
|
|
|
|
|
# Force an ID-only query to the underying context |
|
397
|
49
|
|
|
|
|
172
|
my $requery_obj = $context->reload($bx_subject_class, id => $next_obj_current_context_id); |
|
398
|
49
|
100
|
|
|
|
102
|
if ($requery_obj) { |
|
399
|
|
|
|
|
|
|
# In any case, the DB iterator will pull it up at the appropriate time, |
|
400
|
|
|
|
|
|
|
# and since the object has no changes, it will be returned to the caller then. |
|
401
|
|
|
|
|
|
|
# Discard this in-memory object and pick again |
|
402
|
28
|
|
|
|
|
27
|
$next_obj_current_context = undef; |
|
403
|
28
|
|
|
|
|
48
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
404
|
|
|
|
|
|
|
} else { |
|
405
|
|
|
|
|
|
|
# We've now confirmed that the object in the DB is really gone |
|
406
|
|
|
|
|
|
|
# NOTE: the reload() has already performed the __merge (implying deletion) |
|
407
|
|
|
|
|
|
|
# in the above branch "elsif ($normalized_rule->is_id_only)" so we don't need |
|
408
|
|
|
|
|
|
|
# to __merge/delete it here |
|
409
|
21
|
|
|
|
|
23
|
$next_obj_current_context = undef; |
|
410
|
21
|
|
|
|
|
49
|
redo PICK_NEXT_OBJECT_FOR_LOADING; |
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
} else { |
|
415
|
|
|
|
|
|
|
# The object does not exist in the underlying context. It must be |
|
416
|
|
|
|
|
|
|
# a newly created object. |
|
417
|
125
|
|
|
|
|
178
|
$next_object = $next_obj_current_context; |
|
418
|
125
|
|
|
|
|
151
|
$next_obj_current_context = undef; |
|
419
|
125
|
|
|
|
|
244
|
next PICK_NEXT_OBJECT_FOR_LOADING; |
|
420
|
|
|
|
|
|
|
} |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
} elsif (!defined($next_obj_current_context) |
|
423
|
|
|
|
|
|
|
and |
|
424
|
|
|
|
|
|
|
!defined($next_obj_underlying_context) |
|
425
|
|
|
|
|
|
|
) { |
|
426
|
|
|
|
|
|
|
# Both iterators are exhausted. Bail out |
|
427
|
1953
|
|
|
|
|
2361
|
$next_object = undef; |
|
428
|
1953
|
|
|
|
|
2233
|
$last_loaded_id = undef; |
|
429
|
1953
|
|
|
|
|
3260
|
last PICK_NEXT_OBJECT_FOR_LOADING; |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
} else { |
|
432
|
|
|
|
|
|
|
# Couldn't decide which to pick next? Something has gone horribly wrong. |
|
433
|
|
|
|
|
|
|
# We're using other vars to hold the objects and setting |
|
434
|
|
|
|
|
|
|
# $next_obj_current_context/$next_obj_underlying_context to undef so if |
|
435
|
|
|
|
|
|
|
# the caller is trapping exceptions, this iterator will pick new objects next time |
|
436
|
0
|
|
|
|
|
0
|
my $current_problem_obj = $next_obj_current_context; |
|
437
|
0
|
|
|
|
|
0
|
my $underlying_problem_obj = $next_obj_underlying_context; |
|
438
|
0
|
|
|
|
|
0
|
$next_obj_current_context = undef; |
|
439
|
0
|
|
|
|
|
0
|
$next_obj_underlying_context = undef; |
|
440
|
0
|
|
|
|
|
0
|
$next_object = undef; |
|
441
|
0
|
|
|
|
|
0
|
Carp::croak("Loading iterator internal error. Could not pick a next object for loading.\n" |
|
442
|
|
|
|
|
|
|
. "Next object underlying context: " . Data::Dumper::Dumper($underlying_problem_obj) |
|
443
|
|
|
|
|
|
|
. "\nNext object current context: ". Data::Dumper::Dumper($current_problem_obj)); |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
} |
|
446
|
|
|
|
|
|
|
|
|
447
|
684
|
50
|
|
|
|
1426
|
return unless defined $next_object; |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# end while ! $next_object |
|
450
|
|
|
|
|
|
|
} continue { |
|
451
|
103373
|
100
|
66
|
|
|
384405
|
if (defined($next_object) and defined($offset) and $offset) { |
|
|
|
|
100
|
|
|
|
|
|
452
|
63
|
|
|
|
|
58
|
$offset--; |
|
453
|
63
|
|
|
|
|
93
|
$next_object = undef; |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
} |
|
456
|
|
|
|
|
|
|
|
|
457
|
105263
|
100
|
|
|
|
189940
|
$last_loaded_id = $next_object->id if (defined $next_object); |
|
458
|
|
|
|
|
|
|
|
|
459
|
105263
|
100
|
|
|
|
136030
|
$limit-- if defined $limit; |
|
460
|
|
|
|
|
|
|
|
|
461
|
105263
|
|
|
|
|
184486
|
return $next_object; |
|
462
|
2000
|
|
|
|
|
14111
|
}; # end of the closure |
|
463
|
|
|
|
|
|
|
|
|
464
|
2000
|
|
|
|
|
4896
|
bless $loading_iterator, $class; |
|
465
|
2000
|
|
|
|
|
11280
|
Sub::Name::subname($class . '__loading_iterator_closure__', $loading_iterator); |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
# Inside the closure, it needs to know its own address, but without holding a real reference |
|
468
|
|
|
|
|
|
|
# to itself - otherwise the closure would never go out of scope, the destructor would never |
|
469
|
|
|
|
|
|
|
# get called, and the list of outstanding loaders would never get pruned. This way, the closure |
|
470
|
|
|
|
|
|
|
# holds a reference to the string version of its address, which is the only thing it really |
|
471
|
|
|
|
|
|
|
# needed anyway |
|
472
|
2000
|
|
|
|
|
4961
|
$me_loading_iterator_as_string = $loading_iterator . ''; |
|
473
|
|
|
|
|
|
|
|
|
474
|
2000
|
|
|
|
|
8084
|
$all_loading_iterators{$me_loading_iterator_as_string} = |
|
475
|
|
|
|
|
|
|
[ $me_loading_iterator_as_string, |
|
476
|
|
|
|
|
|
|
$normalized_rule, |
|
477
|
|
|
|
|
|
|
$object_sorter, |
|
478
|
|
|
|
|
|
|
$cached, |
|
479
|
|
|
|
|
|
|
\$underlying_context_objects_count, |
|
480
|
|
|
|
|
|
|
\$cached_objects_count, |
|
481
|
|
|
|
|
|
|
$context, |
|
482
|
|
|
|
|
|
|
]; |
|
483
|
|
|
|
|
|
|
|
|
484
|
2000
|
100
|
|
|
|
5535
|
$is_multiple_loading_iterators = 1 if (keys(%all_loading_iterators) > 1); |
|
485
|
|
|
|
|
|
|
|
|
486
|
2000
|
|
|
|
|
8691
|
return $loading_iterator; |
|
487
|
|
|
|
|
|
|
} # end _create() |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
sub DESTROY { |
|
492
|
2000
|
|
|
2000
|
|
9143
|
my $self = shift; |
|
493
|
|
|
|
|
|
|
|
|
494
|
2000
|
|
|
|
|
4293
|
my $iter_data = $all_loading_iterators{$self}; |
|
495
|
2000
|
50
|
|
|
|
5493
|
if ($iter_data->[0] eq $self) { |
|
496
|
|
|
|
|
|
|
# that's me! |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
# Items in the listref are: $loading_iterator_string, $rule, $object_sorter, $cached, |
|
499
|
|
|
|
|
|
|
# \$underlying_context_objects_count, \$cached_objects_count, $context |
|
500
|
|
|
|
|
|
|
|
|
501
|
2000
|
|
|
|
|
2784
|
my $context = $iter_data->[6]; |
|
502
|
2000
|
50
|
33
|
|
|
8516
|
if ($context and $context->monitor_query) { |
|
503
|
0
|
|
|
|
|
0
|
my $rule = $iter_data->[1]; |
|
504
|
0
|
|
|
|
|
0
|
my $count = ${$iter_data->[4]} + ${$iter_data->[5]}; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
505
|
0
|
|
|
|
|
0
|
$context->_log_query_for_rule($rule->subject_class_name, $rule, "QUERY: Query complete after returning $count object(s) for rule $rule."); |
|
506
|
0
|
|
|
|
|
0
|
$context->_log_done_elapsed_time_for_rule($rule); |
|
507
|
|
|
|
|
|
|
} |
|
508
|
2000
|
|
|
|
|
4233
|
delete $all_loading_iterators{$self}; |
|
509
|
2000
|
100
|
|
|
|
68697
|
$is_multiple_loading_iterators = 0 if (keys(%all_loading_iterators) < 2); |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
} else { |
|
512
|
0
|
|
|
|
|
0
|
Carp::carp('A loading iterator went out of scope, but could not be found in the registered list of iterators'); |
|
513
|
|
|
|
|
|
|
} |
|
514
|
|
|
|
|
|
|
} |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
# Used by the loading itertor to inject a newly loaded object into another |
|
518
|
|
|
|
|
|
|
# loading iterator's @$cached list. This is to handle the case where the user creates |
|
519
|
|
|
|
|
|
|
# an iterator which will load objects from the DB. Before all the data from that |
|
520
|
|
|
|
|
|
|
# iterator is read, another get() or iterator is created that covers (some of) the same |
|
521
|
|
|
|
|
|
|
# objects which get pulled into the object cache, and the second request is run to |
|
522
|
|
|
|
|
|
|
# completion. Since the underlying context iterator has been changed to never return |
|
523
|
|
|
|
|
|
|
# objects currently cached, the first iterator would have incorrectly skipped ome objects that |
|
524
|
|
|
|
|
|
|
# were not loaded when the first iterator was created, but later got loaded by the second. |
|
525
|
|
|
|
|
|
|
sub _inject_object_into_other_loading_iterators { |
|
526
|
0
|
|
|
0
|
|
0
|
my($self, $new_object, $iterator_to_skip) = @_; |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
ITERATOR: |
|
529
|
0
|
|
|
|
|
0
|
foreach my $iter_name ( keys %all_loading_iterators ) { |
|
530
|
0
|
0
|
|
|
|
0
|
next if $iter_name eq $iterator_to_skip; # That's me! Don't insert into our own @$cached this way |
|
531
|
|
|
|
|
|
|
my($loading_iterator, $rule, $object_sorter, $cached) |
|
532
|
0
|
|
|
|
|
0
|
= @{$all_loading_iterators{$iter_name}}; |
|
|
0
|
|
|
|
|
0
|
|
|
533
|
0
|
0
|
|
|
|
0
|
if ($rule->evaluate($new_object)) { |
|
534
|
|
|
|
|
|
|
|
|
535
|
0
|
|
|
|
|
0
|
my $cached_list_len = @$cached; |
|
536
|
0
|
|
|
|
|
0
|
for(my $i = 0; $i < $cached_list_len; $i++) { |
|
537
|
0
|
|
|
|
|
0
|
my $cached_object = $cached->[$i]; |
|
538
|
0
|
0
|
|
|
|
0
|
next if $cached_object->isa('UR::DeletedRef'); |
|
539
|
|
|
|
|
|
|
|
|
540
|
0
|
|
|
|
|
0
|
my $comparison = $object_sorter->($new_object, $cached_object); |
|
541
|
|
|
|
|
|
|
|
|
542
|
0
|
0
|
|
|
|
0
|
if ($comparison < 0) { |
|
|
|
0
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
# The new object sorts sooner than this one. Insert it into the list |
|
544
|
0
|
|
|
|
|
0
|
splice(@$cached, $i, 0, $new_object); |
|
545
|
0
|
|
|
|
|
0
|
next ITERATOR; |
|
546
|
|
|
|
|
|
|
} elsif ($comparison == 0) { |
|
547
|
|
|
|
|
|
|
# This object is already in the list |
|
548
|
0
|
|
|
|
|
0
|
next ITERATOR; |
|
549
|
|
|
|
|
|
|
} |
|
550
|
|
|
|
|
|
|
} |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
# It must go at the end... |
|
553
|
0
|
|
|
|
|
0
|
push @$cached, $new_object; |
|
554
|
|
|
|
|
|
|
} |
|
555
|
|
|
|
|
|
|
} # end foreach |
|
556
|
|
|
|
|
|
|
} |
|
557
|
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# Reverse of _inject_object_into_other_loading_iterators(). Used when one iterator detects that |
|
560
|
|
|
|
|
|
|
# a previously loaded object no longer exists in the underlying context/datasource |
|
561
|
|
|
|
|
|
|
sub _remove_object_from_other_loading_iterators { |
|
562
|
21
|
|
|
21
|
|
31
|
my($self, $disappearing_object, $iterator_to_skip) = @_; |
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
ITERATOR: |
|
565
|
21
|
|
|
|
|
57
|
foreach my $iter_name ( keys %all_loading_iterators ) { |
|
566
|
42
|
50
|
33
|
|
|
102
|
next if(! defined $iterator_to_skip or ($iter_name eq $iterator_to_skip)); # That's me! Don't remove into our own @$cached this way |
|
567
|
|
|
|
|
|
|
my($loading_iterator, $rule, $object_sorter, $cached) |
|
568
|
0
|
|
|
|
|
|
= @{$all_loading_iterators{$iter_name}}; |
|
|
0
|
|
|
|
|
|
|
|
569
|
0
|
0
|
0
|
|
|
|
next if (defined($iterator_to_skip) |
|
570
|
|
|
|
|
|
|
and $loading_iterator eq $iterator_to_skip); # That's me! Don't insert into our own @$cached this way |
|
571
|
0
|
0
|
|
|
|
|
if ($rule->evaluate($disappearing_object)) { |
|
572
|
|
|
|
|
|
|
|
|
573
|
0
|
|
|
|
|
|
my $cached_list_len = @$cached; |
|
574
|
0
|
|
|
|
|
|
for(my $i = 0; $i < $cached_list_len; $i++) { |
|
575
|
0
|
|
|
|
|
|
my $cached_object = $cached->[$i]; |
|
576
|
0
|
0
|
|
|
|
|
next if $cached_object->isa('UR::DeletedRef'); |
|
577
|
|
|
|
|
|
|
|
|
578
|
0
|
|
|
|
|
|
my $comparison = $object_sorter->($disappearing_object, $cached_object); |
|
579
|
|
|
|
|
|
|
|
|
580
|
0
|
0
|
|
|
|
|
if ($comparison == 0) { |
|
|
|
0
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# That's the one, remove it from the list |
|
582
|
0
|
|
|
|
|
|
splice(@$cached, $i, 1); |
|
583
|
0
|
|
|
|
|
|
next ITERATOR; |
|
584
|
|
|
|
|
|
|
} elsif ($comparison < 0) { |
|
585
|
|
|
|
|
|
|
# past the point where we expect to find this object |
|
586
|
0
|
|
|
|
|
|
next ITERATOR; |
|
587
|
|
|
|
|
|
|
} |
|
588
|
|
|
|
|
|
|
} |
|
589
|
|
|
|
|
|
|
} |
|
590
|
|
|
|
|
|
|
} # end foreach |
|
591
|
|
|
|
|
|
|
} |
|
592
|
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# Returns true if any of the object's changed properites are keys |
|
595
|
|
|
|
|
|
|
# in the passed-in hashref. Used by the Loading Iterator to find out if |
|
596
|
|
|
|
|
|
|
# a change is one of the order-by properties of a bx |
|
597
|
|
|
|
|
|
|
sub _changed_property_in_hash { |
|
598
|
0
|
|
|
0
|
|
|
my($self,$object,$hash) = @_; |
|
599
|
|
|
|
|
|
|
|
|
600
|
0
|
|
|
|
|
|
foreach my $prop_name ( $object->_changed_property_names ) { |
|
601
|
0
|
0
|
|
|
|
|
return 1 if (exists $hash->{$prop_name}); |
|
602
|
|
|
|
|
|
|
} |
|
603
|
0
|
|
|
|
|
|
return; |
|
604
|
|
|
|
|
|
|
} |
|
605
|
|
|
|
|
|
|
1; |
|
606
|
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
|