File Coverage

lib/UR/Context/ObjectFabricator.pm
Criterion Covered Total %
statement 419 486 86.2
branch 137 178 76.9
condition 62 84 73.8
subroutine 20 23 86.9
pod 6 11 54.5
total 644 782 82.3


line stmt bran cond sub pod time code
1             package UR::Context::ObjectFabricator;
2              
3 266     266   1070 use strict;
  266         375  
  266         6775  
4 266     266   909 use warnings;
  266         321  
  266         5406  
5              
6 266     266   896 use Scalar::Util;
  266         338  
  266         7215  
7 266     266   884 use UR::Context;
  266         327  
  266         2195  
8              
9             our $VERSION = "0.46"; # UR $VERSION;
10              
11             # A helper package for UR::Context to keep track of the subrefs used
12             # to create objects from database data
13             # These are normal Perl objects, not UR objects, so they get
14             # regular refcounting and scoping
15              
16             our @CARP_NOT = qw( UR::Context );
17              
18             my %all_object_fabricators;
19              
20             sub _create {
21 2205     2205   3080 my $class = shift;
22              
23 2205         8708 my %params = @_;
24              
25 2205 50 33     13144 unless ($params{'fabricator'} and ref($params{'fabricator'}) eq 'CODE') {
26 0         0 Carp::croak("UR::Context::ObjectFabricator::create requires a subroutine ref for the 'fabricator' parameter");
27             }
28              
29 2205 50 33     22334 unless ($params{'context'} and ref($params{'context'}) and $params{'context'}->isa('UR::Context')) {
      33        
30 0         0 Carp::croak("UR::Context::ObjectFabricator::create requires a UR::Context object for the 'context' parameter");
31             }
32              
33 2205         4514 my $self = bless {}, $class;
34              
35 2205         5197 $self->{'fabricator'} = $params{'fabricator'};
36 2205         3825 $self->{'context'} = $params{'context'};
37              
38 2205   100     5549 $self->{'all_params_loaded'} = $params{'all_params_loaded'} || {};
39 2205   100     5267 $self->{'in_clause_values'} = $params{'in_clause_values'} || {};
40              
41 2205         5394 $all_object_fabricators{$self} = $self;
42 2205         6695 Scalar::Util::weaken($all_object_fabricators{$self});
43              
44 2205         4975 return $self;
45             }
46              
47              
48             sub create_for_loading_template {
49 2178     2178 1 4495 my($fab_class, $context, $loading_template, $query_plan, $rule, $rule_template, $values, $dsx) = @_;
50 2178         4546 my @values = @$values;
51              
52 2178         3578 my $class_name = $loading_template->{final_class_name};
53             #$class_name or Carp::croak("No final_class_name in loading template?");
54 2178 50       4937 unless ($class_name) {
55             #Carp::carp("No final_class_name in loading template for rule $rule");
56 0         0 return; # This join doesn't result in an object? - i think this happens when you do a get() with -hints
57             }
58              
59 2178         7711 my $class_meta = $class_name->__meta__;
60 2178         7525 my $class_data = $dsx->_get_class_data_for_loading($class_meta);
61 2178         3556 my $class = $class_name;
62              
63 2178         3834 my $ghost_class = $class_data->{ghost_class};
64 2178         2930 my $sub_classification_meta_class_name = $class_data->{sub_classification_meta_class_name};
65 2178         2802 my $subclassify_by = $class_data->{subclassify_by};
66 2178         2563 my $sub_classification_method_name = $class_data->{sub_classification_method_name};
67              
68             # FIXME, right now, we don't have a rule template for joined entities...
69              
70 2178         3858 my $rule_template_id = $query_plan->{rule_template_id};
71 2178         3330 my $rule_template_without_recursion_desc = $query_plan->{rule_template_without_recursion_desc};
72 2178         3471 my $rule_template_id_without_recursion_desc = $query_plan->{rule_template_id_without_recursion_desc};
73 2178         3713 my $rule_matches_all = $query_plan->{rule_matches_all};
74 2178         2996 my $rule_template_is_id_only = $query_plan->{rule_template_is_id_only};
75 2178         2927 my $rule_specifies_id = $query_plan->{rule_specifies_id};
76 2178         2862 my $rule_template_specifies_value_for_subtype = $query_plan->{rule_template_specifies_value_for_subtype};
77              
78 2178         2742 my $recursion_desc = $query_plan->{recursion_desc};
79 2178         2793 my $recurse_property_on_this_row = $query_plan->{recurse_property_on_this_row};
80 2178         2666 my $recurse_property_referencing_other_rows = $query_plan->{recurse_property_referencing_other_rows};
81              
82 2178         2666 my $needs_further_boolexpr_evaluation_after_loading = $query_plan->{'needs_further_boolexpr_evaluation_after_loading'};
83              
84 2178         6269 my $rule_id = $rule->id;
85 2178         6946 my $rule_without_recursion_desc = $rule_template_without_recursion_desc->get_rule_for_values(@values);
86              
87 2178         2464 my $loading_base_object;
88 2178 100       6500 if ($loading_template == $query_plan->{loading_templates}[0]) {
89 2004         3068 $loading_base_object = 1;
90             }
91             else {
92 174         252 $loading_base_object = 0;
93 174         232 $needs_further_boolexpr_evaluation_after_loading = 0;
94             }
95              
96 2178         2852 my %subclass_is_safe_for_re_bless;
97             my %subclass_for_subtype_name;
98 0         0 my %recurse_property_value_found;
99              
100 2178         2576 my @property_names = @{ $loading_template->{property_names} };
  2178         7022  
101 2178         2664 my @id_property_names = @{ $loading_template->{id_property_names} };
  2178         5271  
102 2178         2550 my @column_positions = @{ $loading_template->{column_positions} };
  2178         5126  
103 2178         2478 my @id_positions = @{ $loading_template->{id_column_positions} };
  2178         4532  
104 2178 100       5177 my $multi_column_id = (@id_positions > 1 ? 1 : 0);
105 2178         9685 my $composite_id_resolver = $class_meta->get_composite_id_resolver;
106              
107             # The old way of specifying that some values were constant for all objects returned
108             # by a get(). The data source would wrap the method that builds the loading template
109             # and wedge in some constant_property_names. The new way is to add columns to the
110             # loading template, and then add the values onto the list returned by the data source
111             # iterator.
112 2178         2797 my %initial_object_data;
113 2178 50       5240 if ($loading_template->{constant_property_names}) {
114 0         0 my @constant_property_names = @{ $loading_template->{constant_property_names} };
  0         0  
115 0         0 my @constant_property_values = map { $rule->value_for($_) } @constant_property_names;
  0         0  
116 0         0 @initial_object_data{@constant_property_names} = @constant_property_values;
117             }
118              
119 2178         5860 my $rule_class_name = $rule_template->subject_class_name;
120 2178         4948 my $template_id = $rule_template->id;
121 2178         2946 my $load_class_name = $class;
122             # $rule can contain params that may not apply to the subclass that's currently loading.
123             # define_boolexpr() in array context will return the portion of the rule that actually applies
124             #my($load_rule, undef) = $load_class_name->define_boolexpr($rule->params_list);
125 2178         5628 my($load_rule, @extra_params) = UR::BoolExpr->resolve($load_class_name, $rule->params_list);
126 2178         6504 my $load_rule_id = $load_rule->id;
127 2178         5685 my $load_template_id = $load_rule->template_id;
128              
129             my @rule_properties_with_in_clauses =
130 2178         7626 grep { $rule_template_without_recursion_desc->operator_for($_) eq 'in' }
  3243         8526  
131             $rule_template_without_recursion_desc->_property_names;
132              
133 2178         3002 my($rule_template_without_in_clause,$rule_template_id_without_in_clause,%in_clause_values,@all_rule_property_names);
134 2178         2791 my $do_record_in_all_params_loaded = 1;
135 2178 100       4977 if (@rule_properties_with_in_clauses) {
136 106         263 $rule_template_id_without_in_clause = $rule_template_without_recursion_desc->id;
137 106         189 foreach my $property_name ( @rule_properties_with_in_clauses ) {
138             # FIXME - removing and re-adding the filter should have the same effect as the substitute below,
139             # but the two result in different rules in the end.
140             #$rule_template_without_in_clause = $rule_template_without_in_clause->remove_filter($property_name);
141             #$rule_template_without_in_clause = $rule_template_without_in_clause->add_filter($property_name);
142 129         2069 $rule_template_id_without_in_clause =~ s/($property_name) in/$1/;
143             }
144 106         332 $rule_template_without_in_clause = UR::BoolExpr::Template->get($rule_template_id_without_in_clause);
145             # Make a note of all the values in the in-clauses. As the objects get returned from the
146             # data source, we'll remove these notes. Anything that's left by the time the iterator is
147             # finalized must be values that matched nothing. Then, finalize can put data in
148             # all_params_loaded showing it matches nothing
149 106         203 my %rule_properties_with_in_clauses = map { $_ => 1 } @rule_properties_with_in_clauses;
  129         386  
150 106         294 @all_rule_property_names = $rule_template_without_in_clause->_property_names;
151 106         203 foreach my $property ( @rule_properties_with_in_clauses ) {
152 129         424 my $values_for_in_clause = $rule_without_recursion_desc->value_for($property);
153 129 50       322 unless ($values_for_in_clause) {
154 0         0 Carp::confess("rule has no value for property $property: $rule_without_recursion_desc");
155             }
156 129 50       345 if (@$values_for_in_clause > 100) {
157 0         0 $do_record_in_all_params_loaded = 0;
158 0         0 next;
159             }
160              
161 129 100       339 my @other_values = map { exists $rule_properties_with_in_clauses{$_}
  196         512  
162             ? undef # placeholder filled in below
163             : $rule_without_recursion_desc->value_for($_) }
164             $rule_template_without_in_clause->_property_names;
165 129         317 my $position_for_this_property = $rule_template_without_in_clause->value_position_for_property_name($property);
166              
167              
168             # If the number of items in the in-clause is over this number, then don't bother recording
169             # the template-id/rule-id, since searching the list to see if this query has been done before
170             # is going to take longer than just re-doing the query
171 129         239 foreach my $value ( @$values_for_in_clause ) {
172 263 100       467 $value = '' if (!defined $value);
173 263         300 $other_values[$position_for_this_property] = $value;
174 263         502 my $rule_with_this_in_property = $rule_template_without_in_clause->get_rule_for_values(@other_values);
175 263         463 $in_clause_values{$property}->{$value}
176             = [$rule_template_id_without_in_clause, $rule_with_this_in_property->id];
177             }
178             }
179              
180             }
181              
182             # This is a local copy of what we want to put in all_params_loaded, when the object fabricator is
183             # finalized
184 2178         3431 my $local_all_params_loaded = {};
185              
186 2178         2557 my($hints_or_delegation,$delegations_with_no_objects);
187 2178 100       4787 if (!$loading_base_object) {
188 174         791 ($hints_or_delegation,$delegations_with_no_objects)
189             = $fab_class->_resolve_delegation_data($rule,$loading_template,$query_plan,$local_all_params_loaded);
190             }
191              
192             my $update_apl_for_loaded_object = sub {
193 103999     103999   76380 my $pending_db_object = shift;
194              
195             # Make a note in all_params_loaded (essentially, the query cache) that we've made a
196             # match on this rule, and some equivalent rules
197 103999 100 66     290554 if ($loading_base_object and not $rule_specifies_id) {
198 103568 50       119357 if ($do_record_in_all_params_loaded) {
199 103568 100 100     179214 if ($rule_class_name ne $load_class_name and scalar(@extra_params) == 0) {
200 22         109 $pending_db_object->{__load}->{$load_template_id}{$load_rule_id}++;
201 22         75 $UR::Context::all_params_loaded->{$load_template_id}{$load_rule_id} = undef;
202 22         59 $local_all_params_loaded->{$load_template_id}{$load_rule_id}++;
203             }
204 103568         254639 $pending_db_object->{__load}->{$template_id}{$rule_id}++;
205 103568         131253 $UR::Context::all_params_loaded->{$template_id}{$rule_id} = undef;
206 103568         95491 $local_all_params_loaded->{$template_id}{$rule_id}++;
207             }
208              
209 103568 100       174501 if (@rule_properties_with_in_clauses) {
210             # FIXME - confirm that all the object properties are filled in at this point, right?
211             #my @values = @$pending_db_object{@rule_properties_with_in_clauses};
212 192         401 my @values = @$pending_db_object{@all_rule_property_names};
213 192         535 my $r = $rule_template_without_in_clause->get_normalized_rule_for_values(@values);
214 192         377 my $r_id = $r->id;
215              
216 192         336 $UR::Context::all_params_loaded->{$rule_template_id_without_in_clause}{$r_id} = undef;
217 192         323 $local_all_params_loaded->{$rule_template_id_without_in_clause}{$r_id}++;
218             # remove the notes about these in-clause values since they matched something
219 266     266   213026 no warnings; # undef treated as an empty string below
  266         461  
  266         65849  
220 192         261 foreach my $property (@rule_properties_with_in_clauses) {
221 251         304 my $value = $pending_db_object->{$property};
222 251         685 delete $in_clause_values{$property}->{$value};
223             }
224             }
225             }
226 2178         13823 };
227              
228              
229 2178         2694 my $fabricator_obj; # filled in after the closure definition
230             my $object_fabricator = sub {
231              
232 104094     104094   79281 my $next_db_row = $_[0];
233              
234             # If all the columns for this object are undef, then this doesn't encode an actual
235             # object, it's a result of a left join that matched nothing
236 104094         64242 my $values_exist;
237 104094         98919 foreach my $column ( @column_positions ) {
238 104160 100       163743 if (defined($next_db_row->[$column])) {
239 104073         71112 $values_exist = 1;
240 104073         80973 last;
241             }
242             }
243 104094 100 100     228333 if (!$loading_base_object and !$values_exist and $delegations_with_no_objects) {
      66        
244 21         69 my $templates_and_rules = $fab_class->_lapl_data_for_delegation_data($delegations_with_no_objects, $next_db_row);
245 21         87 while ( my($template_id, $rule_id) = each %$templates_and_rules) {
246 21         47 $local_all_params_loaded->{$template_id}->{$rule_id} = 0;
247 21         73 $UR::Context::all_params_loaded->{$template_id}->{$rule_id} = 0;
248             }
249 21         51 return;
250             }
251            
252              
253 104073         145867 my $pending_db_object_data = { %initial_object_data };
254 104073         268166 @$pending_db_object_data{@property_names} = @$next_db_row[@column_positions];
255              
256             # resolve id
257 104073         71037 my $pending_db_object_id;
258 104073 100       117079 if ($multi_column_id) {
259 157         513 $pending_db_object_id = $composite_id_resolver->(@$pending_db_object_data{@id_property_names})
260             }
261             else {
262 103916         126328 $pending_db_object_id = $pending_db_object_data->{$id_property_names[0]};
263             }
264              
265 104073 50       137327 unless (defined $pending_db_object_id) {
266 0         0 return undef;
267 0         0 Carp::confess(
268             "no id found in object data for $class_name?\n"
269             . Data::Dumper::Dumper($pending_db_object_data)
270             );
271             }
272              
273 104073         66968 my $pending_db_object;
274              
275             # skip if this object has been deleted but not committed
276 104073         64289 do {
277 266     266   1457 no warnings;
  266         396  
  266         709828  
278 104073 100       179548 if ($UR::Context::all_objects_loaded->{$ghost_class}{$pending_db_object_id}) {
279 45         132 return;
280             #$pending_db_object = undef;
281             #redo;
282             }
283             };
284              
285             # Handle the object based-on whether it is already loaded in the current context.
286 104028 100       202179 if ($pending_db_object = $UR::Context::all_objects_loaded->{$class}{$pending_db_object_id}) {
287 1226         4369 $context->__merge_db_data_with_existing_object($class, $pending_db_object, $pending_db_object_data, \@property_names);
288 1219 100 100     4640 if ($loading_base_object
      100        
289             and
290             $needs_further_boolexpr_evaluation_after_loading
291             and
292             not $rule->evaluate($pending_db_object)
293             ) {
294 21         79 return;
295             }
296 1198         1800 $update_apl_for_loaded_object->($pending_db_object);
297             }
298             else {
299             # Handle the case in which the object is completely new in the current context.
300              
301             # Create a new object for the resultset row
302 102802         387852 $pending_db_object = bless { %$pending_db_object_data, id => $pending_db_object_id }, $class;
303 102802         141145 $pending_db_object->{db_committed} = $pending_db_object_data;
304              
305             # determine the subclass name for classes which automatically sub-classify
306 102802         80507 my $subclass_name;
307 102802 100 66     454581 if (
      66        
308             (
309             $sub_classification_method_name
310             or $subclassify_by
311             or $sub_classification_meta_class_name
312             )
313             and
314             (ref($pending_db_object) eq $class) # not already subclased
315             ) {
316 211 100       495 if ($sub_classification_method_name) {
    50          
317 43         279 $subclass_name = $class->$sub_classification_method_name($pending_db_object);
318 43 50       182 unless ($subclass_name) {
319 0         0 my $pending_obj_id = eval { $pending_db_object->id };
  0         0  
320 0         0 Carp::confess(
321             "Object with id '$pending_obj_id' loaded as abstract class $class failed to subclassify itself using method "
322             . $sub_classification_method_name
323             );
324             }
325             }
326             elsif ($sub_classification_meta_class_name) {
327             # Group objects requiring reclassification by type,
328             # and catch anything which doesn't need reclassification.
329              
330 0         0 my $subtype_name = $pending_db_object->$subclassify_by;
331 0         0 $subclass_name = $subclass_for_subtype_name{$subtype_name};
332 0 0       0 unless ($subclass_name) {
333 0         0 my $type_obj = $sub_classification_meta_class_name->get($subtype_name);
334              
335 0 0       0 unless ($type_obj) {
336             # The base type may give the final subclass, or an intermediate
337             # either choice has trade-offs, but we support both.
338             # If an intermediate subclass is specified, that subclass
339             # will join to a table with another field to indicate additional
340             # subclassing. This means we have to do this part the hard way.
341             # TODO: handle more than one level.
342 0         0 my @all_type_objects = $sub_classification_meta_class_name->get();
343 0         0 for my $some_type_obj (@all_type_objects) {
344 0         0 my $some_subclass_name = $some_type_obj->subclass_name($class);
345 0 0       0 unless (UR::Object::Type->get($some_subclass_name)->is_abstract) {
346 0         0 next;
347             }
348 0         0 my $some_subclass_meta = $some_subclass_name->__meta__;
349 0         0 my $some_subclass_type_class =
350             $some_subclass_meta->sub_classification_meta_class_name;
351 0 0       0 if ($type_obj = $some_subclass_type_class->get($subtype_name)) {
352             # this second-tier subclass works
353 0         0 last;
354             }
355             else {
356             # try another subclass, and check the subclasses under it
357             #print "skipping $some_subclass_name: no $subtype_name for $some_subclass_type_class\n";
358             }
359             }
360             }
361              
362 0 0       0 if ($type_obj) {
363 0         0 $subclass_name = $type_obj->subclass_name($class);
364             }
365             else {
366 0         0 warn "Failed to find $class_name sub-class for type '$subtype_name'!";
367 0         0 $subclass_name = $class_name;
368             }
369              
370 0 0       0 unless ($subclass_name) {
371 0         0 Carp::confess(
372             "Failed to sub-classify $class using "
373             . $type_obj->class
374             . " '" . $type_obj->id . "'"
375             );
376             }
377              
378 0         0 $subclass_name->class;
379             }
380 0         0 $subclass_for_subtype_name{$subtype_name} = $subclass_name;
381             }
382             else {
383 168         592 $subclass_name = $pending_db_object->$subclassify_by;
384 167 50       329 unless ($subclass_name) {
385 0         0 Carp::croak("Failed to sub-classify $class while loading; calling method "
386             . "'$subclassify_by' returned false. Relevant object data: "
387             . Data::Dumper::Dumper($pending_db_object));
388             }
389             }
390              
391             # note: we check this again with the real base class, but this keeps junk objects out of the core hash
392 210 50       971 unless ($subclass_name->isa($class)) {
393             # We may have done a load on the base class, and not been able to use properties to narrow down to the correct subtype.
394             # The resultset returned more data than we needed, and we're filtering out the other subclasses here.
395 0         0 return;
396             }
397             }
398             else {
399             # regular, non-subclassifier
400 102591         75936 $subclass_name = $class;
401             }
402              
403             # store the object
404             # note that we do this on the base class even if we know it's going to be put into a subclass below
405 102801         176737 $UR::Context::all_objects_loaded->{$class}{$pending_db_object_id} = $pending_db_object;
406 102801         82334 $UR::Context::all_objects_cache_size++;
407              
408             # If we're using a light cache, weaken the reference.
409 102801 50 33     152303 if ($UR::Context::light_cache and substr($class,0,5) ne 'App::') {
410 0         0 Scalar::Util::weaken($UR::Context::all_objects_loaded->{$class_name}->{$pending_db_object_id});
411             }
412              
413 102801         136197 $update_apl_for_loaded_object->($pending_db_object);
414              
415 102801         78310 my $boolexpr_evaluated_ok;
416 102801 100       122774 if ($subclass_name eq $class) {
417             # This object doesn't need additional subclassing
418             # Signal that the object has been loaded
419             # NOTE: until this is done indexes cannot be used to look-up an object
420 102640         211112 $pending_db_object->__signal_change__('load');
421             }
422             else {
423             # we did this above, but only checked the base class
424 161         695 my $subclass_ghost_class = $subclass_name->ghost_class;
425 161 100       465 if ($UR::Context::all_objects_loaded->{$subclass_ghost_class}{$pending_db_object_id}) {
426             # We put it in the object cache a few lines above.
427             # FIXME - why not wait until we know we're keeping it before putting it in there?
428 37         60 delete $UR::Context::all_objects_loaded->{$class}{$pending_db_object_id};
429 37         35 $UR::Context::all_objects_cache_size--;
430 37         114 return;
431             #$pending_db_object = undef;
432             #redo;
433             }
434              
435 124         185 my $re_bless = $subclass_is_safe_for_re_bless{$subclass_name};
436 124 100       296 if (not defined $re_bless) {
437 78         545 $re_bless = $dsx->_class_is_safe_to_rebless_from_parent_class($subclass_name, $class);
438 78   100     243 $re_bless ||= 0;
439 78         210 $subclass_is_safe_for_re_bless{$subclass_name} = $re_bless;
440             }
441              
442 124         154 my $loading_info;
443              
444 124 100       295 if (!$re_bless) {
445             # This object cannot just be re-classified into a subclass because the subclass joins to additional tables.
446             # We'll make a parallel iterator for each subclass we encounter.
447              
448             # Note that we let the calling db-based iterator do that, so that if multiple objects on the row need
449             # sub-classing, we do them all at once.
450              
451             # Decrement all of the param_keys it is using.
452 60 50       139 if ($loading_base_object) {
453 60         276 $loading_info = $dsx->_get_object_loading_info($pending_db_object);
454 60         230 $loading_info = $dsx->_reclassify_object_loading_info_for_new_class($loading_info,$subclass_name);
455             }
456              
457             #$pending_db_object->unload;
458 60         143 delete $UR::Context::all_objects_loaded->{$class}->{$pending_db_object_id};
459              
460 60 50       128 if ($loading_base_object) {
461 60         231 $dsx->_record_that_loading_has_occurred($loading_info);
462             }
463              
464             # NOTE: we're returning a class name instead of an object
465             # this tells the caller to re-do the entire row using a subclass to get the real data.
466             # Hack? Probably so...
467 60         350 return $subclass_name;
468             }
469              
470             # Performance shortcut.
471             # These need to be subclassed, but there is no additional data to load.
472             # Just remove from the object cache, rebless to the proper subclass, and
473             # re-add to the object cache
474 64         338 my $already_loaded = $subclass_name->is_loaded($pending_db_object_id);
475              
476 64         83 my $different;
477             my $merge_exception;
478 64 100       156 if ($already_loaded) {
479 38         73 eval { $different = $context->__merge_db_data_with_existing_object($class, $already_loaded, $pending_db_object_data, \@property_names) };
  38         211  
480 38         88 $merge_exception = $@;
481             }
482              
483 64 100 100     420 if ($already_loaded and !$different and !$merge_exception) {
      100        
484 27 50       79 if ($pending_db_object == $already_loaded) {
485 0         0 Carp::croak("An object of type ".$already_loaded->class." with ID '".$already_loaded->id
486             ."' was just loaded, but already exists in the object cache in the proper subclass");
487             }
488 27 100       61 if ($loading_base_object) {
489             # Get our records about loading this object
490 24         97 $loading_info = $dsx->_get_object_loading_info($pending_db_object);
491              
492             # Transfer the load info for the load we _just_ did to the subclass too.
493 24         88 my $subclassified_template = $rule_template->sub_classify($subclass_name);
494 24         77 $loading_info->{$subclassified_template->id} = $loading_info->{$template_id};
495 24         90 $loading_info = $dsx->_reclassify_object_loading_info_for_new_class($loading_info,$subclass_name);
496             }
497              
498             # This will wipe the above data from the object and the contex...
499 27         92 delete $UR::Context::all_objects_loaded->{$class}->{$pending_db_object_id};
500              
501 27 100       76 if ($loading_base_object) {
502             # ...now we put it back for both.
503 24         77 $dsx->_add_object_loading_info($already_loaded, $loading_info);
504 24         68 $dsx->_record_that_loading_has_occurred($loading_info);
505             }
506              
507 27         58 bless($pending_db_object,'UR::DeletedRef');
508 27         39 $pending_db_object = $already_loaded;
509             }
510             else {
511 37 100       112 if ($loading_base_object) {
512 31         219 my $subclassified_template = $rule_template->sub_classify($subclass_name);
513              
514 31         226 $loading_info = $dsx->_get_object_loading_info($pending_db_object);
515 31         452 $dsx->_record_that_loading_has_occurred($loading_info);
516 31         140 $loading_info->{$subclassified_template->id} = delete $loading_info->{$template_id};
517 31         175 $loading_info = $dsx->_reclassify_object_loading_info_for_new_class($loading_info,$subclass_name);
518             }
519              
520 37         135 my $prev_class_name = $pending_db_object->class;
521             #my $id = $pending_db_object->id;
522             #$pending_db_object->__signal_change__("unload");
523 37         80 delete $UR::Context::all_objects_loaded->{$prev_class_name}->{$pending_db_object_id};
524 37         72 delete $UR::Context::all_objects_are_loaded->{$prev_class_name};
525 37 100       96 if ($merge_exception) {
526             # Now that we've removed traces of the incorrectly-subclassed $pending_db_object,
527             # we can pass up any exception generated in __merge_db_data_with_existing_object
528 5         700 Carp::croak($merge_exception);
529             }
530 32 100       76 if ($already_loaded) {
531             # The new object should replace the old object. Since other parts of the user's program
532             # may have references to this object, we need to copy the values from the new object into
533             # the existing cached object
534 6         16 bless($pending_db_object,'UR::DeletedRef');
535 6         11 $pending_db_object = $already_loaded;
536             } else {
537             # This is a completely new object
538 26         55 $UR::Context::all_objects_loaded->{$subclass_name}->{$pending_db_object_id} = $pending_db_object;
539             }
540 32         74 bless $pending_db_object, $subclass_name;
541 32         195 $pending_db_object->__signal_change__("load");
542              
543 32         208 $dsx->_add_object_loading_info($pending_db_object, $loading_info);
544 32         99 $dsx->_record_that_loading_has_occurred($loading_info);
545             }
546              
547             # the object may no longer match the rule after subclassifying...
548 59 100 66     303 if ($needs_further_boolexpr_evaluation_after_loading
      100        
549             and
550             $loading_base_object
551             and
552             not $rule->evaluate($pending_db_object)
553             ) {
554             #print "Object does not match rule!" . Dumper($pending_db_object,[$rule->params_list]) . "\n";
555             #$rule->evaluate($pending_db_object);
556 4         16 return;
557             } else {
558 55         130 $boolexpr_evaluated_ok = 1;
559             }
560             } # end of sub-classification code
561              
562 102695 100 100     640357 if (
      100        
      100        
563             $loading_base_object
564             and
565             $needs_further_boolexpr_evaluation_after_loading
566             and
567             ( ! $boolexpr_evaluated_ok )
568             and
569             ( ! $rule->evaluate($pending_db_object) )
570             ) {
571 14         41 return;
572             }
573             } # end handling newly loaded objects
574              
575             # If the rule had hints, mark that we loaded those things too, in all_params_loaded
576 103879 100       145549 if ($hints_or_delegation) {
577 349         836 my $templates_and_rules = $fab_class->_lapl_data_for_delegation_data($hints_or_delegation,
578             $next_db_row,
579             $pending_db_object);
580 349         1024 while ( my($template_id, $rule_id) = each %$templates_and_rules) {
581 349         630 $local_all_params_loaded->{$template_id}->{$rule_id}++;
582 349         1192 $UR::Context::all_params_loaded->{$template_id}->{$rule_id} = undef;
583             }
584             }
585              
586             # note all of the joins which follow this object as having been "done"
587 103879 50       149283 if (my $next_joins = $loading_template->{next_joins}) {
588 0         0 if (0) {
589             # disabled until a fully reframed query is the basis for these joins
590             for my $next_join (@$next_joins) {
591             my ($bxt_id, $values, $value_position_property_name) = @$next_join;
592             for (my $n = 0; $n < @$value_position_property_name; $n+=2) {
593             my $pos = $value_position_property_name->[$n];
594             my $name = $value_position_property_name->[$n+1];
595             $values->[$pos] = $pending_db_object->$name;
596             }
597             my $bxt = UR::BoolExpr::Template::And->get($bxt_id);
598             my $bx = $bxt->get_rule_for_values(@$values);
599             $UR::Context::all_params_loaded->{$bxt->{id}}->{$bx->{id}} = undef;
600             $local_all_params_loaded->{$bxt->{id}}->{$bx->{id}}++;
601             print "remembering $bx\n";
602             }
603             }
604             }
605              
606             # When there is recursion in the query, we record data from each
607             # recursive "level" as though the query was done individually.
608 103879 100 66     151650 if ($recursion_desc and $loading_base_object) {
609             # if we got a row from a query, the object must have
610             # a db_committed or db_saved_committed
611 17   33     41 my $dbc = $pending_db_object->{db_committed} || $pending_db_object->{db_saved_uncommitted};
612 17 50       38 Carp::croak("Loaded database data has no save data for $class id ".$pending_db_object->id
613             .". Something bad happened.".Data::Dumper::Dumper($pending_db_object)) unless $dbc;
614              
615 17         28 my $value_by_which_this_object_is_loaded_via_recursion = $dbc->{$recurse_property_on_this_row};
616 17         21 my $value_referencing_other_object = $dbc->{$recurse_property_referencing_other_rows};
617 17 100       39 $value_referencing_other_object = '' unless (defined $value_referencing_other_object);
618 17 50       31 unless ($recurse_property_value_found{$value_referencing_other_object}) {
619             # This row points to another row which will be grabbed because the query is hierarchical.
620             # Log the smaller query which would get the hierarchically linked data directly as though it happened directly.
621 17         30 $recurse_property_value_found{$value_referencing_other_object} = 1;
622             # note that the direct query need not be done again
623 17         62 my $equiv_rule = UR::BoolExpr->resolve_normalized(
624             $class,
625             $recurse_property_on_this_row => $value_referencing_other_object,
626             );
627 17         39 my $equiv_rule_id = $equiv_rule->id;
628 17         46 my $equiv_template_id = $equiv_rule->template_id;
629              
630             # note that the recursive query need not be done again
631 17         66 my $equiv_rule_2 = UR::BoolExpr->resolve_normalized(
632             $class,
633             $recurse_property_on_this_row => $value_referencing_other_object,
634             -recurse => $recursion_desc,
635             );
636 17         42 my $equiv_rule_id_2 = $equiv_rule_2->id;
637 17         39 my $equiv_template_id_2 = $equiv_rule_2->template_id;
638              
639             # For any of the hierarchically related data which is already loaded,
640             # note on those objects that they are part of that query. These may have loaded earlier in this
641             # query, or in a previous query. Anything NOT already loaded will be hit later by the if-block below.
642 17         88 my @subset_loaded = $class->is_loaded($recurse_property_on_this_row => $value_referencing_other_object);
643 17         50 $UR::Context::all_params_loaded->{$equiv_template_id}->{$equiv_rule_id} = undef;
644 17         43 $UR::Context::all_params_loaded->{$equiv_template_id_2}->{$equiv_rule_id_2} = undef;
645 17         45 $local_all_params_loaded->{$equiv_template_id}->{$equiv_rule_id} = scalar(@subset_loaded);
646 17         36 $local_all_params_loaded->{$equiv_template_id_2}->{$equiv_rule_id_2} = scalar(@subset_loaded);
647 17         60 for my $pending_db_object (@subset_loaded) {
648 4         10 $pending_db_object->{__load}->{$equiv_template_id}->{$equiv_rule_id}++;
649 4         14 $pending_db_object->{__load}->{$equiv_template_id_2}->{$equiv_rule_id_2}++;
650             }
651             }
652              
653             # NOTE: if it were possible to use undef values in a connect-by, this could be a problem
654             # however, connect by in UR is always COL = COL, which would always fail on NULLs.
655 17 50 66     81 if (defined($value_by_which_this_object_is_loaded_via_recursion) and $recurse_property_value_found{$value_by_which_this_object_is_loaded_via_recursion}) {
656             # This row was expected because some other row in the hierarchical query referenced it.
657             # Up the object count, and note on the object that it is a result of this query.
658 0         0 my $equiv_rule = UR::BoolExpr->resolve_normalized(
659             $class,
660             $recurse_property_on_this_row => $value_by_which_this_object_is_loaded_via_recursion,
661             );
662 0         0 my $equiv_rule_id = $equiv_rule->id;
663 0         0 my $equiv_template_id = $equiv_rule->template_id;
664              
665             # note that the recursive query need not be done again
666 0         0 my $equiv_rule_2 = UR::BoolExpr->resolve_normalized(
667             $class,
668             $recurse_property_on_this_row => $value_by_which_this_object_is_loaded_via_recursion,
669             -recurse => $recursion_desc
670             );
671 0         0 my $equiv_rule_id_2 = $equiv_rule_2->id;
672 0         0 my $equiv_template_id_2 = $equiv_rule_2->template_id;
673              
674 0         0 $UR::Context::all_params_loaded->{$equiv_template_id}->{$equiv_rule_id} = undef;
675 0         0 $UR::Context::all_params_loaded->{$equiv_template_id_2}->{$equiv_rule_id_2} = undef;
676 0         0 $local_all_params_loaded->{$equiv_template_id}->{$equiv_rule_id}++;
677 0         0 $local_all_params_loaded->{$equiv_template_id_2}->{$equiv_rule_id_2}++;
678 0         0 $pending_db_object->{__load}->{$equiv_template_id}->{$equiv_rule_id}++;
679 0         0 $pending_db_object->{__load}->{$equiv_template_id_2}->{$equiv_rule_id_2}++;
680             }
681             } # end of handling recursion
682              
683 103879         160068 return $pending_db_object;
684              
685 2178         34202 }; # end of per-class object fabricator
686 2178         19546 Sub::Name::subname("UR::Context::__object_fabricator(closure)__ ($class_name)", $object_fabricator);
687              
688             # remember all the changes to $UR::Context::all_params_loaded that should be made.
689             # This fixes the problem where you create an iterator for a query, read back some of
690             # the items, but not all, then later make the same query. The old behavior made
691             # entries in all_params_loaded as objects got loaded from the DB, so that at the time
692             # the second query is made, UR::Context::_cache_is_complete_for_class_and_normalized_rule()
693             # sees there are entries in all_params_loaded, and so reports yes, the cache is complete,
694             # and the second query only returns the objects that were loaded during the first query.
695             #
696             # The new behavior builds up changes to be made to all_params_loaded, and someone
697             # needs to call $object_fabricator->finalize() to apply these changes
698 2178         8725 $fabricator_obj = $fab_class->_create(fabricator => $object_fabricator,
699             context => $context,
700             all_params_loaded => $local_all_params_loaded,
701             in_clause_values => \%in_clause_values);
702              
703 2178         24995 return $fabricator_obj;
704             }
705              
706              
707             # Given the data created in _resolve_delegation_data (rule templates and values/valuerefs)
708             # return a hash of template IDs => rule IDs that need to be manipulated in local_all_params_loaded
709             sub _lapl_data_for_delegation_data {
710 370     370   454 my($fab_class, $delegation_data_list, $next_db_row, $pending_db_object) = @_;
711              
712 370         317 my %tmpl_and_rules;
713              
714 370         429 foreach my $delegation_data ( @$delegation_data_list ) {
715 370         396 my $value_sources = $delegation_data->[0];
716 370         331 my $rule_tmpl = $delegation_data->[1];
717 370         314 my @values;
718 370         376 foreach my $value_source ( @$value_sources ) {
719 724 100       1796 if (! ref($value_source)) {
    100          
    50          
720 54         81 push @values, $value_source;
721             } elsif (Scalar::Util::looks_like_number($$value_source)) {
722 666         877 push @values, $next_db_row->[$$value_source];
723             } elsif ($pending_db_object) {
724 4         4 my $method_name = $$value_source;
725 4         5 my $result = eval { $pending_db_object->$method_name };
  4         88  
726 4         9 push @values, $result;
727             } else {
728 0         0 Carp::croak("Can't resolve value for '".$$value_source."' in delegation data when there is no object involved");
729             }
730             }
731 370         1007 my $rule = $rule_tmpl->get_normalized_rule_for_values(@values);
732 370         691 $tmpl_and_rules{$rule_tmpl->id} = $rule->id;
733             }
734 370         586 return \%tmpl_and_rules;
735             }
736              
737              
738             # This is used by fabricators created as a result of filters or hints on delegated properties
739             # of the primary object to pre-calculate rule templates and value sources that can be combined
740             # with these templates to make rules. The resulting template and rule IDs are then plugged into
741             # all_params_loaded to indicate these related objects are loaded so that subsequent queries
742             # will not hit the data sources.
743             sub _resolve_delegation_data {
744 174     174   326 my($fab_class,$rule,$loading_template,$query_plan,$local_all_params_loaded) = @_;
745              
746 174         459 my $rule_template = $rule->template;
747              
748 174         508 my $query_class_meta = $rule_template->subject_class_name->__meta__;
749              
750 174         250 my %hints;
751 174 100       574 if ($rule_template->hints) {
752 27         33 $hints{$_} = 1 foreach(@{ $rule_template->hints });
  27         73  
753             }
754 174         229 my %delegations;
755 174 100       214 if (@{ $query_plan->{'joins'}} ) {
  174         595  
756 103         286 foreach my $delegated_property_name ( $rule_template->_property_names ) {
757 147         411 my $delegated_property_meta = $query_class_meta->property_meta_for_name($delegated_property_name);
758 147 100 66     660 next unless ($delegated_property_meta and $delegated_property_meta->is_delegated);
759 114         299 $delegations{$delegated_property_name} = 1;
760             }
761             }
762              
763 174         306 my $this_object_num = $loading_template->{'object_num'};
764              
765 174         810 my $join = $query_plan->_get_alias_join($loading_template->{'table_alias'});
766 174 100       495 return unless $join; # would this ever be false?
767 160 50       539 return unless ($join->{'foreign_class'} eq $loading_template->{'data_class_name'}); # sanity check
768              
769 160         211 my $delegated_property_meta;
770             # Find out which delegation property was responsible for this object being loaded
771             DELEGATIONS:
772 160         460 foreach my $delegation ((keys %hints), (keys %delegations)) {
773 147         440 my $query_property_meta = $query_class_meta->property_meta_for_name($delegation);
774 147 50       394 next DELEGATIONS unless $query_property_meta;
775              
776 147         629 my @joins_from_delegation = $query_property_meta->_resolve_join_chain();
777 147         324 foreach my $join_from_delegation ( @joins_from_delegation ) {
778 201 100       542 if ($join_from_delegation->id eq $join->id) {
779 123         163 $delegated_property_meta = $query_property_meta;
780 123         283 last DELEGATIONS;
781             }
782             }
783             }
784 160 100       474 return unless $delegated_property_meta;
785 123         291 my $delegated_property_name = $delegated_property_meta->property_name;
786              
787 123 100       474 return if $join->destination_is_all_id_properties();
788              
789 93         147 my @template_filter_names = @{$join->{'foreign_property_names'}};
  93         250  
790 93         138 my %template_filter_values;
791 93         205 foreach my $name ( @template_filter_names ) {
792             my $column_num = $query_plan->column_index_for_class_property_and_object_num(
793 93         507 $join->{'foreign_class'},
794             $name,
795             $this_object_num);
796 93 100       258 if (defined $column_num) {
797 92         267 $template_filter_values{$name} = \$column_num;
798             } else {
799 1         1 my $prop_name = $name;
800 1         3 $template_filter_values{$name} = \$prop_name;
801             }
802             }
803              
804 93 100       253 if ($delegations{$delegated_property_name}) {
805 71         301 my $delegation_final_property_meta = $delegated_property_meta->final_property_meta;
806 71 100 66     356 if ($delegation_final_property_meta
807             and
808             $delegation_final_property_meta->class_name eq $join->{'foreign_class'}
809             ) {
810             # This delegation points to (or at least through) this join's foreign class
811             # We'll note that these related objects were loaded as a result of being
812             # connected to the primary object by this value, and filtered by the
813             # delegation property's value
814 59         417 my $delegation_final_property_name = $delegation_final_property_meta->property_name;
815             my $column_num = $query_plan->column_index_for_class_property_and_object_num(
816 59         227 $join->{'foreign_class'},
817             $delegation_final_property_name,
818             $this_object_num);
819 59         117 push @template_filter_names, $delegation_final_property_name;
820 59 50 66     169 if ($delegation_final_property_meta->column_name and ! defined ($column_num)) {
821             # sanity check
822             Carp::carp("Could not determine column offset in result set for property "
823 0         0 . "$delegation_final_property_name of class " . $join->{'foreign_class'}
824             . " even though it has column_name " . $delegation_final_property_meta->column_name);
825 0         0 $column_num = undef;
826             }
827 59 100       155 if (defined $column_num) {
828 58         154 $template_filter_values{$delegation_final_property_name} = \$column_num;
829             } else {
830 1         3 $template_filter_values{$delegation_final_property_name} = \$delegation_final_property_name;
831             }
832             }
833             }
834              
835             # For missing objects, ie. a left join was done and it matched nothing
836 93         138 my @missing_prop_names;
837             my %missing_values;
838 93         185 for (my $i = 0; $i < @{ $join->{'foreign_property_names'}}; $i++) {
  186         479  
839             # we're using the source class/property here because we're going to denote that a value
840             # of the source class of the join matched nothing
841 93         176 my $prop_name = $join->{'foreign_property_names'}->[$i];
842 93         155 push @missing_prop_names, $prop_name;
843 93         173 my $source_class = $join->{'source_class'};
844 93         182 my $source_prop_name = $join->{'source_property_names'}->[$i];
845 93         111 my $column_num;
846 93 50       332 if( my($actual_prop_meta) = $source_class->__meta__->_concrete_property_meta_for_class_and_name($source_prop_name) ) {
847 93         271 $column_num = $query_plan->column_index_for_class_and_property_before_object_num($actual_prop_meta->class_name,
848             $actual_prop_meta->property_name,
849             $this_object_num);
850             }
851 93 50       248 if (defined $column_num) {
852 93         241 $missing_values{$prop_name} = \$column_num;
853             } else {
854 0         0 Carp::croak("Can't determine resultset column for $source_class property $source_prop_name for rule $rule");
855             }
856             }
857 93 100       269 if ($join->{'where'}) {
858 31         65 for (my $i = 0; $i < @{$join->{'where'}}; $i += 2) {
  62         166  
859 31         69 my $where_prop = $join->{'where'}->[$i];
860 31         51 push @template_filter_names, $where_prop;
861 31         43 push @missing_prop_names, $where_prop;
862              
863 31         58 my $pos = index($where_prop, ' ');
864 31 100       88 if ($pos != -1) {
865             # the key is "propname op"
866 6         13 $where_prop = substr($where_prop,0,$pos);
867             }
868 31         69 my $where_value = $join->{'where'}->[$i+1];
869 31         60 $template_filter_values{$where_prop} = $where_value;
870 31         57 $missing_values{$where_prop} = $where_value;
871             }
872             }
873              
874 93         457 my $missing_rule_tmpl = UR::BoolExpr::Template->resolve($join->{'foreign_class'}, @missing_prop_names)->get_normalized_template_equivalent;
875              
876 93         321 my $related_rule_tmpl = UR::BoolExpr::Template->resolve($join->{'foreign_class'},
877             @template_filter_names)->get_normalized_template_equivalent;
878              
879 93         160 my(@hints_or_delegation, @delegations_with_no_objects);
880             # Items in the first listref can be one of three things:
881             # 1) a reference to an integer - meaning retrieve the value from this column in the result set
882             # 2) a reference to a string - meaning retrieve the value from the object usign this as a property name
883             # 3) a string - meaning this is a literal value to fill in directly
884             # The second item is a rule template we'll be feeding these values in to
885 93         286 my @template_filter_values = @template_filter_values{$related_rule_tmpl->_property_names};
886 93         246 push @hints_or_delegation, [ \@template_filter_values, $related_rule_tmpl];
887              
888 93         255 my @missing_values = @missing_values{$missing_rule_tmpl->_property_names};
889 93         212 push @delegations_with_no_objects, [\@missing_values, $missing_rule_tmpl];
890              
891 93         474 return (\@hints_or_delegation, \@delegations_with_no_objects);
892             }
893              
894              
895              
896             sub all_object_fabricators {
897 4948     4948 1 10566 return values %all_object_fabricators;
898             }
899              
900             # simple accessors
901              
902             sub fabricator {
903 2205     2205 0 2680 my $self = shift;
904 2205         7034 return $self->{'fabricator'};
905             }
906              
907             sub context {
908 0     0 0 0 my $self = shift;
909 0         0 return $self->{'context'};
910             }
911              
912             sub all_params_loaded {
913 23     23 0 31 my $self = shift;
914 23         37 return $self->{'all_params_loaded'};
915             }
916              
917             sub in_clause_values {
918 0     0 0 0 my $self = shift;
919 0         0 return $self->{'in_clause_values'};
920             }
921              
922             # call the object fabricator closure
923             sub fabricate {
924 0     0 1 0 my $self = shift;
925              
926 0         0 &{$self->{'fabricator'}};
  0         0  
927             }
928              
929              
930              
931             # Returns true if this fabricator has loaded an object matching this boolexpr
932             sub is_loading_in_progress_for_boolexpr {
933 66     66 1 369 my $self = shift;
934 66         86 my $boolexpr = shift;
935              
936 66         137 my $template_id = $boolexpr->template_id;
937             # FIXME should it use is_subsest_of here?
938 66 100       242 return unless exists $self->{'all_params_loaded'}->{$template_id};
939 21 100       51 return unless exists $self->{'all_params_loaded'}->{$template_id}->{$boolexpr->id};
940 8         20 return 1;
941             }
942              
943              
944             # UR::Contect::_abandon_object calls this to forget about loading an object
945             sub delete_from_all_params_loaded {
946 23     23 0 31 my($self,$template_id,$boolexpr_id) = @_;
947              
948 23 50 33     103 return unless ($template_id and $boolexpr_id);
949              
950 23         49 my $all_params_loaded = $self->all_params_loaded;
951              
952 23 50       53 return unless $all_params_loaded;
953 23 100       68 return unless exists($all_params_loaded->{$template_id});
954 13         41 delete $all_params_loaded->{$template_id}->{$boolexpr_id};
955             }
956              
957              
958             sub finalize {
959 2144     2144 1 2566 my $self = shift;
960              
961 2144         5036 $self->apply_all_params_loaded();
962              
963 2144         5159 delete $all_object_fabricators{$self};
964 2144         5696 $self->{'all_params_loaded'} = undef;
965             }
966              
967              
968             sub apply_all_params_loaded {
969 2229     2229 1 2606 my $self = shift;
970              
971 2229         3242 my $local_all_params_loaded = $self->{'all_params_loaded'};
972              
973 2229         6501 my @template_ids = keys %$local_all_params_loaded;
974 2229         4011 foreach my $template_id ( @template_ids ) {
975 1324         1514 my @rule_ids = keys %{$local_all_params_loaded->{$template_id}};
  1324         3399  
976 1324         2247 foreach my $rule_id ( @rule_ids ) {
977 1473         1955 my $val = $local_all_params_loaded->{$template_id}->{$rule_id};
978 1473 50       3456 next unless exists $UR::Context::all_params_loaded->{$template_id}->{$rule_id}; # Has unload() removed this one earlier?
979 1473         3122 $UR::Context::all_params_loaded->{$template_id}->{$rule_id} += $val;
980             }
981             }
982              
983             # Anything left in here is in-clause values that matched nothing. Make a note in
984             # all_params_loaded showing that so later queries for those values won't hit the
985             # data source
986 2229         3154 my $in_clause_values = $self->{'in_clause_values'};
987 2229         4457 my @properties = keys %$in_clause_values;
988 2229         3465 foreach my $property ( @properties ) {
989 134         146 my @values = keys %{$in_clause_values->{$property}};
  134         337  
990 134         217 foreach my $value ( @values ) {
991 150         145 my $data = $in_clause_values->{$property}->{$value};
992 150         331 $UR::Context::all_params_loaded->{$data->[0]}->{$data->[1]} = 0;
993             }
994             }
995              
996 2229         4424 $self->{'all_params_loaded'} = {};
997             }
998              
999              
1000             sub DESTROY {
1001 2205     2205   3130 my $self = shift;
1002             # Don't apply the changes. Maybe the importer closure just went out of scope before
1003             # it read all the data
1004 2205         3319 my $local_all_params_loaded = $self->{'all_params_loaded'};
1005 2205 100       4455 if ($local_all_params_loaded) {
1006             # finalize wasn't called on this iterator; maybe the importer closure went out
1007             # of scope before it read all the data.
1008             # Conditionally apply the changes from the local all_params_loaded. If the Context's
1009             # all_params_loaded is defined, then another query has successfully run to
1010             # completion, and we should add our data to it. Otherwise, we're the only query like
1011             # this and all_params_loaded should be cleaned out
1012 61         195 foreach my $template_id ( keys %$local_all_params_loaded ) {
1013 34         48 while(1) {
1014 68         67 my($rule_id, $val) = each %{$local_all_params_loaded->{$template_id}};
  68         145  
1015 68 100       154 last unless $rule_id;
1016 34 100       90 if (defined $UR::Context::all_params_loaded->{$template_id}->{$rule_id}) {
1017 1         2 $UR::Context::all_params_loaded->{$template_id}->{$rule_id} += $val;
1018             } else {
1019 33         63 delete $UR::Context::all_params_loaded->{$template_id}->{$rule_id};
1020             }
1021             }
1022             }
1023             }
1024 2205         134989 delete $all_object_fabricators{$self};
1025             }
1026              
1027             1;
1028              
1029             =pod
1030              
1031             =head1 NAME
1032              
1033             UR::Context::ObjectFabricator - Track closures used to fabricate objects from data sources
1034              
1035             =head1 DESCRIPTION
1036              
1037             Object Fabricators are closures that accept listrefs of data returned by
1038             data source iterators, take slices out of them, and construct UR objects
1039             out of the results. They also handle updating the query cache and merging
1040             changed DB data with previously cached objects.
1041              
1042             UR::Context::ObjectFabricator objects are used internally by UR::Context,
1043             and not intended to be used directly.
1044              
1045             =head1 METHODS
1046              
1047             =over 4
1048              
1049             =item create_for_loading_template
1050              
1051             my $fab = UR::Context::ObjectFabricator->create_for_loading_template(
1052             $context, $loading_tmpl_hashref, $template_data,
1053             $rule, $rule_template, $values, $dsx);
1054              
1055             Returns an object fabricator instance that is able to construct objects
1056             of the rule's target class from rows of data returned by data source
1057             iterators. Object fabricators are used a part of the object loading
1058             process, and are called by UR::Context::get_objects_for_class_and_rule()
1059             to transform a row of data returned by a data source iterator into a UR
1060             object.
1061              
1062             For each class involved in a get request, the system prepares a loading
1063             template that describes which columns of the data source data are to be
1064             used to construct an instance of that class. For example, in the case where
1065             a get() is done on a child class, and the parent and child classes store data
1066             in separate tables linked by a relation-property/foreign-key, then the query
1067             against the data source will involve and SQL join (for RDBMS data sources).
1068             That join will produce a result set that includes data from both tables.
1069              
1070             The C<$loading_tmpl_hashref> will have information about which columns of
1071             that result set map to which properties of each involved class. The heart
1072             of the fabricator closure is a list slice extracting the data for that class
1073             and assigning it to a hash slice of properties to fill in the initial object
1074             data for its class. The remainder of the closure is bookkeeping to keep the
1075             object cache ($UR::Context::all_objects_loaded) and query cache
1076             ($UR::Context::all_params_loaded) consistent.
1077              
1078             The interaction of the object fabricator, the query cache, object cache
1079             pruner and object loading iterators that may or may not have loaded all
1080             their data requires that the object fabricators keep a list of changes they
1081             plan to make to the query cache instead of applying them directly. When
1082             the Underlying Context Loading iterator has loaded the last row from the
1083             Data Source Iterator, it calls C on the object fabricator to
1084             tell it to go ahead and apply its changes; essentially treating that
1085             data as a transaction.
1086              
1087             =item all_object_fabricators
1088              
1089             my @fabs = UR::Context::ObjectFabricator->all_object_fabricators();
1090              
1091             Returns a list of all object fabricators that have not yet been finalized
1092              
1093             =item fabricate
1094              
1095             my $ur_object = $fab->fabricate([columns,from,data,source]);
1096              
1097             Given a listref of data pulled from a data source iterator, it slices out
1098             the appropriate columns from the list and constructs a single object to
1099             return.
1100              
1101             =item is_loading_in_progress_for_boolexpr
1102              
1103             my $bool = $fab->is_loading_in_progress_for_boolexpr($boolexpr);
1104              
1105             Given a UR::BoolExpr instance, it returns true if the given fabricator is
1106             prepared to construct objects matching this boolexpr. This is used by
1107             UR::Context to know if other iterators are still pulling in objects that
1108             could match another iterator's boolexpr, and it should therefore not trust
1109             that the object cache is conplete.
1110              
1111             =item finalize
1112              
1113             $fab->finalize();
1114              
1115             Indicates to the iterator that the caller is done using it for constructing
1116             objects, probably because the data source has no more data or the iterator
1117             that was using this fabricator has gone out of scope.
1118              
1119             =item apply_all_params_loaded
1120              
1121             $fab->apply_all_params_loaded();
1122              
1123             As the fabricator constructs objects, it buffers changes to all_params_loaded
1124             (the Context's query cache) to maintain consistency if multiple iterators are
1125             working concurrently. At the appripriate time, call apply_all_params_loaded()
1126             to take those changes and apply them to the current Context's all_params_loaded.
1127              
1128             =back
1129              
1130             =cut
1131