File Coverage

lib/UR/Context.pm
Criterion Covered Total %
statement 1198 1484 80.7
branch 543 746 72.7
condition 172 264 65.1
subroutine 72 81 88.8
pod 14 32 43.7
total 1999 2607 76.6


line stmt bran cond sub pod time code
1             package UR::Context;
2              
3 266     266   1055 use strict;
  266         352  
  266         7815  
4 266     266   964 use warnings;
  266         323  
  266         6768  
5 266     266   899 use Sub::Name;
  266         338  
  266         12670  
6 266     266   1067 use Scalar::Util;
  266         316  
  266         12739  
7              
8             require UR;
9             our $VERSION = "0.46"; # UR $VERSION;
10              
11 266     266   108140 use UR::Context::ImportIterator;
  266         520  
  266         2228  
12 266     266   145453 use UR::Context::ObjectFabricator;
  266         558  
  266         2700  
13 266     266   115762 use UR::Context::LoadingIterator;
  266         608  
  266         2539  
14              
15             UR::Object::Type->define(
16             class_name => 'UR::Context',
17             is_abstract => 1,
18             has => [
19             parent => { is => 'UR::Context', id_by => 'parent_id', is_optional => 1 },
20             query_underlying_context => { is => 'Boolean',
21             is_optional => 1,
22             default_value => undef,
23             doc => 'Flag indicating whether the context must (1), must not (0) or may (undef) query underlying contexts when handling a query' },
24             ],
25             valid_signals => [qw(precommit sync_databases commit prerollback rollback)],
26             doc => <
27             The environment in which all data examination and change occurs in UR. The current context represents the current
28             state of everything, and acts as a manager/intermediary between the current application and underlying database(s).
29             This is responsible for mapping object requests to database requests, managing caching, transaction
30             consistency, locking, etc. by delegating to the correct components to handle these tasks.
31             EOS
32             );
33              
34             our @CARP_NOT = qw( UR::Object::Iterator Class::AutoloadCAN );
35              
36             # These references all point to internal structures of the current process context.
37             # They are created here for boostrapping purposes, because they must exist before the object itself does.
38             our $all_objects_loaded ||= {}; # Master index of all tracked objects by class and then id.
39             our $all_change_subscriptions ||= {}; # Index of other properties by class, property_name, and then value.
40             our $all_objects_are_loaded ||= {}; # Track when a class informs us that all objects which exist are loaded.
41             our $all_params_loaded ||= {}; # Track parameters used to load by template_id then by rule_id
42              
43             # These items are used by prune_object_cache() to control the cache size
44             our $all_objects_cache_size ||= 0; # count of the unloadable objects we've loaded from data sources
45             our $cache_last_prune_serial ||= 0; # serial number the last time we pruned objects
46             our $cache_size_highwater; # high water mark for cache size. Start pruning when $all_objects_cache_size goes over
47             our $cache_size_lowwater; # low water mark for cache size
48             our $GET_COUNTER = 1; # This is where the serial number for the __get_serial key comes from
49             our $light_cache = 0; # whether refs in all_objects_loaded should be weak
50              
51             # For bootstrapping.
52             $UR::Context::current = __PACKAGE__;
53              
54             # called by UR.pm during bootstraping
55             our $initialized = 0;
56             sub _initialize_for_current_process {
57 266     266   553 my $class = shift;
58 266 50       939 if ($initialized) {
59 0         0 die "Attempt to re-initialize the current process?";
60             }
61              
62 266   50     3241 my $root_id = $ENV{UR_CONTEXT_ROOT} ||= 'UR::Context::DefaultRoot';
63 266         3422 $UR::Context::root = UR::Context::Root->get($root_id);
64 266 50       1049 unless ($UR::Context::root) {
65 0         0 die "Failed to find root context object '$root_id':!? Odd value in environment variable UR_CONTEXT_ROOT?";
66             }
67              
68 266 50       1169 if (my $base_id = $ENV{UR_CONTEXT_BASE}) {
69 0         0 $UR::Context::base = UR::Context::Process->get($base_id);
70 0 0       0 unless ($UR::Context::base) {
71 0         0 die "Failed to find base context object '$base_id':!? Odd value in environment variable UR_CONTEXT_BASE?";
72             }
73             }
74             else {
75 266         539 $UR::Context::base = $UR::Context::root;
76             }
77              
78 266         1105 $UR::Context::process = UR::Context::Process->_create_for_current_process(parent_id => $UR::Context::base->id);
79              
80 266 50 33     2063 if (exists $ENV{'UR_CONTEXT_CACHE_SIZE_LOWWATER'} || exists $ENV{'UR_CONTEXT_CACHE_SIZE_HIGHWATER'}) {
81 0   0     0 $cache_size_highwater = $ENV{'UR_CONTEXT_CACHE_SIZE_HIGHWATER'} || 0;
82 0   0     0 $cache_size_lowwater = $ENV{'UR_CONTEXT_CACHE_SIZE_LOWWATER'} || 0;
83 0         0 manage_objects_may_go_out_of_scope();
84             }
85              
86              
87             # This changes when we initiate in-memory transactions on-top of the basic, heavier weight one for the process.
88 266         519 $UR::Context::current = $UR::Context::process;
89              
90 266 50       959 if (exists $ENV{'UR_CONTEXT_MONITOR_QUERY'}) {
91 0         0 $UR::Context::current->monitor_query($ENV{'UR_CONTEXT_MONITOR_QUERY'});
92             }
93              
94 266         485 $initialized = 1;
95 266         904 return $UR::Context::current;
96             }
97              
98             # whether some UR objects might go out of scope, for example if pruning is on,
99             # light cache is on, or an AutoUnloadPool is alive
100             my $objects_may_go_out_of_scope = 0;
101             sub objects_may_go_out_of_scope {
102 143 100   143 0 312 if (@_) {
103 27         26 $objects_may_go_out_of_scope = shift;
104             }
105 143         358 return $objects_may_go_out_of_scope;
106             }
107              
108             sub manage_objects_may_go_out_of_scope {
109 27 100 66 27 0 254 if ((defined($cache_size_highwater) and $cache_size_highwater > 0)
      66        
      66        
110             or
111             $light_cache
112             or
113             UR::Context::AutoUnloadPool->_pool_count
114             ) {
115 14         37 objects_may_go_out_of_scope(1);
116             } else {
117 13         39 objects_may_go_out_of_scope(0);
118             }
119             }
120              
121              
122             # the current context is either the process context, or the current transaction on-top of it
123             *get_current = \¤t;
124             sub current {
125 470     470 0 7896 return $UR::Context::current;
126             }
127              
128             sub process {
129 2     2 0 10 return $UR::Context::process;
130             }
131              
132             sub date_template {
133 28     28 0 200 return q|%Y-%m-%d %H:%M:%S|;
134             }
135              
136             sub now {
137 28     28 0 424 return Date::Format::time2str(date_template(), time());
138             }
139              
140             my $master_monitor_query = 0;
141             sub monitor_query {
142 595612 100   595612 0 867898 return if $UR::Object::Type::bootstrapping;
143 447497         372717 my $self = shift;
144 447497 100       666041 $self = $UR::Context::current unless (ref $self);
145              
146 447497 50       585815 if (@_) {
147 0 0       0 if (ref $self) {
148 0         0 $self->{'monitor_query'} = shift;
149             } else {
150 0         0 $master_monitor_query = shift;
151             }
152              
153             }
154 447497 100       687480 return ref($self) ? $self->{'monitor_query'} : $master_monitor_query;
155             }
156              
157             my %_query_log_times;
158             my $query_logging_fh = IO::Handle->new();
159             $query_logging_fh->fdopen(fileno(STDERR), 'w');
160             $query_logging_fh->autoflush(1);
161             sub query_logging_fh {
162 0 0   0 0 0 $query_logging_fh = $_[1] if @_ > 1;
163 0         0 return $query_logging_fh;
164             }
165              
166             sub _log_query_for_rule {
167 0 0   0   0 return if $UR::Object::Type::bootstrapping;
168 0         0 my $self = shift;
169 0         0 my($subject_class,$rule,$message) = @_;
170              
171 0         0 my $monitor_level;
172 0 0       0 return unless ($monitor_level = $self->monitor_query);
173 0 0 0     0 return if (substr($subject_class, 0,4) eq 'UR::' and $monitor_level < 2); # Don't log queries for internal classes
174              
175 0         0 my $elapsed_time = 0;
176 0 0       0 if (defined($rule)) {
177 0         0 my $time_now = Time::HiRes::time();
178 0 0       0 if (! exists $_query_log_times{$rule->id}) {
179 0         0 $_query_log_times{$rule->id} = $time_now;
180             } else {
181 0         0 $elapsed_time = $time_now - $_query_log_times{$rule->id};
182             }
183             }
184              
185 0 0       0 if ($elapsed_time) {
186 0         0 $message .= sprintf(" Elapsed %.4f s", $elapsed_time);
187             }
188 0         0 $query_logging_fh->print($message."\n");
189             }
190              
191             sub _log_done_elapsed_time_for_rule {
192 0     0   0 my($self, $rule) = @_;
193              
194 0         0 delete $_query_log_times{$rule->id};
195             }
196              
197              
198             sub resolve_data_sources_for_class_meta_and_rule {
199 270054     270054 1 225690 my $self = shift;
200 270054         196268 my $class_meta = shift;
201 270054         193054 my $boolexpr = shift; ## ignored in the default case
202              
203 270054         525518 my $class_name = $class_meta->class_name;
204              
205             # These are some hard-coded cases for splitting up class-classes
206             # and data dictionary entities into namespace-specific meta DBs.
207             # Maybe there's some more generic way to move this somewhere else
208              
209             # FIXME This part is commented out for the moment. When class info is in the
210             # Meta DBs, then try getting this to work
211             #if ($class_name eq 'UR::Object::Type') {
212             # my %params = $boolexpr->legacy_params_hash;
213             # my($namespace) = ($params->{'class_name'} =~ m/^(\w+?)::/);
214             # $namespace ||= $params->{'class_name'}; # In case the class name is just the namespace
215             #
216             # return $namespace . '::DataSource::Meta';
217             #}
218              
219 270054         221053 my $data_source;
220              
221             # For data dictionary items
222             # When the FileMux datasource is more generalized and works for
223             # any kind of underlying datasource, this code can move from here
224             # and into the base class for Meta datasources
225 270054 100       1092611 if ($class_name->isa('UR::DataSource::RDBMS::Entity')) {
226 1665         4620 my $params = $boolexpr->legacy_params_hash;
227 1665         1860 my $namespace;
228 1665 50 33     15437 if ($params->{'namespace'}) {
    50 33        
    0 0        
229 0         0 $namespace = $params->{'namespace'};
230 0         0 $data_source = $params->{'namespace'} . '::DataSource::Meta';
231              
232             } elsif ($params->{'data_source'} &&
233             ! ref($params->{'data_source'}) &&
234             $params->{'data_source'}->can('get_namespace')) {
235              
236 1665         18330 $namespace = $params->{'data_source'}->get_namespace;
237 1665         3307 $data_source = $namespace . '::DataSource::Meta';
238              
239             } elsif ($params->{'data_source'} &&
240             ref($params->{'data_source'}) eq 'ARRAY') {
241 0         0 my %namespaces = map { $_->get_namespace => 1 } @{$params->{'data_source'}};
  0         0  
  0         0  
242 0 0       0 unless (scalar(keys %namespaces) == 1) {
243 0         0 Carp::confess("get() across multiple namespaces is not supported");
244             }
245 0         0 $namespace = $params->{'data_source'}->[0]->get_namespace;
246 0         0 $data_source = $namespace . '::DataSource::Meta';
247             } else {
248 0         0 Carp::confess("Required parameter (namespace or data_source_id) missing");
249             #$data_source = 'UR::DataSource::Meta';
250             }
251              
252 1665 50       5114 if (my $exists = UR::Object::Type->get($data_source)) {
253             # switch the terminology above to stop using $data_source for the class name
254             # now it's the object..
255 1665         41884 $data_source = $data_source->get();
256             }
257             else {
258 0         0 $self->warning_message("no data source $data_source: generating for $namespace...");
259 0         0 UR::DataSource::Meta->generate_for_namespace($namespace);
260 0         0 $data_source = $data_source->get();
261             }
262              
263 1665 50       6215 unless ($data_source) {
264 0         0 Carp::confess "Failed to find or generate a data source for meta data for namespace $namespace!";
265             }
266              
267             } else {
268 268389         501006 $data_source = $class_meta->data_source;
269             }
270              
271 270054 100       376489 if ($data_source) {
272 4474         15907 $data_source = $data_source->resolve_data_sources_for_rule($boolexpr);
273             }
274 270054         323232 return $data_source;
275             }
276              
277              
278             # this is used to determine which data source an object should be saved-to
279              
280             sub resolve_data_source_for_object {
281 572     572 1 602 my $self = shift;
282 572         516 my $object = shift;
283 572         1239 my $class_meta = $object->__meta__;
284 572         1230 my $class_name = $class_meta->class_name;
285            
286 572 100 66     5335 if ($class_name->isa('UR::DataSource::RDBMS::Entity') || $class_name->isa('UR::DataSource::RDBMS::Entity::Ghost')) {
287 32         73 my $data_source = $object->data_source;
288 32         171 my($namespace) = ($data_source =~ m/(^\w+?)::DataSource/);
289 32 50       61 unless ($namespace) {
290 0         0 Carp::croak("Can't resolve data source for object of type $class_name: The object's namespace could not be inferred from its data_source $data_source");
291             }
292 32         59 my $ds_name = $namespace . '::DataSource::Meta';
293 32         636 return $ds_name->get();
294             }
295              
296             # Default behavior
297 540         1366 my $ds = $class_meta->data_source;
298 540         700 return $ds;
299             }
300              
301             # this turns on and off light caching (weak refs)
302              
303             sub _light_cache {
304 0 0   0   0 if (@_ > 1) {
305 0         0 $light_cache = $_[1];
306 0         0 manage_objects_may_go_out_of_scope();
307             }
308 0         0 return $light_cache;
309             }
310              
311              
312             # Given a rule, and a property name not mentioned in the rule,
313             # can we infer the value of that property from what actually is in the rule?
314              
315             sub infer_property_value_from_rule {
316 37     37 1 948 my($self,$wanted_property_name,$rule) = @_;
317              
318             # First, the easy case... The property is directly mentioned in the rule
319 37 100       95 if ($rule->specifies_value_for($wanted_property_name)) {
320 23         70 return $rule->value_for($wanted_property_name);
321             }
322              
323 14         39 my $subject_class_name = $rule->subject_class_name;
324 14         41 my $subject_class_meta = UR::Object::Type->get($subject_class_name);
325 14         54 my $wanted_property_meta = $subject_class_meta->property_meta_for_name($wanted_property_name);
326 14 50       31 unless ($wanted_property_meta) {
327 0         0 $self->error_message("Class $subject_class_name has no property named $wanted_property_name");
328 0         0 return;
329             }
330              
331 14 100       35 if ($wanted_property_meta->is_delegated) {
332 5         25 $self->context_return($self->_infer_delegated_property_from_rule($wanted_property_name,$rule));
333             } else {
334 9         38 $self->context_return($self->_infer_direct_property_from_rule($wanted_property_name,$rule));
335             }
336             }
337              
338             # These are things that are changes to the program state, but not changes to the object instance
339             # so they shouldn't be counted in the object's change_count
340             my %changes_not_counted = map { $_ => 1 } qw(load define unload query connect);
341             sub add_change_to_transaction_log {
342 237100     237100 0 310550 my ($self,$subject, $property, @data) = @_;
343              
344 237100         180162 my ($class,$id);
345 237100 100       337041 if (ref($subject)) {
346 237068         196773 $class = ref($subject);
347 237068         503581 $id = $subject->id;
348 237068 100       460899 unless ($changes_not_counted{$property} ) {
349 8315         12324 $subject->{_change_count}++;
350             #print "changing $subject $property @data\n";
351             }
352             }
353             else {
354 32         52 $class = $subject;
355 32         49 $subject = undef;
356 32         45 $id = undef;
357             }
358              
359 237100 100       345674 if ($UR::Context::Transaction::log_all_changes) {
360             # eventually all calls to __signal_change__ will go directly here
361 5892         18777 UR::Context::Transaction->log_change($subject, $class, $id, $property, @data);
362             }
363              
364 237100 100       526688 if (my $index_list = $UR::Object::Index::all_by_class_name_and_property_name{$class}{$property}) {
365 99698 100 100     391247 unless ($property eq 'create' or $property eq 'load' or $property eq 'define') {
      66        
366 968         1359 for my $index (@$index_list) {
367 1275         4146 $index->_remove_object(
368             $subject,
369             { $property => $data[0] }
370             )
371             }
372             }
373            
374 99698 100 100     293745 unless ($property eq 'delete' or $property eq 'unload') {
375 98797         116939 for my $index (@$index_list) {
376 196933         346082 $index->_add_object($subject)
377             }
378             }
379             }
380             }
381              
382             our $sig_depth = 0;
383             my %subscription_classes;
384             sub send_notification_to_observers {
385 299947     299947 0 339646 my ($self,$subject, $property, @data) = @_;
386              
387 299947         202116 my ($class,$id);
388 299947 100       356600 if (ref($subject)) {
389 238444         186747 $class = ref($subject);
390 238444         323577 $id = $subject->id;
391             } else {
392 61503         48604 $class = $subject;
393             }
394              
395 299947         314135 my $check_classes = $subscription_classes{$class};
396 299947 100       429064 unless ($check_classes) {
397             $subscription_classes{$class} = $check_classes = [
398             $class
399             ? (
400             $class,
401 19055 50       454942 (grep { $_->isa("UR::Object") } $class->inheritance),
  71835         230729  
402             ''
403             )
404             : ('')
405             ];
406             }
407 299947 50       486734 my @check_properties = ($property ? ($property, '') : ('') );
408 299947 100       462854 my @check_ids = (defined($id) ? ($id, '') : ('') );
409              
410             my @matches =
411 47715         60621 map { @$_ }
412 95455 100       90199 grep { defined $_ } map { defined($id) ? @$_{@check_ids} : values(%$_) }
  47743         91207  
413 98782         96158 grep { defined $_ } map { @$_{@check_properties} }
  49391         82538  
414 299947         573217 grep { defined $_ } @$UR::Context::all_change_subscriptions{@$check_classes};
  947659         1049787  
415              
416 299947 100       747196 return unless @matches;
417              
418 47624         38974 $sig_depth++;
419 47624 100       70341 if (@matches > 1) {
420 266     266   434660 no warnings;
  266         451  
  266         46812  
421             # sort by priority
422 88         335 @matches = sort { $a->[2] <=> $b->[2] } @matches;
  270         377  
423             };
424            
425 47624         48338 foreach my $callback_info (@matches) {
426 47804         70513 my ($callback, $note, undef, $id, $once) = @$callback_info;
427 47804 100       63656 UR::Observer->get($id)->delete() if $once;
428 47804         102405 $callback->($subject, $property, @data);
429             }
430              
431 47601         38907 $sig_depth--;
432              
433 47601         87399 return scalar(@matches);
434             }
435              
436              
437             sub query {
438 253615     253615 0 225845 my $self = shift;
439              
440             # Fast optimization for the default case.
441 253615 100 66     786155 if ( ( !ref($self) or ! $self->query_underlying_context)
      33        
442             and ! Scalar::Util::blessed($_[1]) # This happens when query() is called with a class name and boolexpr
443             ) {
444 266     266   9431 no warnings;
  266         446  
  266         1670738  
445 248987 100       447019 if (exists $UR::Context::all_objects_loaded->{$_[0]}) {
446 247549         355569 my $is_monitor_query = $self->monitor_query;
447 247549 100       731184 if (defined(my $obj = $UR::Context::all_objects_loaded->{$_[0]}->{$_[1]})) {
    50          
448             # Matched the class and ID directly - pull it right out of the cache
449 61357 50       85635 if ($is_monitor_query) {
450 0         0 $self->_log_query_for_rule($_[0], undef, Carp::shortmess("QUERY: class $_[0] by ID $_[1]"));
451 0         0 $self->_log_query_for_rule($_[0], undef, "QUERY: matched 1 cached object\nQUERY: returning 1 object\n\n");
452             }
453              
454 61357         81772 $obj->{'__get_serial'} = $UR::Context::GET_COUNTER++;
455 61357         169808 return $obj;
456              
457             } elsif (my $subclasses = $UR::Object::Type::_init_subclasses_loaded{$_[0]}) {
458             # Check subclasses of the requested class, along with the ID
459             # yes, it only goes one level deep. This should catch enough cases to be worth it.
460             # Deeper searches will be covered by get_objects_for_class_and_rule()
461 186192         232212 foreach my $subclass (@$subclasses) {
462 3003549 100 100     9620127 if (exists $UR::Context::all_objects_loaded->{$subclass} and
463             my $obj = $UR::Context::all_objects_loaded->{$subclass}->{$_[1]}
464             ) {
465 74888 50       104421 if ($is_monitor_query) {
466 0         0 $self->_log_query_for_rule($_[0], undef, Carp::shortmess("QUERY: class $_[0] by ID $_[1]"));
467 0         0 $self->_log_query_for_rule($_[0], undef, "QUERY: matched 1 cached object in subclass $subclass\nQUERY: returning 1 object\n\n");
468             }
469              
470 74888         92520 $obj->{'__get_serial'} = $UR::Context::GET_COUNTER++;
471 74888         201942 return $obj;
472             }
473             }
474             }
475             }
476             };
477              
478             # Normal logic for finding objects smartly is below.
479              
480 117370         125740 my $class = shift;
481              
482             # Handle the case in which this is called as an object method.
483             # Functionality is completely different.
484              
485 117370 100       175443 if(ref($class)) {
486 73         83 my @rvals;
487 73         114 foreach my $prop (@_) {
488 270         711 push(@rvals, $class->$prop());
489             }
490              
491 73 50       153 if(wantarray) {
492 73         310 return @rvals;
493             }
494             else {
495 0         0 return \@rvals;
496             }
497             }
498            
499 117297         335941 my ($rule, @extra) = UR::BoolExpr->resolve($class,@_);
500            
501 117297 100       197398 if (@extra) {
502             # remove this and have the developer go to the datasource
503 11 100 100     68 if (scalar @extra == 2 and ($extra[0] eq "sql" or $extra[0] eq 'sql in')) {
      33        
504 8         23 return $UR::Context::current->_get_objects_for_class_and_sql($class,$extra[1]);
505             }
506            
507             # keep this part: let the sub-class handle special params if it can
508 3         35 return $class->get_with_special_parameters($rule, @extra);
509             }
510              
511             # This is here for bootstrapping reasons: we must be able to load class singletons
512             # in order to have metadata for regular loading....
513             # UR::DataSource::QueryPlan isa UR::Value (which has custom loading logic), but we need to be able to generate
514             # a QueryPlan independant of the normal loading process, otherwise there'd be endless recursion (Can't generate a QueryPlan
515             # for a QueryPlan without generating a QueryPlan first....)
516 117286 100 100     288376 if (!$rule->has_meta_options and ($class->isa("UR::Object::Type") or $class->isa("UR::Singleton") or $class->isa("UR::DataSource::QueryPlan"))) {
      66        
517 56924         117441 my $normalized_rule = $rule->normalize;
518 56924         192837 my @objects = $class->_load($normalized_rule);
519            
520 56920 50       94982 return unless defined wantarray;
521 56920 100       135443 return @objects if wantarray;
522            
523 30665 50 33     70845 if ( @objects > 1 and defined(wantarray)) {
524 0         0 Carp::croak("Multiple matches for $class query called in scalar context. $rule matches " . scalar(@objects). " objects");
525             }
526            
527 30665         90153 return $objects[0];
528             }
529              
530 60362         141340 return $UR::Context::current->get_objects_for_class_and_rule($class, $rule);
531             }
532              
533             sub _resolve_id_for_class_and_rule {
534 725     725   1211 my ($self,$class_meta,$rule) = @_;
535            
536 725         2483 my $class = $class_meta->class_name;
537 725         881 my $id;
538 725 50       2551 my @id_property_names = $class_meta->id_property_names
539             or Carp::confess( # Bad should be at least one
540             "No id property names for class ($class). This should not have happened."
541             );
542              
543 725 100       1759 if ( @id_property_names == 1 ) { # only 1 - try to auto generate
544 716         3374 $id = $class_meta->autogenerate_new_object_id($rule);
545 714 50       2088 unless ( defined $id ) {
546 0         0 $class->error_message("Failed to auto-generate an ID for single ID property class ($class)");
547 0         0 return;
548             }
549             }
550             else { # multiple
551             # Try to give a useful message by getting id prop names that are not deinfed
552 9         9 my @missed_names;
553 9         12 for my $name ( @id_property_names ) {
554 18 100       35 push @missed_names, $name unless $rule->specifies_value_for($name);
555             }
556 9 50       16 if ( @missed_names ) { # Ok - prob w/ class def, list the ones we missed
557 9         59 $class->error_message("Attempt to create $class with multiple ids without these properties: ".join(', ', @missed_names));
558 9         22 return;
559             }
560             else { # Bad - something is really wrong...
561 0         0 Carp::confess("Attempt to create $class failed to resolve id from underlying id properties.");
562             }
563             }
564            
565 714         1562 return $id;
566             }
567              
568             our $construction_method = 'create';
569              
570             # Pulled out the complicated code of create_entity() below that deals with
571             # abstract classes and subclassify_by
572             sub _create_entity_from_abstract_class {
573 34     34   49 my $self = shift;
574              
575 34         43 my $class = shift;
576 34         124 my $class_meta = $class->__meta__;
577 34         124 my($rule, %extra) = UR::BoolExpr->resolve_normalized($class, @_);
578              
579             # If we can easily determine the correct subclass, delegate to that subclass' create()
580 34         163 my $subclassify_by = $class_meta->subclassify_by();
581 34 50       94 unless (defined $subclassify_by) {
582 0         0 Carp::croak("Can't call $construction_method on abstract class $class without a subclassify_by property");
583             }
584              
585 34         107 my $sub_class_name = $rule->value_for($subclassify_by);
586 34 100       96 unless (defined $sub_class_name) {
587             # The subclassification wasn't included in the rule
588 7         52 my $property_meta = $class_meta->property($subclassify_by);
589 7 50       16 unless ($property_meta) {
590 0         0 Carp::croak("Abstract class $class has subclassify_by $subclassify_by, but no property exists by that name");
591             }
592              
593             # There are a few different ways the property can supply a value for subclassify_by...
594             # The sure-fire way to get a value is to go ahead an instantiate the object into the
595             # base/abstract class, and then we can just call the property as a method. There's
596             # a lot of overhead in that, so first we'll try some of the easier, common-case ways
597              
598 7 100 66     30 if ($property_meta->default_value) {
    100 33        
    50          
    100          
599             # The property has a default value
600 1         3 $sub_class_name = $property_meta->default_value();
601              
602             } elsif ($property_meta->is_calculated and ref($property_meta->calculate) eq 'CODE') {
603             # It's calculated via a coderef
604              
605 2         9 my $calculate_from = $property_meta->calculate_from;
606 2         4 my @calculate_params;
607 2         5 foreach my $prop_name ( @$calculate_from ) {
608             # The things in calculate_from must appear in the rule
609 2 100       8 unless ($rule->specifies_value_for($prop_name)) {
610 1         7 Carp::croak("Class $class subclassify_by calculation property '$subclassify_by' "
611             . "requires '$prop_name' in the $construction_method() params\n"
612             . "Params were: " . UR::Util->display_string_for_params_list($rule->params_list));
613             }
614 1         5 push @calculate_params, $rule->value_for($prop_name);
615             }
616              
617 1         6 my $sub = $property_meta->calculate;
618 1 50       5 unless ($sub) {
619 0         0 Carp::croak("Can't use undefined value as subroutine reference while resolving "
620             . "value for class $class calculated property '$subclassify_by'");
621             }
622 1         6 $sub_class_name = $sub->(@calculate_params);
623              
624             } elsif ($property_meta->is_calculated and !ref($property_meta->calculate)) {
625             # It's calculated via a string that's eval-ed
626 0         0 Carp::croak("Can't use a non-coderef as a calculation for class $class subclassify_by");
627              
628             } elsif ($property_meta->is_delegated) {
629             #Carp::croak("Delegated properties are not supported for subclassifying $class with property '$subclassify_by'");
630 3         20 my @values = $self->infer_property_value_from_rule($subclassify_by, $rule);
631 3 50       13 if (! @values ) {
    100          
632 0         0 Carp::croak("Invalid parameters for $class->$construction_method(): "
633             . "Couldn't infer a value for indirect property '$subclassify_by' via rule $rule");
634             } elsif (@values > 1) {
635 1         9 Carp::croak("Invalid parameters for $class->$construction_method(): "
636             . "Infering a value for property '$subclassify_by' via rule $rule returned multiple values: "
637             . join(', ', @values));
638             } else {
639 2         4 $sub_class_name = $values[0];
640             }
641              
642             } else {
643 1         227 Carp::croak("Can't use undefined value as a subclass name for $class property '$subclassify_by'");
644             }
645             }
646              
647 31 50       97 unless (defined $sub_class_name) {
648 0         0 Carp::croak("Invalid parameters for $class->$construction_method(): "
649             . "Can't use undefined value as a subclass name for param '$subclassify_by'");
650             }
651 31 50       94 if ($sub_class_name eq $class) {
652 0         0 Carp::croak("Invalid parameters for $class->$construction_method(): "
653             . "Value for $subclassify_by cannot be the same as the original class");
654             }
655 31 100       184 unless ($sub_class_name->isa($class)) {
656 1         205 Carp::croak("Invalid parameters for $class->$construction_method(): "
657             . "Class $sub_class_name is not a subclass of $class");
658             }
659 30         229 return $sub_class_name->$construction_method(@_);
660             }
661              
662             my %memos;
663             my %memos2;
664             sub create_entity {
665 4084     4084 0 5367 my $self = shift;
666              
667 4084         4321 my $class = shift;
668              
669 4084         6091 my $memo = $memos{$class};
670 4084 100       7835 unless ($memo) {
671             # we only want to grab the data necessary for object construction once
672             # this occurs the first time a new object is created for a given class
673            
674 926         3355 my $class_meta = $class->__meta__;
675 926         6064 my @inheritance = reverse ($class_meta, $class_meta->ancestry_class_metas);
676              
677             # %property_objects maps property names to UR::Object::Property objects
678             # by going through the reversed list of UR::Object::Type objects below
679             # We set up this hash to have the correct property objects for each property
680             # name. This is important in the case of property name overlap via
681             # inheritance. The property object used should be the one "closest"
682             # to the class. In other words, a property directly on the class gets
683             # used instead of an inherited one.
684 926         1873 my %property_objects;
685             my %direct_properties;
686 0         0 my %indirect_properties;
687 0         0 my %set_properties;
688 0         0 my %default_values;
689 0         0 my %default_value_requires_query;
690 0         0 my %default_value_requires_call;
691 0         0 my %immutable_properties;
692 0         0 my @deep_copy_default_values;
693              
694 926         1806 for my $co ( @inheritance ) {
695             # Reverse map the ID into property values.
696             # This has to occur for all subclasses which represent table rows.
697            
698             # deal with %property_objects
699 2579         13396 my @property_objects = $co->direct_property_metas;
700 2579         3978 my @property_names = map { $_->property_name } @property_objects;
  7110         10965  
701 2579         7688 @property_objects{@property_names} = @property_objects;
702            
703 2579         3707 foreach my $prop ( @property_objects ) {
704 7110         11188 my $name = $prop->property_name;
705            
706 7110 50       11197 unless (defined $name) {
707 0         0 Carp::confess("no name on property for class " . $co->class_name . "?\n" . Data::Dumper::Dumper($prop));
708             }
709              
710 7110         12009 my $default_value = $prop->default_value;
711 7110 100       10218 if (defined $default_value) {
712 1246 100 100     2981 if ($prop->data_type and $prop->_data_type_as_class_name eq $prop->data_type and $prop->_data_type_as_class_name->can("get")) {
    100 100        
713             # an ID or other query params in hash/array form return an object or objects
714 6         52 $default_value_requires_query{$name} = $default_value;
715             }
716             elsif (ref($default_value)) {
717             #warn (
718             # "a reference value $default_value is used as a default on "
719             # . $co->class_name
720             # . " forcing a copy during construction "
721             # . " of $class $name..."
722             #);
723 25         87 push @deep_copy_default_values, $name;
724             }
725 1246         2898 $default_values{$name} = $default_value;
726             }
727              
728 7110 100       12534 if ($prop->calculated_default) {
729 2         4 $default_value_requires_call{$name} = $prop->calculated_default;
730             }
731            
732 7110 100       11089 if ($prop->is_many) {
    100          
733 119         212 $set_properties{$name} = $prop;
734             }
735             elsif ($prop->is_delegated) {
736 742         1557 $indirect_properties{$name} = $prop;
737             }
738             else {
739 6249         7324 $direct_properties{$name} = $prop;
740             }
741            
742 7110 100       11682 unless ($prop->is_mutable) {
743 838         1865 $immutable_properties{$name} = 1;
744             }
745             }
746             }
747            
748 926         2948 my @indirect_property_names = keys %indirect_properties;
749 926         3228 my @direct_property_names = keys %direct_properties;
750              
751 926         1518 my @subclassify_by_methods;
752 926         1779 foreach my $co ( @inheritance ) {
753             # If this class inherits from something with subclassify_by, make sure the param
754             # actually matches. If it's not supplied, then set it to the same as the class create()
755             # is being called on
756 2579 100 100     5896 if ( $class ne $co->class_name
      100        
757             and $co->is_abstract
758             and my $method = $co->subclassify_by
759             ) {
760 23         57 push @subclassify_by_methods, $method;
761             }
762             }
763              
764 926 100       7545 $memos{$class} = $memo = [
765             $class_meta,
766             $class_meta->first_sub_classification_method_name,
767             $class_meta->is_abstract,
768             \@inheritance,
769             \%property_objects,
770             \%direct_properties,
771             \%indirect_properties,
772             \%set_properties,
773             \%immutable_properties,
774             \@subclassify_by_methods,
775             \%default_values,
776             (@deep_copy_default_values ? \@deep_copy_default_values : undef),
777             \%default_value_requires_query,
778             \%default_value_requires_call,
779             ];
780             }
781            
782             my (
783 4084         9769 $class_meta,
784             $first_sub_classification_method_name,
785             $is_abstract,
786             $inheritance,
787             $property_objects,
788             $direct_properties,
789             $indirect_properties,
790             $set_properties,
791             $immutable_properties,
792             $subclassify_by_methods,
793             $initial_default_values,
794             $deep_copy_default_values,
795             $default_value_requires_query,
796             $initial_default_value_requires_call,
797             ) = @$memo;
798              
799             # The old way of automagic subclassing...
800             # The class specifies that we should call a class method (sub_classification_method_name)
801             # to determine the correct subclass
802 4084 50       7241 if ($first_sub_classification_method_name) {
803 0         0 my $sub_class_name = $class->$first_sub_classification_method_name(@_);
804 0 0 0     0 if (defined($sub_class_name) and ($sub_class_name ne $class)) {
805             # delegate to the sub-class to create the object
806 0 0       0 unless ($sub_class_name->can($construction_method)) {
807 0         0 Carp::croak("Can't locate object method '$construction_method' via package '$sub_class_name' "
808             . "while resolving proper subclass for $class during $construction_method");
809              
810             }
811 0         0 return $sub_class_name->$construction_method(@_);
812             }
813             # fall through if the class names match
814             }
815              
816 4084 100       6974 if ($is_abstract) {
817             # The new way of automagic subclassing. The class specifies a property (subclassify_by)
818             # that holds/returns the correct subclass name
819 34         142 return $self->_create_entity_from_abstract_class($class, @_);
820             }
821              
822             # normal case: make a rule out of the passed-in params
823             # rather than normalizing the rule, we just do the extension part which is fast
824 4050         12396 my $rule = UR::BoolExpr->resolve($class, @_);
825 4049         9278 my $template = $rule->template;
826              
827 4049         9513 my $params = { $rule->_params_list, $template->extend_params_list_for_values(@{$rule->{values}}) };
  4049         16545  
828 4049 100       10815 if (my $a = $template->{_ambiguous_keys}) {
829 104         185 my $p = $template->{_ambiguous_property_names};
830 104         425 @$params{@$p} = delete @$params{@$a};
831             }
832              
833 4049         5398 my $id = $params->{id};
834 4049 100       7703 unless (defined $id) {
835 725         3178 $id = $self->_resolve_id_for_class_and_rule($class_meta,$rule);
836 723 100       1573 unless ($id) {
837 9         33 return;
838             }
839 714         3514 $rule = UR::BoolExpr->resolve_normalized($class, %$params, id => $id);
840 714         1479 $params = { $rule->params_list }; ;
841             }
842              
843 4038         9778 my %default_value_requires_call = %$initial_default_value_requires_call;
844 4038         9429 delete @default_value_requires_call{ keys %$params };
845              
846             # handle postprocessing default values
847            
848 4038         9194 my %default_values = %$initial_default_values;
849            
850 4038         7163 for my $name (keys %$default_value_requires_query) {
851 9         16 my @id_by;
852 9 100       55 if (my $id_by = $property_objects->{$name}->id_by) {
853 3 50       15 @id_by = (ref($id_by) ? @$id_by : ($id_by));
854             }
855              
856 9 100       40 if ($params->{$name}) {
    100          
857 2         5 delete $default_values{$name};
858             }
859             elsif (@$params{@id_by}) {
860             # some or all of the id is present
861             # don't fall back to the default
862 1         7 for my $id_by (@id_by) {
863 1 50       4 delete $default_values{$id_by} if exists $params->{$id_by};
864             }
865 1         2 delete $default_values{$name};
866             }
867             else {
868 6         11 my $query = $default_value_requires_query->{$name};
869 6         8 my @query;
870 6 100       17 if (ref($query) eq 'HASH') {
871             # queries come in as a hash
872 3         10 @query = %$query;
873             }
874             else {
875             # an ID or a boolean expression
876 3         4 @query = ($query);
877             }
878 6         12 my $prop = $property_objects->{$name};
879 6         25 my $class = $prop->_data_type_as_class_name;
880 6         10 eval {
881 6 100       24 if ($prop->is_many) {
882 2         12 $default_values{$name} = [ $class->get(@query) ];
883             }
884             else {
885 4         24 $default_values{$name} = $class->get(@query);
886             }
887             };
888 6 50       27 if ($@) {
889 0         0 warn "error setting " . $prop->class_name . " " . $prop->property_name . " to default_value from query $query for type $class!";
890             };
891             }
892             }
893              
894 4038 100       7138 if ($deep_copy_default_values) {
895 60         102 for my $name (@$deep_copy_default_values) {
896 235 100       571 if ($params->{$name}) {
897 175         190 delete $default_values{$name};
898             }
899             else {
900 60         206 $default_values{$name} = UR::Util::deep_copy($default_values{$name});
901             }
902             }
903             }
904              
905             # @extra is extra values gotten by inheritance
906 4038         6091 my @extra;
907              
908 4038         4862 my $indirect_values = {};
909 4038         6534 for my $property_name (keys %$indirect_properties) {
910             # pull indirect values out of the constructor hash
911             # so we can apply them separately after making the object
912 2088 100       6239 if ( exists $params->{ $property_name } ) {
    100          
913 13         32 $indirect_values->{ $property_name } = delete $params->{ $property_name };
914 13         25 delete $default_values{$property_name};
915             }
916             elsif (exists $default_values{$property_name}) {
917 8         25 $indirect_values->{ $property_name } = delete $default_values{$property_name};
918             }
919             }
920              
921             # if the indirect property is immutable, but it is via something which is
922             # mutable, we use those values to get or create the bridge.
923 4038         3991 my %indirect_immutable_properties_via;
924 4038         6420 for my $property_name (keys %$indirect_values) {
925 21 100       73 if ($immutable_properties->{$property_name}) {
926 17         26 my $meta = $indirect_properties->{$property_name};
927 17 50       46 next unless $meta; # not indirect
928 17         58 my $via = $meta->via;
929 17 100       147 next unless $via; # not a via/to (id_by or reverse_id_by)
930 15         44 $indirect_immutable_properties_via{$via}{$property_name} = delete $indirect_values->{$property_name};
931             }
932             }
933              
934 4038         5768 for my $via (keys %indirect_immutable_properties_via) {
935 15         53 my $via_property_meta = $class_meta->property_meta_for_name($via);
936 15         21 my ($source_indirect_property, $source_value) = each %{$indirect_immutable_properties_via{$via}}; # There'll only ever be one key/value
  15         42  
937              
938 15 50       40 unless ($via_property_meta) {
939 0         0 Carp::croak("No metadata for class $class property $via while resolving indirect value for property $source_indirect_property");
940             }
941              
942 15         35 my $indirect_property_meta = $class_meta->property_meta_for_name($source_indirect_property);
943 15 50       35 unless ($indirect_property_meta) {
944 0         0 Carp::croak("No metadata for class $class property $source_indirect_property while resolving indirect value for property $source_indirect_property");
945             }
946              
947 15 100       53 unless ($indirect_property_meta->to) {
948             # We're probably dealing with a subclassify_by property where the subclass has
949             # implicitly overridden the indirect property in the parent class with a constant-value
950             # property in the subclass. Try asking the parent class about a property of the same name
951 5         23 ($indirect_property_meta) = grep { $_->property_name eq $indirect_property_meta->property_name } $class_meta->ancestry_property_metas();
  25         47  
952 5 50 33     20 unless ($indirect_property_meta and $indirect_property_meta->to) {
953 0         0 Carp::croak("Can't resolve indirect relationship for possibly overridden property '$source_indirect_property'"
954             . " in class $class. Parent classes have no property named '$source_indirect_property'");
955             }
956             }
957 15         48 my $foreign_class = $via_property_meta->data_type;
958 15         38 my $foreign_property = $indirect_property_meta->to;
959 15         71 my $foreign_object = $foreign_class->get($foreign_property => $source_value);
960 15 100       40 unless ($foreign_object) {
961             # This will trigger recursion back here (into create_entity() ) if this property is multiply
962             # indirect, such as through a bridge object
963 6         29 $foreign_object = $foreign_class->create($foreign_property => $source_value);
964 6 50       18 unless ($foreign_object) {
965 0         0 Carp::croak("Can't create object of class $foreign_class with params ($foreign_property => '$source_value')"
966             . " while resolving indirect value for class $class property $source_indirect_property");
967             }
968             }
969              
970 15         68 my @joins = $indirect_property_meta->_resolve_join_chain();
971 15         27 my %local_properties_to_set;
972 15         24 foreach my $join ( @joins ) {
973 31 100       163 if ($join->{foreign_class}->isa("UR::Value")) {
974             # this final "join" is to the set of values available to the raw primitive type
975             # ...not what we really mean by delegation
976 11         16 next;
977             }
978 20         27 for (my $i = 0; $i < @{$join->{'source_property_names'}}; $i++) {
  38         87  
979 20         50 my $source_property_name = $join->{'source_property_names'}->[$i];
980 20 100       52 next unless (exists $direct_properties->{$source_property_name});
981 15         26 my $foreign_property_name = $join->{'foreign_property_names'}->[$i];
982 15         52 my $value = $foreign_object->$foreign_property_name;
983              
984 15 100 100     53 if ($rule->specifies_value_for($source_property_name)
985             and
986             $rule->value_for($source_property_name) ne $value)
987             {
988 2         15 Carp::croak("Invalid parameters for $class->$construction_method(): "
989             . "Conflicting values for property '$source_property_name'. $construction_method rule "
990             . "specifies value '" . $rule->value_for($source_property_name) . "' but "
991             . "indirect immutable property '$source_indirect_property' with value "
992             . "$source_value requires it to be '$value'");
993             }
994              
995 13         37 $local_properties_to_set{$source_property_name} = $value;
996             }
997             }
998             # transfer the values we resolved back into %$params
999 13         31 my @param_keys = keys %local_properties_to_set;
1000 13         56 @$params{@param_keys} = @local_properties_to_set{@param_keys};
1001             }
1002              
1003 4036         4797 my $set_values = {};
1004 4036         5965 for my $property_name (keys %$set_properties) {
1005 435 100       959 if (exists $params->{ $property_name }) {
1006 129         153 delete $default_values{ $property_name };
1007 129         282 $set_values->{ $property_name } = delete $params->{ $property_name };
1008             }
1009             }
1010              
1011 4036         13504 my $entity = $self->_construct_object($class, %default_values, %$params, @extra);
1012 4036 100       8452 return unless defined $entity;
1013 4032         9883 $self->add_change_to_transaction_log($entity, $construction_method);
1014 4032 100       8076 $self->add_change_to_transaction_log($entity, 'load') if $construction_method eq '__define__';
1015              
1016 4032         6576 for my $property_name ( keys %default_value_requires_call ) {
1017 3         4 my $method = $default_value_requires_call{$property_name};
1018 3         9 my $value = $method->($entity);
1019 3         15 $entity->$property_name($value);
1020             }
1021              
1022             # If a property is calculated + immutable, and it wasn't supplied in the params,
1023             # that means we need to run the calculation once and store the value in the
1024             # object as a read-only attribute
1025 4032         7021 foreach my $property_name ( keys %$immutable_properties ) {
1026 2449         3308 my $property_meta = $property_objects->{$property_name};
1027 2449 100 66     14436 if (!exists($params->{$property_name}) and $property_meta and $property_meta->is_calculated) {
      100        
1028 1         5 my $value = $entity->$property_name;
1029 1         3 $params->{$property_name} = $value;
1030             }
1031             }
1032              
1033 4032         6532 for my $subclassify_by (@$subclassify_by_methods) {
1034 62         225 my $param_value = $rule->value_for($subclassify_by);
1035 62 100       190 $param_value = eval { $entity->$subclassify_by } unless (defined $param_value);
  25         86  
1036 62 50       150 $param_value = $default_values{$subclassify_by} unless (defined $param_value);
1037            
1038 62 50       252 if (! defined $param_value) {
    100          
1039            
1040             # This should have been taken care of by the time we got here...
1041 0         0 Carp::croak("Invalid parameters for $class->$construction_method(): " .
1042             "Can't use an undefined value as a subclass name for param '$subclassify_by'");
1043              
1044             } elsif ($param_value ne $class) {
1045 5         900 Carp::croak("Invalid parameters for $class->$construction_method(): " .
1046             "Value for subclassifying param '$subclassify_by' " .
1047             "($param_value) does not match the class it was called on ($class)");
1048             }
1049             }
1050              
1051             # add items for any multi properties
1052 4027 100       7411 if (%$set_values) {
1053 100         260 for my $property_name (keys %$set_values) {
1054 129         231 my $meta = $set_properties->{$property_name};
1055 129         446 my $singular_name = $meta->singular_name;
1056 129         236 my $adder = 'add_' . $singular_name;
1057 129         195 my $value = $set_values->{$property_name};
1058 129 100       368 unless (ref($value) eq 'ARRAY') {
1059 2         4 $value = [$value];
1060             }
1061 129         244 for my $item (@$value) {
1062 186 50       486 if (ref($item) eq 'ARRAY') {
    100          
1063 0         0 $entity->$adder(@$item);
1064             }
1065             elsif (ref($item) eq 'HASH') {
1066 12         53 $entity->$adder(%$item);
1067             }
1068             else {
1069 174         629 $entity->$adder($item);
1070             }
1071             }
1072             }
1073             }
1074              
1075             # set any indirect mutable properties
1076 4027 100       7456 if (%$indirect_values) {
1077 6         19 for my $property_name (keys %$indirect_values) {
1078 6         33 $entity->$property_name($indirect_values->{$property_name});
1079             }
1080             }
1081              
1082 4027 100       7028 if (%$immutable_properties) {
1083 1605         7436 my @problems = $entity->__errors__();
1084 1605 100       3417 if (@problems) {
1085 74         96 my @errors_fatal_to_construction;
1086            
1087             my %problems_by_property_name;
1088 74         121 for my $problem (@problems) {
1089 89         84 my @problem_properties;
1090 89         2058 for my $name ($problem->properties) {
1091 89 50       215 if ($immutable_properties->{$name}) {
1092 0         0 push @problem_properties, $name;
1093             }
1094             }
1095 89 50       233 if (@problem_properties) {
1096 0         0 push @errors_fatal_to_construction, join(" and ", @problem_properties) . ': ' . $problem->desc;
1097             }
1098             }
1099            
1100 74 50       431 if (@errors_fatal_to_construction) {
1101 0         0 my $msg = 'Failed to $construction_method ' . $class . ' with invalid immutable properties:'
1102             . join("\n", @errors_fatal_to_construction);
1103             }
1104             }
1105             }
1106              
1107 4027         14003 $entity->__signal_observers__($construction_method);
1108 4027 100       9095 $entity->__signal_observers__('load') if $construction_method eq '__define__';
1109 4027         7479 $entity->{'__get_serial'} = $UR::Context::GET_COUNTER++;
1110 4027         4220 $UR::Context::all_objects_cache_size++;
1111 4027         20479 return $entity;
1112             }
1113              
1114             sub _construct_object {
1115 102530     102530   93217 my $self = shift;
1116 102530         78208 my $class = shift;
1117            
1118 102530         747369 my $params = { @_ };
1119              
1120 102530         115888 my $id = $params->{id};
1121 102530 50       163012 unless (defined($id)) {
1122 0         0 Carp::confess(
1123             "No ID specified (or incomplete id params) for $class _construct_object. Params were:\n"
1124             . Data::Dumper::Dumper($params)
1125             );
1126             }
1127              
1128 102530 100       224879 if ($UR::Context::all_objects_loaded->{$class}->{$id}) {
1129             # The object exists. This is not an exception for some reason?
1130             # We just return false to indicate that the object is not creatable.
1131 4         40 $class->error_message("An object of class $class already exists with id value '$id'");
1132 4         12 return;
1133             }
1134              
1135 102526         78949 my $object;
1136 102526 100       172683 if ($object = $UR::DeletedRef::all_objects_deleted->{$class}->{$id}) {
1137 14         70 UR::DeletedRef->resurrect($object);
1138 14         73 %$object = %$params;
1139             } else {
1140 102512         120635 $object = bless $params, $class;
1141             }
1142            
1143 102526 100       244294 if (my $ghost = $UR::Context::all_objects_loaded->{$class . "::Ghost"}->{$id}) {
1144             # we're making something which was previously deleted and is pending save.
1145             # we must capture the old db_committed data to ensure eventual saving is done correctly.
1146             # note this object's database state in the new object so saves occurr correctly,
1147             # as an update instead of an insert.
1148 10 100       33 if (my $committed_data = $ghost->{db_committed}) {
1149 9         36 $object->{db_committed} = { %$committed_data };
1150             }
1151              
1152 10 100       31 if (my $unsaved_data = $ghost->{'db_saved_uncommitted'}) {
1153 1         4 $object->{'db_saved_uncommitted'} = { %$unsaved_data };
1154             }
1155 10         34 $ghost->__signal_change__("delete");
1156 10         25 $self->_abandon_object($ghost);
1157             }
1158              
1159             # put the object in the master repository of objects for the application.
1160 102526         183214 $UR::Context::all_objects_loaded->{$class}{$id} = $object;
1161              
1162             # If we're using a light cache, weaken the reference.
1163 102526 50       150793 if ($light_cache) { # and substr($class,0,5) ne 'App::') {
1164 0         0 Scalar::Util::weaken($UR::Context::all_objects_loaded->{$class}->{$id});
1165             }
1166              
1167 102526         165503 return $object;
1168             }
1169              
1170             sub delete_entity {
1171 672     672 0 833 my ($self,$entity) = @_;
1172              
1173 672 50       1087 if (ref($entity)) {
1174             # Delete the specified object.
1175 672 100 66     2360 if ($entity->{db_committed} || $entity->{db_saved_uncommitted}) {
1176              
1177             # gather params for the ghost object
1178 74         100 my $do_data_source;
1179             my %ghost_params;
1180             #my @pn;
1181             #{ no warnings 'syntax';
1182             # @pn = grep { $_ ne 'data_source_id' || ($do_data_source=1 and 0) } # yes this really is '=' and not '=='
1183             # grep { exists $entity->{$_} }
1184             # $entity->__meta__->all_property_names;
1185             #}
1186 0         0 my(@prop_names, @many_prop_names);
1187 74         270 foreach my $prop_name ( $entity->__meta__->all_property_names) {
1188 422 100       650 next unless exists $entity->{$prop_name}; # skip non-directly-stored properties
1189 272 50       406 if ($prop_name eq 'data_source_id') {
1190 0         0 $do_data_source = 1;
1191 0         0 next;
1192             }
1193 272 50       380 if (ref($entity->{$prop_name}) eq 'ARRAY') {
1194 0         0 push @many_prop_names, $prop_name;
1195             } else {
1196 272         301 push @prop_names, $prop_name;
1197             }
1198             }
1199            
1200            
1201             # we're not really allowed to interrogate the data_source property directly
1202 74         345 @ghost_params{@prop_names} = $entity->get(@prop_names); # hrm doesn't work for is_many properties :(
1203 74         148 foreach my $prop_name ( @many_prop_names ) {
1204 0         0 my @values = $entity->get($prop_name);
1205 0         0 $ghost_params{$prop_name} = \@values;
1206             }
1207 74 50       155 if ($do_data_source) {
1208 0         0 $ghost_params{'data_source_id'} = $entity->{'data_source_id'};
1209             }
1210              
1211             # create ghost object
1212 74         297 my $ghost = $self->_construct_object($entity->ghost_class, id => $entity->id, %ghost_params);
1213 74 50       236 unless ($ghost) {
1214 0         0 Carp::confess("Failed to constructe a deletion record for an unsync'd delete.");
1215             }
1216 74         512 $ghost->__signal_change__("create");
1217              
1218 74         175 for my $com (qw(db_committed db_saved_uncommitted)) {
1219             $ghost->{$com} = $entity->{$com}
1220 148 100       564 if $entity->{$com};
1221             }
1222              
1223             }
1224 672         1582 $entity->__signal_change__('delete');
1225 672         1451 $self->_abandon_object($entity);
1226 672         1476 return $entity;
1227             }
1228             else {
1229 0         0 Carp::confess("Can't call delete as a class method.");
1230             }
1231             }
1232              
1233             sub _abandon_object {
1234 2305     2305   2027 my $self = shift;
1235 2305         2019 my $object = $_[0];
1236 2305         4241 my $class = $object->class;
1237 2305         3677 my $id = $object->id;
1238              
1239 2305 100       4077 if ($object->{'__get_serial'}) {
1240             # Keep a correct accounting of objects. This one is getting deleted by a method
1241             # other than UR::Context::prune_object_cache
1242 1998         1735 $UR::Context::all_objects_cache_size--;
1243             }
1244              
1245             # Remove the object from the main hash.
1246 2305         3185 delete $UR::Context::all_objects_loaded->{$class}->{$id};
1247 2305         2263 delete $UR::Context::all_objects_are_loaded->{$class};
1248              
1249             # Remove all of the load info it is using so it'll get re-loaded if asked for later
1250 2305 100       3843 if ($object->{'__load'}) {
1251 1651         1408 while (my ($template_id, $rules) = each %{ $object->{'__load'}} ) {
  3430         8099  
1252 1779         3026 foreach my $rule_id ( keys %$rules ) {
1253 1785         2040 delete $UR::Context::all_params_loaded->{$template_id}->{$rule_id};
1254              
1255 1785         4522 foreach my $fabricator ( UR::Context::ObjectFabricator->all_object_fabricators ) {
1256 23         82 $fabricator->delete_from_all_params_loaded($template_id, $rule_id);
1257             }
1258             }
1259             }
1260             }
1261              
1262             # Turn our $object reference into a UR::DeletedRef.
1263             # Further attempts to use it will result in readable errors.
1264             # The object can be resurrected.
1265 2305 50       3897 if ($ENV{'UR_DEBUG_OBJECT_RELEASE'}) {
1266 0         0 print STDERR "MEM DELETE object $object class ",$object->class," id ",$object->id,"\n";
1267             }
1268 2305         5978 UR::DeletedRef->bury($object);
1269              
1270 2305         2613 return $object;
1271             }
1272              
1273              
1274             # This one works when the rule specifies the value of an indirect property, and we want
1275             # the value of a direct property of the class
1276             sub _infer_direct_property_from_rule {
1277 9     9   13 my($self,$wanted_property_name,$rule) = @_;
1278              
1279 9         21 my $rule_template = $rule->template;
1280 9         28 my @properties_in_rule = $rule_template->_property_names; # FIXME - why is this method private?
1281 9         20 my $subject_class_name = $rule->subject_class_name;
1282 9         30 my $subject_class_meta = $subject_class_name->__meta__;
1283              
1284 9         7 my($alternate_class,$alternate_get_property, $alternate_wanted_property);
1285              
1286 0         0 my @r_values; # There may be multiple properties in the rule that will get to the wanted property
1287             PROPERTY_IN_RULE:
1288 9         16 foreach my $property_name ( @properties_in_rule) {
1289 9         22 my $property_meta = $subject_class_meta->property_meta_for_name($property_name);
1290 9   66     28 my $final_property_meta = $property_meta->final_property_meta || $property_meta;
1291 9         21 $alternate_get_property = $final_property_meta->property_name;
1292 9         19 $alternate_class = $final_property_meta->class_name;
1293              
1294 9 100       19 unless ($alternate_wanted_property) {
1295             # Either this was also a direct property of the rule, or there's no
1296             # obvious link between the indirect property and the wanted property.
1297             # the caller probably just should have done a get()
1298 8         9 $alternate_wanted_property = $wanted_property_name;
1299 8         9 $alternate_get_property = $property_name;
1300 8         8 $alternate_class = $subject_class_name;
1301             }
1302            
1303 9         20 my $value_from_rule = $rule->value_for($property_name);
1304 9         11 my @alternate_values;
1305 9         10 eval {
1306             # Inside an eval in case the get() throws an exception, the next
1307             # property in the rule may succeed
1308 9         22 my @alternate_objects = $self->query($alternate_class, $alternate_get_property => $value_from_rule );
1309 9         17 @alternate_values = map { $_->$alternate_wanted_property } @alternate_objects;
  7         17  
1310             };
1311 9 100       23 next unless (@alternate_values);
1312              
1313 6         11 push @r_values, \@alternate_values;
1314             }
1315              
1316 9 100       23 if (@r_values == 0) {
    50          
1317             # no solutions found
1318 3         21 return;
1319              
1320             } elsif (@r_values == 1) {
1321             # there was only one solution
1322 6         6 return @{$r_values[0]};
  6         34  
1323              
1324             } else {
1325             # multiple solutions. Only return the intersection of them all
1326             # FIXME - this totally won't work for properties that return objects, listrefs or hashrefs
1327             # FIXME - this only works for AND rules - for now, that's all that exist
1328 0         0 my %intersection = map { $_ => 1 } @{ shift @r_values };
  0         0  
  0         0  
1329 0         0 foreach my $list ( @r_values ) {
1330 0         0 %intersection = map { $_ => 1 } grep { $intersection{$_} } @$list;
  0         0  
  0         0  
1331             }
1332 0         0 return keys %intersection;
1333             }
1334             }
1335              
1336              
1337             # we want the value of a delegated property, and the rule specifies
1338             # a direct value
1339             sub _infer_delegated_property_from_rule {
1340 5     5   7 my($self, $wanted_property_name, $rule) = @_;
1341              
1342 5         15 my $rule_template = $rule->template;
1343 5         11 my $subject_class_name = $rule->subject_class_name;
1344 5         22 my $subject_class_meta = $subject_class_name->__meta__;
1345              
1346 5         14 my $wanted_property_meta = $subject_class_meta->property_meta_for_name($wanted_property_name);
1347 5 50       20 unless ($wanted_property_meta->via) {
1348 0         0 Carp::croak("There is no linking meta-property (via) on property $wanted_property_name on $subject_class_name");
1349             }
1350              
1351 5         21 my $linking_property_meta = $subject_class_meta->property_meta_for_name($wanted_property_meta->via);
1352 5         21 my $final_property_meta = $wanted_property_meta->final_property_meta;
1353              
1354 5 100       15 if ($linking_property_meta->reverse_as) {
1355 1         3 eval{ $linking_property_meta->data_type->class() }; # Load the class if it isn't already loaded
  1         3  
1356 1 50       3 if ($linking_property_meta->data_type ne $final_property_meta->class_name) {
1357 0         0 Carp::croak("UR::Context::_infer_delegated_property_from_rule() doesn't handle multiple levels of indiretion yet");
1358             }
1359             }
1360              
1361 5         17 my @rule_translation = $linking_property_meta->get_property_name_pairs_for_join();
1362              
1363 5         7 my %alternate_get_params;
1364 5         11 foreach my $pair ( @rule_translation ) {
1365 5         9 my $rule_param = $pair->[0];
1366 5 100       16 next unless ($rule_template->specifies_value_for($rule_param));
1367 4         6 my $alternate_param = $pair->[1];
1368              
1369 4         12 my $value = $rule->value_for($rule_param);
1370 4         12 $alternate_get_params{$alternate_param} = $value;
1371             }
1372              
1373 5         19 my $alternate_class = $final_property_meta->class_name;
1374 5         15 my $alternate_wanted_property = $wanted_property_meta->to;
1375 5         8 my @alternate_values;
1376 5         8 eval {
1377 5         17 my @alternate_objects = $self->query($alternate_class, %alternate_get_params);
1378 5         11 @alternate_values = map { $_->$alternate_wanted_property } @alternate_objects;
  7         108  
1379             };
1380 5         33 return @alternate_values;
1381             }
1382              
1383              
1384             sub object_cache_size_highwater {
1385 3     3 1 22 my $self = shift;
1386              
1387 3 50       9 if (@_) {
1388 3         6 my $value = shift;
1389 3         5 $cache_size_highwater = $value;
1390              
1391 3 100       9 if (defined $value) {
1392 2 50 66     11 if ($cache_size_lowwater and $value <= $cache_size_lowwater) {
1393 0         0 Carp::confess("Can't set the highwater mark less than or equal to the lowwater mark");
1394 0         0 return;
1395             }
1396 2         8 $self->prune_object_cache();
1397             }
1398 3         9 manage_objects_may_go_out_of_scope();
1399             }
1400 3         10 return $cache_size_highwater;
1401             }
1402              
1403             sub object_cache_size_lowwater {
1404 3     3 1 1315 my $self = shift;
1405 3 50       12 if (@_) {
1406 3         5 my $value = shift;
1407 3         4 $cache_size_lowwater = $value;
1408              
1409 3 50 66     18 if (defined($value) and $cache_size_highwater and $value >= $cache_size_highwater) {
      66        
1410 0         0 Carp::confess("Can't set the lowwater mark greater than or equal to the highwater mark");
1411 0         0 return;
1412             }
1413             }
1414 3         8 return $cache_size_lowwater;
1415             }
1416              
1417              
1418             sub get_data_sources_for_loaded_classes {
1419 4     4 0 8 my $class = shift;
1420              
1421 4         5 my %data_source_for_class;
1422 4         132 foreach my $class ( keys %$UR::Context::all_objects_loaded ) {
1423 362 50       473 next if (substr($class,0,-6) eq '::Type'); # skip class objects
1424              
1425 362 100       556 next unless exists $UR::Context::all_objects_loaded->{$class . '::Type'};
1426 112         182 my $class_meta = $UR::Context::all_objects_loaded->{$class . '::Type'}->{$class};
1427 112 100       206 next unless $class_meta;
1428 56 100       304 next unless ($class_meta->is_uncachable());
1429 24         78 $data_source_for_class{$class} = $class_meta->data_source_id;
1430             }
1431              
1432 4         38 return %data_source_for_class;
1433             }
1434              
1435              
1436             our $is_pruning = 0;
1437             sub prune_object_cache {
1438 42     42 1 51 my $self = shift;
1439              
1440 42 100       83 return if ($is_pruning); # Don't recurse into here
1441              
1442 5 100 33     28 return if (!defined($cache_size_highwater) or !defined($cache_size_lowwater));
1443 4 50       14 return unless ($all_objects_cache_size > $cache_size_highwater);
1444              
1445 4         8 $is_pruning = 1;
1446 4         6 my $t1;
1447 4 50 33     47 if ($ENV{'UR_DEBUG_OBJECT_RELEASE'} || $ENV{'UR_DEBUG_OBJECT_PRUNING'}) {
1448 0         0 $t1 = Time::HiRes::time();
1449 0         0 print STDERR Carp::longmess("MEM PRUNE begin at $t1 ",scalar(localtime($t1)),"\n");
1450             }
1451              
1452 4   50     23 my $index_id_sep = UR::Object::Index->__meta__->composite_id_separator() || "\t";
1453              
1454 4         19 my %data_source_for_class = $self->get_data_sources_for_loaded_classes;
1455              
1456             # NOTE: This pokes right into the object cache and futzes with Index IDs directly.
1457             # We can't get the Index objects though get() because we'd recurse right back into here
1458 4         7 my %indexes_by_class;
1459 4         7 foreach my $idx_id ( keys %{$UR::Context::all_objects_loaded->{'UR::Object::Index'}} ) {
  4         20  
1460 21         27 my $class = substr($idx_id, 0, index($idx_id, $index_id_sep));
1461 21 100       39 next unless exists $data_source_for_class{$class};
1462 10         8 push @{$indexes_by_class{$class}}, $UR::Context::all_objects_loaded->{'UR::Object::Index'}->{$idx_id};
  10         26  
1463             }
1464              
1465 4         8 my $deleted_count = 0;
1466 4         5 my $pass = 0;
1467              
1468 4 50       10 $cache_size_highwater = 1 if ($cache_size_highwater < 1);
1469 4 50       34 $cache_size_lowwater = 1 if ($cache_size_lowwater < 1);
1470              
1471             # Instead of sorting object cache by __get_serial, since we are trying to
1472             # conserve memory, we pass through the object cache reviewing chunks of older objects
1473             # first while working our way through the whole cache.
1474 4         8 my $target_serial = $cache_last_prune_serial;
1475              
1476 4         6 my $serial_range = ($GET_COUNTER - $target_serial);
1477 4         6 my $max_passes = 10;
1478 4         17 my $target_serial_increment = int($serial_range / $max_passes) + 1;
1479 4   66     29 while ($all_objects_cache_size > $cache_size_lowwater && $target_serial < $GET_COUNTER) {
1480 33         74 $pass++;
1481 33         31 $target_serial += $target_serial_increment;
1482              
1483 33         23 my @objects_to_prune;
1484 33         104 foreach my $class (keys %data_source_for_class) {
1485 198         208 my $objects_for_class = $UR::Context::all_objects_loaded->{$class};
1486 198   100     301 $indexes_by_class{$class} ||= [];
1487              
1488 198         569 foreach my $id ( keys ( %$objects_for_class ) ) {
1489 2215         1850 my $obj = $objects_for_class->{$id};
1490 2215 100       2535 next unless defined $obj; # object with this ID does not exist
1491 2173 100 33     2991 if (
      100        
      66        
1492             $obj->is_weakened
1493             || $obj->is_prunable && $obj->{__get_serial} && $obj->{__get_serial} <= $target_serial
1494             ) {
1495 287 50       385 if ($ENV{'UR_DEBUG_OBJECT_RELEASE'}) {
1496 0         0 print STDERR "MEM PRUNE object $obj class $class id $id\n";
1497             }
1498 287         215 push @objects_to_prune, $obj;
1499 287         288 $deleted_count++;
1500             }
1501             }
1502             }
1503 33         101 $self->_weaken_references_for_objects(\@objects_to_prune);
1504             }
1505 4         27 $is_pruning = 0;
1506              
1507 4         8 $cache_last_prune_serial = $target_serial;
1508 4 50 33     27 if ($ENV{'UR_DEBUG_OBJECT_RELEASE'} || $ENV{'UR_DEBUG_OBJECT_PRUNING'}) {
1509 0         0 my $t2 = Time::HiRes::time();
1510 0         0 printf("MEM PRUNE complete, $deleted_count objects marked after $pass passes in %.4f sec\n\n\n",$t2-$t1);
1511             }
1512 4 50       12 if ($all_objects_cache_size > $cache_size_lowwater) {
1513 0         0 Carp::carp "After several passes of pruning the object cache, there are still $all_objects_cache_size objects";
1514 0 0       0 if ($ENV{'UR_DEBUG_OBJECT_PRUNING'}) {
1515 0         0 warn "Top 10 classes by object count:\n" . $self->_object_cache_pruning_report;
1516             }
1517             }
1518 4         25 return 1;
1519             }
1520              
1521             sub _weaken_references_for_objects {
1522 45     45   59 my($self, $obj_list) = @_;
1523              
1524 45 50       109 Carp::croak('Argument to _weaken_references_to_objects must be an arrayref')
1525             unless ref($obj_list) eq 'ARRAY';
1526              
1527 45         43 my %indexes_by_class;
1528 45         142 foreach my $obj ( @$obj_list) {
1529 302         460 my $class = $obj->class;
1530 302   100     605 $indexes_by_class{ $class } ||= [ UR::Object::Index->get(indexed_class_name => $class) ];
1531              
1532 302         210 $_->weaken_reference_for_object($obj) foreach @{ $indexes_by_class{ $class }};
  302         509  
1533 302         289 delete $obj->{__get_serial};
1534 302         455 Scalar::Util::weaken($UR::Context::all_objects_loaded->{$class}->{$obj->id});
1535 302         434 $all_objects_cache_size--;
1536             }
1537             }
1538              
1539              
1540             sub _object_cache_pruning_report {
1541 0     0   0 my $self = shift;
1542 0         0 my $max_show = shift;
1543              
1544 0 0       0 $max_show = 10 unless defined ($max_show);
1545              
1546 0         0 my @sorted_counts = sort { $b->[1] <=> $a->[1] }
1547 0         0 map { [ $_ => scalar(keys %{$UR::Context::all_objects_loaded->{$_}}) ] }
  0         0  
1548 0         0 grep { !$_->__meta__->is_meta_meta }
  0         0  
1549             keys %$UR::Context::all_objects_loaded;
1550 0         0 my $message = '';
1551 0   0     0 for (my $i = 0; $i < 10 and $i < @sorted_counts; $i++) {
1552 0         0 my $class_name = $sorted_counts[$i]->[0];
1553 0         0 my $count = $sorted_counts[$i]->[1];
1554 0         0 $message .= "$class_name: $count\n";
1555              
1556 0 0       0 if ($ENV{'UR_DEBUG_OBJECT_PRUNING'} > 1) {
1557             # more detailed info
1558 0         0 my $no_data_source = 0;
1559 0         0 my $other_references = 0;
1560 0         0 my $strengthened = 0;
1561 0         0 my $has_changes = 0;
1562 0         0 my $prunable = 0;
1563 0         0 my $class_data_source = eval { $class_name->__meta__->data_source_id; };
  0         0  
1564 0         0 foreach my $obj ( values %{$UR::Context::all_objects_loaded->{$class_name}} ) {
  0         0  
1565 0 0       0 next unless $obj;
1566              
1567 0         0 my $is_prunable = 1;
1568 0 0       0 if (! $class_data_source ) {
1569 0         0 $no_data_source++;
1570 0         0 $is_prunable = 0;
1571             }
1572 0 0       0 if (! exists $obj->{'__get_serial'}) {
1573 0         0 $other_references++;
1574 0         0 $is_prunable = 0;
1575             }
1576 0 0       0 if (exists $obj->{'__strengthened'}) {
1577 0         0 $strengthened++;
1578 0         0 $is_prunable = 0;
1579             }
1580 0 0       0 if ($obj->__changes__) {
1581 0         0 $has_changes++;
1582 0         0 $is_prunable = 0;
1583             }
1584 0 0       0 if ($is_prunable) {
1585 0         0 $prunable++;
1586             }
1587             }
1588 0         0 $message .= sprintf("\tNo data source: %d other refs: %d strengthend: %d has changes: %d prunable: %d\n",
1589             $no_data_source, $other_references, $strengthened, $has_changes, $prunable);
1590             }
1591             }
1592 0         0 return $message;
1593             }
1594              
1595              
1596             sub value_for_object_property_in_underlying_context {
1597 329     329 0 296 my ($self, $obj, $property_name) = @_;
1598              
1599 329   66     560 my $saved = $obj->{db_saved_uncommitted} || $obj->{db_committed};
1600 329 50       407 unless ($saved) {
1601 0         0 Carp::croak(qq(No object found in underlying context));
1602             }
1603              
1604 329         649 return $saved->{$property_name};
1605             }
1606              
1607              
1608             # True if the object was loaded from an underlying context and/or datasource, or if the
1609             # object has been committed to the underlying context
1610             sub object_exists_in_underlying_context {
1611 230     230 0 359 my($self, $obj) = @_;
1612              
1613 230 100       811 return if ($obj->{'__defined'});
1614 159   66     824 return (exists($obj->{'db_committed'}) || exists($obj->{'db_saved_uncommitted'}));
1615             }
1616              
1617              
1618             # Holds the logic for handling OR-type rules passed to get_objects_for_class_and_rule()
1619             sub _get_objects_for_class_and_or_rule {
1620 9     9   15 my ($self, $class, $rule, $load, $return_closure) = @_;
1621              
1622 9         31 $rule = $rule->normalize;
1623 9         25 my @u = $rule->underlying_rules;
1624 9         11 my @results;
1625 9         27 for my $u (@u) {
1626 18 100 66     73 if (wantarray or not defined wantarray) {
1627 2         6 push @results, $self->get_objects_for_class_and_rule($class,$u,$load,$return_closure);
1628             }
1629             else {
1630 16         41 my $result = $self->get_objects_for_class_and_rule($class,$u,$load,$return_closure);
1631 16         31 push @results, $result;
1632             }
1633             }
1634 9 100       25 if ($return_closure) {
1635 8         23 my $object_sorter = $rule->template->sorter();
1636              
1637 8         8 my @next;
1638             return sub {
1639             # fill in missing slots in @next
1640 26     26   54 for(my $i = 0; $i < @results; $i++) {
1641 43 100       72 unless (defined $next[$i]) {
1642             # This slot got used last time through
1643 36         53 $next[$i] = $results[$i]->();
1644 36 100       86 unless (defined $next[$i]) {
1645             # That iterator is exhausted, splice it out
1646 16         19 splice(@results, $i, 1);
1647 16         47 splice(@next, $i, 1);
1648 16 100       43 redo if $i < @results; #the next iterator is now at $i, not $i++
1649             }
1650             }
1651             }
1652              
1653 26         25 my $lowest_slot = 0;
1654 26         41 for(my $i = 1; $i < @results; $i++) {
1655 9         22 my $cmp = $object_sorter->($next[$lowest_slot], $next[$i]);
1656 9 100       29 if ($cmp > 0) {
    100          
1657 3         7 $lowest_slot = $i;
1658             } elsif ($cmp == 0) {
1659             # duplicate object, mark this slot to fill in next time around
1660 2         6 $next[$i] = undef;
1661             }
1662             }
1663              
1664 26         28 my $retval = $next[$lowest_slot];
1665 26         25 $next[$lowest_slot] = undef;
1666 26         49 return $retval;
1667 8         64 };
1668             }
1669              
1670             # remove duplicates
1671 1         1 my $last = 0;
1672 1         1 my $plast = 0;
1673 1         2 my $next = 0;
1674 1 50       5 @results = grep { $plast = $last; $last = $_; $plast == $_ ? () : ($_) } sort @results;
  2         1  
  2         3  
  2         6  
1675              
1676 1 50       3 return unless defined wantarray;
1677 1 50       6 return @results if wantarray;
1678 0 0       0 if (@results > 1) {
1679 0         0 $self->_exception_for_multi_objects_in_scalar_context($rule,\@results);
1680             }
1681 0         0 return $results[0];
1682             }
1683              
1684              
1685             # this is the underlying method for get/load/is_loaded in ::Object
1686              
1687             sub get_objects_for_class_and_rule {
1688 344073     344073 1 509165 my ($self, $class, $rule, $load, $return_closure) = @_;
1689 344073         275333 my $initial_load = $load;
1690             #my @params = $rule->params_list;
1691             #print "GET: $class @params\n";
1692              
1693 344073         562432 my $rule_template = $rule->template;
1694            
1695 344073         654520 my $group_by = $rule_template->group_by;
1696              
1697 344073 100 100     969904 if (ref($self) and !defined($load)) {
1698 149923         257132 $load = $self->query_underlying_context; # could still be undef...
1699             }
1700              
1701 344073 100 100     584822 if ($group_by and $rule_template->order_by) {
1702 2         4 my %group_by = map { $_ => 1 } @{ $rule->template->group_by };
  2         7  
  2         6  
1703 2         4 foreach my $order_by_property ( @{ $rule->template->order_by } ) {
  2         5  
1704 2 100       9 unless ($group_by{$order_by_property}) {
1705 1         72 Carp::croak("Property '$order_by_property' in the -order_by list must appear in the -group_by list for BoolExpr $rule");
1706             }
1707             }
1708             }
1709              
1710 344072 100 100     584395 if (
1711             $cache_size_highwater
1712             and
1713             $all_objects_cache_size > $cache_size_highwater
1714             ) {
1715 40         119 $self->prune_object_cache();
1716             }
1717              
1718 344072 100       1262523 if ($rule_template->isa("UR::BoolExpr::Template::Or")) {
1719 9         45 return $self->_get_objects_for_class_and_or_rule($class,$rule,$load,$return_closure);
1720             }
1721              
1722             # an identifier for all objects gotten in this request will be set/updated on each of them for pruning later
1723 344063         302493 my $this_get_serial = $GET_COUNTER++;
1724            
1725 344063         779619 my $meta = $class->__meta__();
1726              
1727             # A query on a subclass where the parent class is_abstract and has a subclassify_by property
1728             # (meaning that the parent class has a property which directly stores the proper subclass for
1729             # each row - subclasses inherit the property from the parent, and the subclass isn't is_abstract)
1730             # should have a filter added to the rule to keep only rows of the subclass we're interested in.
1731             # This will improve the SQL performance when it's later constructed.
1732 344063         710307 my $subclassify_by = $meta->subclassify_by;
1733 344063 50 100     585275 if ($subclassify_by
      66        
      66        
1734             and ! $meta->is_abstract
1735             and ! $rule->template->group_by
1736             and ! $rule->specifies_value_for($subclassify_by)
1737             ) {
1738 35         145 $rule = $rule->add_filter($subclassify_by => $class);
1739             }
1740              
1741             # If $load is undefined, and there is no underlying context, we define it to FALSE explicitly
1742             # TODO: instead of checking for a data source, skip this
1743             # We'll always go to the underlying context, even if it has nothing.
1744             # This optimization only works by coincidence since we don't stack contexts currently beyond 1.
1745 344063         257713 my $ds;
1746 344063 100 100     718421 if (!defined($load) or $load) {
1747 269483         476063 ($ds) = $self->resolve_data_sources_for_class_meta_and_rule($meta,$rule);
1748 269483 100 100     486833 if (! $ds or $class =~ m/::Ghost$/) {
1749             # Classes without data sources and Ghosts can only ever come from the cache
1750 265594         215715 $load = 0;
1751             }
1752             }
1753            
1754             # this is an arrayref of all of the cached data
1755             # it is set in one of two places below
1756 344063         245715 my $cached;
1757            
1758             # this will turn foo=>$foo into foo.id=>$foo->id where possible
1759 344063         670814 my $no_hard_refs_rule = $rule->flatten_hard_refs;
1760            
1761             # we do not currently fully "flatten" b/c the bx constant_values do not flatten/reframe
1762             #my $flat_rule = ( (1 or $no_hard_refs_rule->subject_class_name eq 'UR::Object::Property') ? $no_hard_refs_rule : $no_hard_refs_rule->flatten);
1763            
1764             # this is a no-op if the rule is already normalized
1765 344063         602184 my $normalized_rule = $no_hard_refs_rule->normalize;
1766              
1767 344063         612566 my $is_monitor_query = $self->monitor_query;
1768 344063 50       473429 $self->_log_query_for_rule($class,$normalized_rule,Carp::shortmess("QUERY: Query start for rule $normalized_rule")) if ($is_monitor_query);
1769              
1770             # see if we need to load if load was not defined
1771 344063 100       471752 unless (defined $load) {
1772             # check to see if the cache is complete
1773             # also returns a list of the complete cached objects where that list is found as a side-effect
1774 3726         10853 my ($cache_is_complete, $cached) = $self->_cache_is_complete_for_class_and_normalized_rule($class, $normalized_rule);
1775 3726 100       7525 $load = ($cache_is_complete ? 0 : 1);
1776             }
1777              
1778 344063 100 100     566704 if ($ds and $load and $rule_template->order_by) {
      100        
1779             # if any of the order_by is calculated, then we need to do an unordered query against the
1780             # data source, then we can do it as a non-load query and do the sorting on all the in-memory
1781             # objects
1782 100         381 my $qp = $ds->_resolve_query_plan($rule_template);
1783 100 100       386 if ($qp->order_by_non_column_data) {
1784 3 50       11 $self->_log_query_for_rule($class,$normalized_rule,"QUERY: Doing an unordered query on the datasource because one of the order_by properties of the rule is not expressable by the data source") if ($is_monitor_query);
1785 3         17 $self->get_objects_for_class_and_rule($class, $rule->remove_filter('-order')->remove_filter('-order_by'), 1);
1786 3         11 $load = 0;
1787             }
1788             }
1789              
1790 344063         541549 my $normalized_rule_template = $normalized_rule->template;
1791              
1792             # optimization for the common case
1793 344063 100 66     802273 if (!$load and !$return_closure) {
1794 342015         603934 my @c = $self->_get_objects_for_class_and_rule_from_cache($class,$normalized_rule);
1795 342015         299372 my $obj_count = scalar(@c);
1796 342015         392022 foreach ( @c ) {
1797 263898 100       430302 unless (exists $_->{'__get_serial'}) {
1798             # This is a weakened reference. Convert it back to a regular ref
1799 91583         80025 my $class = ref $_;
1800 91583         138356 my $id = $_->id;
1801 91583         104294 my $ref = $UR::Context::all_objects_loaded->{$class}->{$id};
1802 91583         94986 $UR::Context::all_objects_loaded->{$class}->{$id} = $ref;
1803             }
1804 263898         284272 $_->{'__get_serial'} = $this_get_serial;
1805             }
1806              
1807 342015 50       451904 if ($is_monitor_query) {
1808 0         0 $self->_log_query_for_rule($class,$normalized_rule,"QUERY: matched $obj_count cached objects (no loading)");
1809 0         0 $self->_log_query_for_rule($class,$normalized_rule,"QUERY: Query complete after returning $obj_count object(s) for rule $rule");
1810 0         0 $self->_log_done_elapsed_time_for_rule($normalized_rule);
1811             }
1812              
1813 342015 100 100     590302 if (defined($normalized_rule_template->limit) || defined($normalized_rule_template->offset)) {
1814 13         69 $self->_prune_obj_list_for_limit_and_offset(\@c,$normalized_rule_template);
1815             }
1816              
1817 342015 100       682578 return @c if wantarray; # array context
1818 272322 100       365754 return unless defined wantarray; # null context
1819 272321 100       379949 Carp::confess("multiple objects found for a call in scalar context!" . Data::Dumper::Dumper(\@c)) if @c > 1;
1820 272320         775637 return $c[0]; # scalar context
1821             }
1822              
1823 2048         5950 my $object_sorter = $normalized_rule_template->sorter();
1824              
1825             # the above process might have found all of the cached data required as a side-effect in which case
1826             # we have a value for this early
1827             # either way: ensure the cached data is known and sorted
1828 2048 50       4690 if ($cached) {
1829 0         0 @$cached = sort $object_sorter @$cached;
1830             }
1831             else {
1832 2048         5941 $cached = [ sort $object_sorter $self->_get_objects_for_class_and_rule_from_cache($class,$normalized_rule) ];
1833             }
1834 2048 50       5596 $self->_log_query_for_rule($class, $normalized_rule, "QUERY: matched ".scalar(@$cached)." cached objects") if ($is_monitor_query);
1835 2048         4482 foreach ( @$cached ) {
1836 1174 100       2246 unless (exists $_->{'__get_serial'}) {
1837             # This is a weakened reference. Convert it back to a regular ref
1838 6         18 my $class = ref $_;
1839 6         18 my $id = $_->id;
1840 6         14 my $ref = $UR::Context::all_objects_loaded->{$class}->{$id};
1841 6         22 $UR::Context::all_objects_loaded->{$class}->{$id} = $ref;
1842             }
1843 1174         1541 $_->{'__get_serial'} = $this_get_serial;
1844             }
1845              
1846            
1847             # make a loading iterator if loading must be done for this rule
1848 2048         2619 my $loading_iterator;
1849 2048 100       4887 if ($load) {
1850             # this returns objects from the underlying context after importing them into the current context,
1851             # but only if they did not exist in the current context already
1852 2023 50       4639 $self->_log_query_for_rule($class, $normalized_rule, "QUERY: importing from underlying context with rule $normalized_rule") if ($is_monitor_query);
1853              
1854 2023         11630 $loading_iterator = UR::Context::LoadingIterator->_create($cached, $self,$normalized_rule, $ds,$this_get_serial);
1855             }
1856              
1857 2025 100       4452 if ($return_closure) {
1858 78 100       226 if ($load) {
1859             # return the iterator made above
1860 53         277 return $loading_iterator;
1861             }
1862             else {
1863             # make a quick iterator for the cached data
1864 25 100 66     79 if(defined($normalized_rule_template->limit) || defined($normalized_rule_template->offset)) {
1865 3         14 $self->_prune_obj_list_for_limit_and_offset($cached,$normalized_rule_template);
1866             }
1867 25     81   170 return sub { return shift @$cached };
  81         124  
1868             }
1869             }
1870             else {
1871 1947         2365 my @results;
1872 1947 50       3756 if ($loading_iterator) {
1873             # use the iterator made above
1874 1947         2001 my $found;
1875 1947         4788 while (defined($found = $loading_iterator->(1))) {
1876 3190         6490 push @results, $found;
1877             }
1878             }
1879             else {
1880             # just get the cached data
1881 0 0 0     0 if(defined($normalized_rule_template->limit) || defined($normalized_rule_template->offset)) {
1882 0         0 $self->_prune_obj_list_for_limit_and_offset($cached,$normalized_rule_template);
1883             }
1884 0         0 @results = @$cached;
1885             }
1886 1932 100       5194 return unless defined wantarray;
1887 1671 100       6896 return @results if wantarray;
1888 512 100       1433 if (@results > 1) {
1889 1         20 $self->_exception_for_multi_objects_in_scalar_context($rule,\@results);
1890             }
1891 511         2181 return $results[0];
1892             }
1893             }
1894              
1895              
1896             sub _exception_for_multi_objects_in_scalar_context {
1897 1     1   3 my($self,$rule,$resultsref) = @_;
1898              
1899 1         6 my $message = sprintf("Multiple results unexpected for query.\n\tClass %s\n\trule params: %s\n\tGot %d results",
1900             $rule->subject_class_name,
1901             join(',', $rule->params_list),
1902             scalar(@$resultsref));
1903 1         4 my $lastidx = $#$resultsref;
1904 1 50       5 if (@$resultsref > 10) {
1905 0         0 $message .= "; the first 10 are";
1906 0         0 $lastidx = 9;
1907             }
1908 1         11 Carp::confess($message . ":\n" . Data::Dumper::Dumper([@$resultsref[0..$lastidx]]));
1909             }
1910              
1911             sub _prune_obj_list_for_limit_and_offset {
1912 16     16   31 my($self, $obj_list, $tmpl) = @_;
1913              
1914 16 100       63 my $limit = defined($tmpl->limit) ? $tmpl->limit : $#$obj_list;
1915 16   100     50 my $offset = $tmpl->offset || 0;
1916              
1917 16 100       51 if ($offset > @$obj_list) {
1918 1         169 Carp::carp('-offset is larger than the result list');
1919 1         5 @$obj_list = ();
1920             } else {
1921 15         75 @$obj_list = splice(@$obj_list, $offset, $limit);
1922             }
1923             }
1924              
1925              
1926             sub __merge_db_data_with_existing_object {
1927 1287     1287   1713 my($self, $class_name, $existing_object, $pending_db_object_data, $property_names) = @_;
1928              
1929 1287 100       2213 unless (defined $pending_db_object_data) {
1930             # This means a row in the database is missing for an object we loaded before
1931 23 100 33     74 if (defined($existing_object)
      66        
1932             and $self->object_exists_in_underlying_context($existing_object)
1933             and $existing_object->__changes__
1934             ) {
1935 2         6 my $id = $existing_object->id;
1936 2         14 Carp::croak("$class_name ID '$id' previously existed in an underlying context, has since been deleted from that context, and the cached object now has unsavable changes.\nDump: ".Data::Dumper::Dumper($existing_object)."\n");
1937             } else {
1938             #print "Removing object id ".$existing_object->id." because it has been removed from the database\n";
1939 21         65 UR::Context::LoadingIterator->_remove_object_from_other_loading_iterators($existing_object);
1940 21         67 $existing_object->__signal_change__('delete');
1941 21         70 $self->_abandon_object($existing_object);
1942 21         42 return $existing_object;
1943             }
1944             }
1945              
1946 1264         1086 my $expected_db_data;
1947 1264 100       3155 if (exists $existing_object->{'db_saved_uncommitted'}) {
    50          
1948 23         52 $expected_db_data = $existing_object->{'db_saved_uncommitted'};
1949              
1950             } elsif (exists $existing_object->{'db_committed'}) {
1951 1241         1416 $expected_db_data = $existing_object->{'db_committed'};
1952              
1953             } else {
1954 0         0 my $id = $existing_object->id;
1955 0         0 Carp::croak("$class_name ID '$id' has just been loaded, but it exists in the application as a new unsaved object!\nDump: " . Data::Dumper::Dumper($existing_object) . "\n");
1956             }
1957              
1958 1264         1181 my $different = 0;
1959 1264         1132 my $conflict = undef;
1960              
1961 1264         1471 foreach my $property ( @$property_names ) {
1962 266     266   1953 no warnings 'uninitialized';
  266         438  
  266         54232  
1963              
1964             # All direct properties are stored in the same-named hash key, right?
1965 3677 50       5015 next unless (exists $existing_object->{$property});
1966              
1967 3677         3528 my $object_value = $existing_object->{$property};
1968 3677         2760 my $db_value = $pending_db_object_data->{$property};
1969 3677         3205 my $expected_db_value = $expected_db_data->{$property};
1970              
1971 3677 100       4959 if ($object_value ne $expected_db_value) {
1972 42         59 $different++;
1973             }
1974              
1975            
1976 3677 100 100     7350 if ( $object_value eq $db_value # current value matches DB value
      100        
1977             or
1978             ($object_value eq $expected_db_value) # current value hasn't changed since it was loaded from the DB
1979             or
1980             ($db_value eq $expected_db_value) # DB value matches what it was when we loaded it from the DB
1981             ) {
1982             # no conflict. Check the next one
1983 3665         3568 next;
1984             } else {
1985 12         25 $conflict = $property;
1986 12         18 last;
1987             }
1988             }
1989              
1990 1264 100       2041 if (defined $conflict) {
1991             # conflicting change!
1992             # Since the user could be catching this exception, go ahead and update the
1993             # object's notion of what is in the database
1994 12         54 my %old_dbc = %$expected_db_data;
1995 12         41 @$expected_db_data{@$property_names} = @$pending_db_object_data{@$property_names};
1996              
1997             my $old_value = defined($old_dbc{$conflict})
1998 12 50       54 ? "'" . $old_dbc{$conflict} . "'"
1999             : '(undef)';
2000             my $new_db_value = defined($pending_db_object_data->{$conflict})
2001 12 50       57 ? "'" . $pending_db_object_data->{$conflict} . "'"
2002             : '(undef)';
2003             my $new_obj_value = defined($existing_object->{$conflict})
2004 12 50       42 ? "'" . $existing_object->{$conflict} . "'"
2005             : '(undef)';
2006              
2007 12         46 my $obj_id = $existing_object->id;
2008              
2009 12         3624 Carp::croak("\nA change has occurred in the database for $class_name property '$conflict' on object ID $obj_id from $old_value to $new_db_value.\n"
2010             . "At the same time, this application has made a change to that value to $new_obj_value.\n\n"
2011             . "The application should lock data which it will update and might be updated by other applications.");
2012              
2013             }
2014            
2015             # No conflicts. Update db_committed and db_saved_uncommitted based on the DB data
2016 1252         5904 %$expected_db_data = (%$expected_db_data, %$pending_db_object_data);
2017              
2018 1252 100       2676 if (! $different) {
2019             # FIXME HACK! This is to handle the case when you get an object, start a software transaction,
2020             # change something in the database for that object, reload the object (so __merge updates the value
2021             # found in the DB), then rollback the transaction. The act of updating the value here in __merge makes
2022             # a change record that gets undone when the transaction is rolled back. After the rollback, the current
2023             # value goes back to the originally loaded value, db_committed has the newly clhanged DB value, but
2024             # _change_count is 0 turning off change tracking makes it so this internal change isn't undone by rollback
2025 1223         1353 local $UR::Context::Transaction::log_all_changes = 0; # HACK!
2026             # The object has no local changes. Go ahead and update the current value, too
2027 1223         1361 foreach my $property ( @$property_names ) {
2028 266     266   1247 no warnings 'uninitialized';
  266         406  
  266         82033  
2029 3571 100       6429 next if ($existing_object->{$property} eq $pending_db_object_data->{$property});
2030              
2031 22         101 $existing_object->$property($pending_db_object_data->{$property});
2032             }
2033             }
2034              
2035             # re-figure how many changes are really there
2036 1252         3424 my @change_count = $existing_object->__changes__;
2037 1252         1701 $existing_object->{'_change_count'} = scalar(@change_count);
2038              
2039 1252         2124 return $different;
2040             }
2041              
2042              
2043              
2044             sub _get_objects_for_class_and_sql {
2045             # this is a depracated back-door to get objects with raw sql
2046             # only use it if you know what you're doing
2047 8     8   12 my ($self, $class, $sql) = @_;
2048 8         21 my $meta = $class->__meta__;
2049             #my $ds = $self->resolve_data_sources_for_class_meta_and_rule($meta,$class->define_boolexpr());
2050 8         20 my $ds = $self->resolve_data_sources_for_class_meta_and_rule($meta,UR::BoolExpr->resolve($class));
2051 8         25 my $id_list = $ds->_resolve_ids_from_class_name_and_sql($class,$sql);
2052 4 50 33     21 return unless (defined($id_list) and @$id_list);
2053              
2054 4         17 my $rule = UR::BoolExpr->resolve_normalized($class, id => $id_list);
2055            
2056 4         11 return $self->get_objects_for_class_and_rule($class,$rule);
2057             }
2058              
2059             sub _cache_is_complete_for_class_and_normalized_rule {
2060 3726     3726   5337 my ($self,$class,$normalized_rule) = @_;
2061              
2062             # TODO: convert this to use the rule object instead of going back to the legacy hash format
2063              
2064 3726         4081 my ($id,$params,@objects,$cache_is_complete);
2065 3726         9093 $params = $normalized_rule->legacy_params_hash;
2066 3726         6130 $id = $params->{id};
2067              
2068             # Determine ahead of time whether we believe the object MUST be loaded if it exists.
2069             # If this is true, we will shortcut out of any action which loads or prepares for loading.
2070              
2071             # Try to resolve without loading in cases where we are sure
2072             # that doing so will return the complete results.
2073            
2074 3726         4745 my $id_only = $params->{_id_only};
2075 3726 100 100     10248 $id_only = undef if ref($id) and ref($id) eq 'HASH';
2076 3726 100       9826 if ($id_only) {
    100          
2077             # _id_only means that only id parameters were passed in.
2078             # Either a single id or an arrayref of ids.
2079             # Try to pull objects from the cache in either case
2080 1185 100       2378 if (ref $id) {
2081             # arrayref id
2082            
2083             # we check the immediate class and all derived
2084             # classes for any of the ids in the set.
2085             @objects =
2086 70         107 grep { $_ }
2087 27         80 map { @$_{@$id} }
2088 27         117 map { $all_objects_loaded->{$_} }
  27         76  
2089             ($class, $class->__meta__->subclasses_loaded);
2090              
2091             # see if we found all of the requested objects
2092 27 100       88 if (@objects == @$id) {
2093             # we found them all
2094             # return them all
2095 9         13 $cache_is_complete = 1;
2096             }
2097             else {
2098             # Ideally we'd filter out the ones we found,
2099             # but that gets complicated.
2100             # For now, we do it the slow way for partial matches
2101 18         43 @objects = ();
2102             }
2103             }
2104             else {
2105             # scalar id
2106             # Check for objects already loaded.
2107 266     266   1360 no warnings;
  266         413  
  266         430888  
2108 1158 100       6345 if (exists $all_objects_loaded->{$class}->{$id}) {
    100          
2109 554         698 $cache_is_complete = 1;
2110             @objects =
2111 554         1485 grep { $_ }
2112 554         1012 $all_objects_loaded->{$class}->{$id};
2113             }
2114             elsif (not $class->isa("UR::Value")) {
2115             # we already checked the immediate class,
2116             # so just check derived classes
2117             # this is not done for values because an identity can exist
2118             # with independent objects with values, unlike entities
2119             @objects =
2120 22         43 grep { $_ }
2121 508         1805 map { $all_objects_loaded->{$_}->{$id} }
  22         61  
2122             $class->__meta__->subclasses_loaded;
2123 508 50       1349 if (@objects) {
2124 0         0 $cache_is_complete = 1;
2125             }
2126             }
2127             }
2128             }
2129             elsif ($params->{_unique}) {
2130             # _unique means that this set of params could never
2131             # result in more than 1 object.
2132            
2133             # See if the 1 is in the cache
2134             # If not we have to load
2135            
2136 29         123 @objects = $self->_get_objects_for_class_and_rule_from_cache($class,$normalized_rule);
2137 29 50       92 if (@objects) {
2138 0         0 $cache_is_complete = 1;
2139             }
2140             }
2141            
2142 3726 100       6992 if ($cache_is_complete) {
2143             # if the $cache_is_comlete, the $cached list DEFINITELY represents all objects we need to return
2144             # we know that loading is NOT necessary because what we've found cached must be the entire set
2145            
2146             # Because we happen to have that set, we return it in addition to the boolean flag
2147 563 50       3082 return wantarray ? (1, \@objects) : ();
2148             }
2149            
2150             # We need to do more checking to see if loading is necessary
2151             # Either the parameters were non-unique, or they were unique
2152             # and we didn't find the object checking the cache.
2153              
2154             # See if we need to do a load():
2155              
2156 3163         8018 my $template_id = $normalized_rule->template_id;
2157 3163         6764 my $rule_id = $normalized_rule->id;
2158             my $loading_is_in_progress_on_another_iterator =
2159 3163         13808 grep { $_->is_loading_in_progress_for_boolexpr($normalized_rule) }
  66         185  
2160             UR::Context::ObjectFabricator->all_object_fabricators;
2161              
2162 3163 100       6259 return 0 if $loading_is_in_progress_on_another_iterator;
2163              
2164             # complex (non-single-id) params
2165             my $loading_was_done_before_with_these_params = (
2166             # exact match to previous attempt
2167             ( exists ($UR::Context::all_params_loaded->{$template_id})
2168             and
2169 3155   100     19705 exists ($UR::Context::all_params_loaded->{$template_id}->{$rule_id})
2170             )
2171             ||
2172             # this is a subset of a previous attempt
2173             ($self->_loading_was_done_before_with_a_superset_of_this_rule($normalized_rule))
2174             );
2175            
2176 3155   100     14240 my $object_is_loaded_or_non_existent =
2177             $loading_was_done_before_with_these_params
2178             || $class->all_objects_are_loaded;
2179            
2180 3155 100       5633 if ($object_is_loaded_or_non_existent) {
2181             # These same non-unique parameters were used to load previously,
2182             # or we loaded everything at some point.
2183             # No load necessary.
2184 1300         4088 return 1;
2185             }
2186             else {
2187             # Load according to params
2188 1855         6367 return;
2189             }
2190             } # done setting $load, and possibly filling $cached/$cache_is_complete as a side-effect
2191              
2192              
2193             sub all_objects_loaded {
2194 2378     2378 1 2987 my $self = shift;
2195 2378         2836 my $class = $_[0];
2196             return(
2197 107024         91853 grep {$_}
2198 2378         8119 map { values %{ $UR::Context::all_objects_loaded->{$_} } }
  23125         13833  
  23125         67189  
2199             $class, $class->__meta__->subclasses_loaded
2200             );
2201             }
2202              
2203             sub all_objects_loaded_unsubclassed {
2204 1839     1839 0 1507 my $self = shift;
2205 1839         1545 my $class = $_[0];
2206 1839         1385 return (grep {$_} values %{ $UR::Context::all_objects_loaded->{$class} } );
  2133         2776  
  1839         3791  
2207             }
2208              
2209              
2210             sub _get_objects_for_class_and_rule_from_cache {
2211             # Get all objects which are loaded in the application which match
2212             # the specified parameters.
2213 344121     344121   352267 my ($self, $class, $rule) = @_;
2214            
2215 344121         615821 my ($template,@values) = $rule->template_and_values;
2216              
2217             #my @param_list = $rule->params_list;
2218             #print "CACHE-GET: $class @param_list\n";
2219              
2220 344121         448686 my $strategy = $rule->{_context_query_strategy};
2221 344121 100       493919 unless ($strategy) {
2222 334750 100       560534 if ($rule->template->group_by) {
    100          
    100          
2223 29         71 $strategy = $rule->{_context_query_strategy} = "set intersection";
2224             }
2225             elsif ($rule->num_values == 0) {
2226 489         1164 $strategy = $rule->{_context_query_strategy} = "all";
2227             }
2228             elsif ($rule->is_id_only) {
2229 269368         416669 $strategy = $rule->{_context_query_strategy} = "id";
2230             }
2231             else {
2232 64864         115039 $strategy = $rule->{_context_query_strategy} = "index";
2233             }
2234             }
2235            
2236 344121         390503 my @results = eval {
2237            
2238 344121 100       639667 if ($strategy eq "all") {
    100          
    100          
    50          
2239 503         1463 return $self->all_objects_loaded($class);
2240             }
2241             elsif ($strategy eq "id") {
2242 278660         479245 my $id = $rule->value_for_id();
2243            
2244 278660 50       422614 unless (defined $id) {
2245 0         0 $id = $rule->value_for_id();
2246             }
2247            
2248             # Try to get the object(s) from this class directly with the ID.
2249             # Note that the code below is longer than it needs to be, but
2250             # is written to run quickly by resolving the most common cases
2251             # first, and gathering data only if and when it must.
2252            
2253 278660         207675 my @matches;
2254 278660 100       369584 if (ref($id) eq 'ARRAY') {
2255             # The $id is an arrayref. Get all of the set.
2256 58         116 @matches = grep { $_ } map { @$_{@$id} } map { $all_objects_loaded->{$_} } ($class);
  118         178  
  58         145  
  58         134  
2257            
2258             # We're done if the number found matches the number of ID values.
2259 58 100       207 return @matches if @matches == @$id;
2260             }
2261             else {
2262             # The $id is a normal scalar.
2263 278602 50       385595 if (not defined $id) {
2264             #Carp::carp("Undefined id passed as params for query on $class");
2265 0         0 Carp::cluck("\n\n**** Undefined id passed as params for query on $class");
2266 0   0     0 $id ||= '';
2267             }
2268 278602         201429 my $match;
2269             # FIXME This is a performance optimization for class metadata to avoid the search through
2270             # @subclasses_loaded a few lines further down. When 100s of classes are loaded it gets
2271             # a bit slow. Maybe UR::Object::Type should override get() instad and put it there?
2272 278602 100 100     785509 if (! $UR::Object::Type::bootstrapping and $class eq 'UR::Object::Type') {
2273 45653         78931 my $meta_class_name = $id . '::Type';
2274             $match = $all_objects_loaded->{$meta_class_name}->{$id}
2275             ||
2276 45653   66     163829 $all_objects_loaded->{'UR::Object::Type'}->{$id};
2277 45653 100       71328 if ($match) {
2278 38860         76526 return $match;
2279             } else {
2280 6793         13000 return;
2281             }
2282             }
2283              
2284 232949         426836 $match = $all_objects_loaded->{$class}->{$id};
2285            
2286             # We're done if we found anything. If not we keep checking.
2287 232949 100       418637 return $match if $match;
2288             }
2289            
2290             # Try to get the object(s) from this class's subclasses.
2291             # We may be adding to matches made above is we used an arrayref
2292             # and the results are incomplete.
2293            
2294 164844         440179 my @subclasses_loaded = $class->__meta__->subclasses_loaded;
2295 164844 100       407952 return @matches unless @subclasses_loaded;
2296            
2297 7056 50       11680 if (ref($id) eq 'ARRAY') {
2298             # The $id is an arrayref. Get all of the set and add it to anything found above.
2299             push @matches,
2300 0         0 grep { $_ }
2301 0         0 map { @$_{@$id} }
2302 0         0 map { $all_objects_loaded->{$_} }
  0         0  
2303             @subclasses_loaded;
2304             }
2305             else {
2306             # The $id is a normal scalar, but we didn't find it above.
2307             # Try each subclass, exiting if we find anything.
2308 7056         10253 for (@subclasses_loaded) {
2309 76681         85932 my $match = $all_objects_loaded->{$_}->{$id};
2310 76681 100       102652 return $match if $match;
2311             }
2312             }
2313            
2314             # Since an ID was specified, and we've scanned the core hash every way possible,
2315             # we're done. Return nothing if necessary.
2316 487         1215 return @matches;
2317             }
2318             elsif ($strategy eq "index") {
2319             # FIXME - optimize by using the rule (template?)'s param names directly to get the
2320             # index id instead of re-figuring it out each time
2321              
2322 64929         133387 my $class_meta = $rule->subject_class_name->__meta__;
2323 64929         136528 my %params = $rule->params_list;
2324 64929         73502 my $should_evaluate_later;
2325 64929         127462 for my $key (keys %params) {
2326 114597 100 66     487217 if (substr($key,0,1) eq '-' or substr($key,0,1) eq '_') {
    100          
2327 224         525 delete $params{$key};
2328             }
2329             elsif ($key =~ /^\w*\./) {
2330             # a chain of properties
2331 27         37 $should_evaluate_later = 1;
2332 27         64 delete $params{$key};
2333             }
2334             else {
2335 114346         255210 my $prop_meta = $class_meta->property_meta_for_name($key);
2336             # NOTE: We _could_ remove the is_delegated check if we knew we were operating on
2337             # a read-only context.
2338 114346 100 100     328903 if ($prop_meta && ($prop_meta->is_many or $prop_meta->is_delegated)) {
      66        
2339             # These indexes perform poorly in the general case if we try to index
2340             # the is_many properties. Instead, strip them out from the basic param
2341             # list, and evaluate the superset of indexed objects through the rule
2342 121         191 $should_evaluate_later = 1;
2343 121         310 delete $params{$key};
2344             }
2345             }
2346             }
2347            
2348 64929         191185 my @properties = sort keys %params;
2349 64929 100       120798 unless (@properties) {
2350             # All the supplied filters were is_many properties
2351 201         813 return grep { $rule->evaluate($_) } $self->all_objects_loaded($class);
  441         970  
2352             }
2353              
2354 64728         82628 my @values = map { $params{$_} } @properties;
  114225         182770  
2355            
2356 64728 50       121941 unless (@properties == @values) {
2357 0         0 Carp::confess();
2358             }
2359            
2360             # find or create the index
2361 64728         114316 my $pstring = join(",",@properties);
2362 64728         200777 my $index_id = UR::Object::Index->__meta__->resolve_composite_id_from_ordered_values($class,$pstring);
2363 64728         123439 my $index = $all_objects_loaded->{'UR::Object::Index'}{$index_id};
2364 64728   66     122742 $index ||= UR::Object::Index->create(
2365             id => $index_id,
2366             indexed_class_name => $class,
2367             indexed_property_string => $pstring
2368             );
2369            
2370              
2371             # add the indexed objects to the results list
2372            
2373            
2374 64728 50       110201 if ($UR::Debug::verify_indexes) {
2375 0         0 my @matches = $index->get_objects_matching(@values);
2376 0         0 @matches = sort @matches;
2377 0         0 my @matches2 = sort grep { $rule->evaluate($_) } $self->all_objects_loaded($class);
  0         0  
2378 0 0       0 unless ("@matches" eq "@matches2") {
2379 0         0 print "@matches\n";
2380 0         0 print "@matches2\n";
2381             #Carp::cluck("Mismatch!");
2382 0         0 my @matches3 = $index->get_objects_matching(@values);
2383 0         0 my @matches4 = $index->get_objects_matching(@values);
2384 0         0 return @matches2;
2385             }
2386 0         0 return @matches;
2387             }
2388            
2389 64728 100       85730 if ($should_evaluate_later) {
2390 24         91 return grep { $rule->evaluate($_) } $index->get_objects_matching(@values);
  60         161  
2391             } else {
2392 64704         190066 return $index->get_objects_matching(@values);
2393             }
2394             }
2395             elsif ($strategy eq 'set intersection') {
2396             #print $rule->num_values, " ", $rule->is_id_only, "\n";
2397 29         58 my $template = $rule->template;
2398 29         63 my $group_by = $template->group_by;
2399              
2400             # get the objects in memory, and make sets for them if they do not exist
2401 29         94 my $rule_no_group = $rule->remove_filter('-group_by');
2402 29         88 $rule_no_group = $rule_no_group->remove_filter('-order_by');
2403 29         120 my @objects_in_set = $self->_get_objects_for_class_and_rule_from_cache($class, $rule_no_group);
2404 29         94 my @sets_from_grouped_objects = _group_objects($rule_no_group->template,\@values,$group_by,\@objects_in_set);
2405              
2406             # determine the template that the grouped subsets will use
2407             # find templates which are subsets of that template
2408             # find sets with a
2409 29         66 my $set_class = $class . '::Set';
2410 29         85 my $expected_template_id = $rule->template->_template_for_grouped_subsets->id;
2411             my @matches =
2412             grep {
2413             # TODO: make the template something indexable so we can pull from index
2414 29         91 my $bx = UR::BoolExpr->get($_->id);
  100         234  
2415 100         203 my $bxt = $bx->template;
2416 100 100       171 if ($bxt->id ne $expected_template_id) {
    100          
2417             #print "TEMPLATE MISMATCH $expected_template_id does not match $bxt->{id}! set: $_ with bxid $bx->{id} cannot be under rule $rule_no_group" . Data::Dumper::Dumper($_);
2418 40         71 ();
2419             }
2420             elsif (not $bx->is_subset_of($rule_no_group) ) {
2421             #print "SUBSET MISMATCH: $rule_no_group is not a superset of $_ with bxid $bx->{id}" . Data::Dumper::Dumper($_);
2422 20         58 ();
2423             }
2424             else {
2425             #print "MATCH: $rule_no_group with $expected_template_id matches $bx $bx->{id}" . Data::Dumper::Dumper($_);
2426 40         112 ($_);
2427             }
2428             }
2429             $self->all_objects_loaded($set_class);
2430            
2431             # Code to check that newly fabricated set definitions are in the set we query back out:
2432             # my @all = $self->all_objects_loaded($set_class);
2433             # my %expected;
2434             # @expected{@sets_from_grouped_objects} = @sets_from_grouped_objects;
2435             # for my $match (@matches) {
2436             # delete $expected{$match};
2437             # }
2438             # if (keys %expected) {
2439             # #$DB::single = 1;
2440             # print Data::Dumper::Dumper(\%expected);
2441             # }
2442              
2443 29         126 return @matches;
2444             }
2445             else {
2446 0         0 die "unknown strategy $strategy";
2447             }
2448             };
2449            
2450             # Handle passing-through any exceptions.
2451 344121 50       497746 die $@ if $@;
2452              
2453 344121 100       667424 if (my $recurse = $template->recursion_desc) {
2454 45         74 my ($this,$prior) = @$recurse;
2455             # remove undef items. undef/NULL in the recursion linkage means it doesn't link to anything
2456 25         46 my @values = grep { defined }
2457 45         74 map { $_->$prior }
  25         68  
2458             @results;
2459 45 100       106 if (@values) {
2460             # We do get here, so that adjustments to intermediate foreign keys
2461             # in the cache will result in a new query at the correct point,
2462             # and not result in missing data.
2463             #push @results, $class->get($this => \@values, -recurse => $recurse);
2464 13         15 push @results, map { $class->get($this => $_, -recurse => $recurse) } @values;
  20         89  
2465             }
2466             }
2467              
2468 344121         594557 my $group_by = $template->group_by;
2469             #if ($group_by) {
2470             # # return sets instead of the actual objects
2471             # @results = _group_objects($template,\@values,$group_by,\@results);
2472             #}
2473              
2474 344121 100       516553 if (@results > 1) {
2475 16036         16796 my $sorter;
2476 16036 100       28962 if ($group_by) {
2477             # We need to rewrite the original rule on the member class to be a rule
2478             # on the Set class to do proper ordering
2479 5         17 my $set_class = $template->subject_class_name . '::Set';
2480 5         35 my $set_template = UR::BoolExpr::Template->resolve($set_class, -group_by => $group_by);
2481 5         20 $sorter = $set_template->sorter;
2482             } else {
2483 16031         50554 $sorter = $template->sorter;
2484             }
2485 16036         50324 @results = sort $sorter @results;
2486             }
2487              
2488             # Return in the standard way.
2489 344121 50       963516 return @results if (wantarray);
2490 0 0       0 Carp::confess("Multiple matches for $class @_!") if (@results > 1);
2491 0         0 return $results[0];
2492             }
2493              
2494             sub _group_objects {
2495 29     29   55 my ($template,$values,$group_by,$objects) = @_;
2496 29         147 my $sub_template = $template->remove_filter('-group_by');
2497 29         71 for my $property (@$group_by) {
2498 5         35 $sub_template = $sub_template->add_filter($property);
2499             }
2500 29         78 my $set_class = $template->subject_class_name . '::Set';
2501 29         43 my @groups;
2502             my %seen;
2503 29         56 for my $result (@$objects) {
2504 44         53 my %values_for_group_property;
2505 44         65 foreach my $group_property ( @$group_by ) {
2506 19         64 my @values = $result->$group_property;
2507 19 100       40 if (@values) {
2508 16         37 $values_for_group_property{$group_property} = \@values;
2509             } else {
2510 3         10 $values_for_group_property{$group_property} = [ undef ];
2511             }
2512             }
2513 44         145 my @combinations = UR::Util::combinations_of_values(map { $values_for_group_property{$_} } @$group_by);
  19         72  
2514 44         63 foreach my $extra_values ( @combinations ) {
2515 45         145 my $bx = $sub_template->get_rule_for_values(@$values,@$extra_values);
2516 45 100       92 next if $seen{$bx->id}++;
2517 26         58 my $group = $set_class->get($bx->id);
2518 26         100 push @groups, $group;
2519             }
2520             }
2521 29         82 return @groups;
2522             }
2523              
2524             sub _loading_was_done_before_with_a_superset_of_this_rule {
2525 1990     1990   2994 my($self,$rule) = @_;
2526              
2527 1990         4861 my $template = $rule->template;
2528              
2529 1990 50 66     5293 if (exists $UR::Context::all_params_loaded->{$template->id}
2530             and exists $UR::Context::all_params_loaded->{$template->id}->{$rule->id}
2531             ) {
2532 0         0 return 1;
2533             }
2534              
2535 1990 100       5256 if ($template->subject_class_name->isa("UR::Value")) {
2536 107         314 return;
2537             }
2538              
2539 1883         4984 my @rule_values = $rule->values;
2540 1883         6114 my @rule_param_names = $template->_property_names;
2541 1883         2490 my %rule_values;
2542 1883         5344 for (my $i = 0; $i < @rule_param_names; $i++) {
2543 3045         7683 $rule_values{ $rule_param_names[$i] } = $rule_values[$i];
2544             }
2545              
2546 1883         7211 foreach my $loaded_template_id ( keys %$UR::Context::all_params_loaded ) {
2547 7351         12475 my $loaded_template = UR::BoolExpr::Template->get($loaded_template_id);
2548 7351 100       13065 if($template->is_subset_of($loaded_template)) {
2549             # Try limiting the possibilities by matching the previously-loaded rule value_id's
2550             # on this rule's values
2551 628         1459 my @param_names = $loaded_template->_property_names;
2552 628         1504 my @values = @rule_values{ @param_names };
2553 628         648 my $value_id;
2554 266     266   1468 { no warnings 'uninitialized';
  266         468  
  266         355899  
  628         628  
2555 628         1457 $value_id = join($UR::BoolExpr::Util::record_sep, @values);
2556             }
2557 628         794 my @candidates = grep { index($_, $value_id) > 0 } keys(%{ $UR::Context::all_params_loaded->{$loaded_template_id} });
  1072         2308  
  628         1861  
2558 628         1502 foreach my $loaded_rule_id ( @candidates ) {
2559 140         458 my $loaded_rule = UR::BoolExpr->get($loaded_rule_id);
2560 140 100       418 return 1 if ($rule->is_subset_of($loaded_rule));
2561             }
2562             }
2563             }
2564 1753         6628 return;
2565             }
2566              
2567              
2568              
2569             sub _forget_loading_was_done_with_template_and_rule {
2570 3     3   5 my($self,$template_id, $rule_id) = @_;
2571              
2572 3         10 delete $all_params_loaded->{$template_id}->{$rule_id};
2573             }
2574              
2575             # Given a list of values, returns a list of lists containing all subsets of
2576             # the input list, including the original list and the empty list
2577             sub _get_all_subsets_of_params {
2578 0     0   0 my $self = shift;
2579              
2580 0 0       0 return [] unless @_;
2581 0         0 my $first = shift;
2582 0         0 my @rest = $self->_get_all_subsets_of_params(@_);
2583 0         0 return @rest, map { [$first, @$_ ] } @rest;
  0         0  
2584             }
2585              
2586             sub query_underlying_context {
2587 337010     337010 1 266933 my $self = shift;
2588 337010 50       473461 unless (ref $self) {
2589 0         0 $self = $self->current;
2590             }
2591 337010 50       451512 if (@_) {
2592 0         0 $self->{'query_underlying_context'} = shift;
2593             }
2594 337010         804368 return $self->{'query_underlying_context'};
2595             }
2596              
2597              
2598             # all of these delegate to the current context...
2599              
2600             sub has_changes {
2601 0     0 1 0 return shift->get_current->has_changes(@_);
2602             }
2603              
2604             sub commit {
2605 52 50   52 1 5186 Carp::carp 'UR::Context::commit() called as a function, not a method. Assumming commit on current context' unless @_;
2606              
2607 52         108 my $self = shift;
2608 52 100       299 $self = UR::Context->current() unless ref $self;
2609              
2610 52         350 $self->__signal_change__('precommit');
2611              
2612 52 100       292 unless ($self->_sync_databases) {
2613 3         13 $self->__signal_observers__('sync_databases', 0);
2614 3         14 $self->__signal_change__('commit',0);
2615 3         15 return;
2616             }
2617              
2618 45         328 $self->__signal_observers__('sync_databases', 1);
2619              
2620 45 50       338 unless ($self->_commit_databases) {
2621 0         0 $self->__signal_change__('commit',0);
2622 0         0 die "Application failure during commit!";
2623             }
2624 45         217 $self->__signal_change__('commit',1);
2625              
2626 45         390 $_->delete foreach UR::Change->get();
2627              
2628 45         132 foreach ( $self->all_objects_loaded('UR::Object') ) {
2629 27177         24529 delete $_->{'_change_count'};
2630             }
2631              
2632 45         2001 return 1;
2633             }
2634              
2635             sub rollback {
2636 15     15 1 1006 my $self = shift;
2637 15 50       49 unless ($self) {
2638 0         0 warn 'UR::Context::rollback() called as a function, not a method. Assumming rollback on current context';
2639 0         0 $self = UR::Context->current();
2640             }
2641 15         75 $self->__signal_change__('prerollback');
2642              
2643 15 50       57 unless ($self->_reverse_all_changes) {
2644 0         0 $self->__signal_change__('rollback', 0);
2645 0         0 die "Application failure during reverse_all_changes?!";
2646             }
2647 15 50       96 unless ($self->_rollback_databases) {
2648 0         0 $self->__signal_change__('rollback', 0);
2649 0         0 die "Application failure during rollback!";
2650             }
2651 15         78 $self->__signal_change__('rollback', 1);
2652 15         57 return 1;
2653             }
2654              
2655             sub _tmp_self {
2656 3     3   4 my $self = shift;
2657 3 50       10 if (ref($self)) {
2658 3         11 return ($self,ref($self));
2659             }
2660             else {
2661 0         0 return ($UR::Context::current, $self);
2662             }
2663             }
2664              
2665             sub clear_cache {
2666 3     3 1 14 my ($self,$class) = _tmp_self(shift @_);
2667 3         8 my %args = @_;
2668              
2669             # dont unload any of the infrastructional classes, or any classes
2670             # the user requested to be saved
2671 3         5 my %local_dont_unload;
2672 3 50       11 if ($args{'dont_unload'}) {
2673 0         0 for my $class_name (@{$args{'dont_unload'}}) {
  0         0  
2674 0         0 $local_dont_unload{$class_name} = 1;
2675 0         0 for my $subclass_name ($class_name->__meta__->subclasses_loaded) {
2676 0         0 $local_dont_unload{$subclass_name} = 1;
2677             }
2678             }
2679             }
2680              
2681 3         17 for my $class_name (UR::Object->__meta__->subclasses_loaded) {
2682              
2683             # Once transactions are fully implemented, the command params will sit
2684             # beneath the regular transaction, so we won't need this. For now,
2685             # we need a work-around.
2686 287 50       372 next if $class_name eq "UR::Command::Param";
2687 287 100       1684 next if $class_name->isa('UR::Singleton');
2688            
2689 257         797 my $class_obj = $class_name->__meta__;
2690             #if ($class_obj->data_source and $class_obj->is_transactional) {
2691             # # normal
2692             #}
2693             #elsif (!$class_obj->data_source and !$class_obj->is_transactional) {
2694             # # expected
2695             #}
2696             #elsif ($class_obj->data_source and !$class_obj->is_transactional) {
2697             # Carp::confess("!!!!!data source on non-transactional class $class_name?");
2698             #}
2699             #elsif (!$class_obj->data_source and $class_obj->is_transactional) {
2700             # # okay
2701             #}
2702              
2703 257 100       577 next unless $class_obj->is_uncachable;
2704 233 100       462 next if $class_obj->is_meta_meta;
2705 227 100       447 next unless $class_obj->is_transactional;
2706              
2707             next if ($local_dont_unload{$class_name} ||
2708 215 50 33     308 grep { $class_name->isa($_) } @{$args{'dont_unload'}});
  0         0  
  215         515  
2709              
2710 215 100       436 next if $class_obj->is_meta;
2711              
2712 76 100       199 next if not defined $class_obj->data_source;
2713              
2714 30         58 for my $obj ($self->all_objects_loaded_unsubclassed($class_name)) {
2715             # Check the type against %local_dont_unload again, because all_objects_loaded()
2716             # will return child class objects, as well as the class you asked for. For example,
2717             # GSC::DNA->a_o_l() will also return GSC::ReadExp objects, and the user may have wanted
2718             # to save those. We also check whether the $obj type isa one of the requested classes
2719             # because, for example, GSC::Sequence->a_o_l returns GSC::ReadExp types, and the user
2720             # may have wanted to save all GSC::DNAs
2721 18         18 my $obj_type = ref $obj;
2722             next if ($local_dont_unload{$obj_type} ||
2723 18 50 33     37 grep {$obj_type->isa($_) } @{$args{'dont_unload'}});
  0         0  
  18         52  
2724 18         54 $obj->unload;
2725             }
2726 30         25 my @obj = grep { defined($_) } values %{ $UR::Context::all_objects_loaded->{$class_name} };
  4         12  
  30         54  
2727 30 100       50 if (@obj) {
2728             $class->warning_message("Skipped unload of $class_name objects during clear_cache: "
2729 3         14 . join(",",map { $_->id } @obj )
  4         13  
2730             . "\n"
2731             );
2732 3 50       6 if (my @changed = grep { $_->__changes__ } @obj) {
  4         17  
2733 0         0 require YAML;
2734 0         0 $class->error_message(
2735             "The following objects have changes:\n"
2736             . Data::Dumper::Dumper(\@changed)
2737             . "The clear_cache method cannot be called with unsaved changes on objects.\n"
2738             . "Use reverse_all_changes() first to really undo everything, then clear_cache(),"
2739             . " or call sync_database() and clear_cache() if you want to just lighten memory but keep your changes.\n"
2740             . "Clearing the cache with active changes will be supported after we're sure all code like this is gone. :)\n"
2741             );
2742 0         0 exit 1;
2743             }
2744             }
2745 30         59 delete $UR::Context::all_objects_loaded->{$class_name};
2746 30         31 delete $UR::Context::all_objects_are_loaded->{$class_name};
2747 30         34 delete $UR::Context::all_params_loaded->{$class_name};
2748             }
2749 3         59 1;
2750             }
2751              
2752             sub _order_data_sources_for_saving {
2753 103     103   4456 my @data_sources = @_;
2754              
2755 103         180 my %can_savepoint = map { $_->id => $_->can_savepoint } @data_sources;
  242         693  
2756 103         215 my %classes = map { $_->id => $_->class } @data_sources;
  242         483  
2757 103 100       217 my %is_default = map { $_->id => $_->isa('UR::DataSource::Default') ? 1 : 0 } @data_sources; # Default data sources go last
  242         437  
2758              
2759             return
2760             sort {
2761 103         466 $is_default{$a->id} <=> $is_default{$b->id}
2762             ||
2763             $can_savepoint{$a->id} <=> $can_savepoint{$b->id}
2764             ||
2765 199 50 100     395 $classes{$a->id} cmp $classes{$b->id}
2766             }
2767             @data_sources;
2768             }
2769              
2770              
2771             our $IS_SYNCING_DATABASE = 0;
2772             sub _sync_databases {
2773 54     54   466 my $self = shift;
2774 54         116 my %params = @_;
2775              
2776             # Glue App::DB->sync_database with UR::Context->_sync_databases()
2777             # and avoid endless recursion.
2778             # FIXME Remove this when we're totally off of the old API
2779             # You'll also want to remove all the gotos from this function and uncomment
2780             # the returns
2781 54 50       174 return 1 if $IS_SYNCING_DATABASE;
2782 54         106 $IS_SYNCING_DATABASE = 1;
2783 54 50       204 if ($App::DB::{'sync_database'}) {
2784 0 0       0 unless (App::DB->sync_database() ) {
2785 0         0 $IS_SYNCING_DATABASE = 0;
2786 0         0 $self->error_message(App::DB->error_message());
2787 0         0 return;
2788             }
2789             }
2790 54         90 $IS_SYNCING_DATABASE = 0; # This should be far down enough to avoid recursion, right?
2791            
2792 54         261 my @o = grep { ref($_) eq 'UR::DeletedRef' } $self->all_objects_loaded('UR::Object');
  30744         28351  
2793 54 50       1963 if (@o) {
2794 0         0 print Data::Dumper::Dumper(\@o);
2795 0         0 Carp::confess();
2796             }
2797              
2798             # Determine what has changed.
2799             my @changed_objects = (
2800             $self->all_objects_loaded('UR::Object::Ghost'),
2801 54         221 grep { $_->__changes__ } $self->all_objects_loaded('UR::Object')
  30744         44297  
2802             #UR::Util->mapreduce_grep(sub { $_[0]->__changes__ },$self->all_objects_loaded('UR::Object'))
2803             );
2804              
2805 54 50       2506 return 1 unless (@changed_objects);
2806              
2807             # Ensure validity.
2808             # This is primarily to catch custom validity logic in class overrides.
2809 54         160 my @invalid = grep { $_->__errors__ } @changed_objects;
  433         1129  
2810             #my @invalid = UR::Util->mapreduce_grep(sub { $_[0]->__errors__}, @changed_objects);
2811 54 100       197 if (@invalid) {
2812 1         8 $self->display_invalid_data_for_save(\@invalid);
2813 1         6 goto PROBLEM_SAVING;
2814             #return;
2815             }
2816              
2817             # group changed objects by data source
2818 53         98 my %ds_objects;
2819 53         134 for my $obj (@changed_objects) {
2820 427         708 my $data_source = $self->resolve_data_source_for_object($obj);
2821 427 100       639 next unless $data_source;
2822 147         466 my $data_source_id = $data_source->id;
2823 147   100     684 $ds_objects{$data_source_id} ||= { 'ds_obj' => $data_source, 'changed_objects' => []};
2824 147         142 push @{ $ds_objects{$data_source_id}->{'changed_objects'} }, $obj;
  147         361  
2825             }
2826              
2827             my @ds_in_order =
2828 56         145 map { $_->id }
2829 53         192 _order_data_sources_for_saving(map { $_->{ds_obj} } values(%ds_objects));
  56         249  
2830              
2831             # save on each in succession
2832 53         107 my @done;
2833             my $rollback_on_non_savepoint_handle;
2834 53         121 for my $data_source_id (@ds_in_order) {
2835 56         97 my $obj_list = $ds_objects{$data_source_id}->{'changed_objects'};
2836 56         105 my $data_source = $ds_objects{$data_source_id}->{'ds_obj'};
2837 56         434 my $result = $data_source->_sync_database(
2838             %params,
2839             changed_objects => $obj_list,
2840             );
2841 52 100       186 if ($result) {
2842 50         119 push @done, $data_source;
2843 50         167 next;
2844             }
2845             else {
2846 2         12 $self->error_message(
2847             "Failed to sync data source: $data_source_id: "
2848             . $data_source->error_message
2849             );
2850 2         9 for my $prev_data_source (@done) {
2851 0         0 $prev_data_source->_reverse_sync_database;
2852             }
2853 2         24 goto PROBLEM_SAVING;
2854             #return;
2855             }
2856             }
2857            
2858 47         422 return 1;
2859              
2860             PROBLEM_SAVING:
2861 3 50       12 if ($App::DB::{'rollback'}) {
2862 0         0 App::DB->rollback();
2863             }
2864 3         29 return;
2865             }
2866              
2867              
2868             sub display_invalid_data_for_save {
2869 2     2 0 4 my $self = shift;
2870 2         3 my @objects_with_errors = @{shift @_};
  2         4  
2871              
2872 2         24 $self->error_message('Invalid data for save!');
2873              
2874 2         4 for my $obj (@objects_with_errors) {
2875 266     266   1504 no warnings;
  266         475  
  266         305979  
2876 4   33     14 my $identifier = eval { $obj->__display_name__ } || $obj->id;
2877 4         11 my $msg = $obj->class . " identified by " . $identifier . " has problems on\n";
2878 4         11 my @problems = $obj->__errors__;
2879 4         8 foreach my $error ( @problems ) {
2880 5         10 $msg .= $error->__display_name__ . "\n";
2881             }
2882              
2883 4         6 $msg .= " Current state:\n";
2884 4         15 my $datadumper = Data::Dumper::Dumper($obj);
2885 4         299 my $nr_of_lines = $datadumper =~ tr/\n//;
2886 4 50       10 if ($nr_of_lines > 40) {
2887             # trim it down to the first 15 and last 3 lines
2888 0         0 $datadumper =~ m/^((?:.*\n){15})/;
2889 0         0 $msg .= $1;
2890 0         0 $datadumper =~ m/((?:.*(?:\n|$)){3})$/;
2891 0         0 $msg .= "[...]\n$1\n";
2892             } else {
2893 4         6 $msg .= $datadumper;
2894             }
2895 4         10 $self->error_message($msg);
2896             }
2897              
2898 2         9 return 1;
2899             }
2900              
2901              
2902             sub _reverse_all_changes {
2903 15     15   23 my $self = shift;
2904 15         24 my $class;
2905 15 100       40 if (ref($self)) {
2906 1         2 $class = ref($self);
2907             }
2908             else {
2909 14         20 $class = $self;
2910 14         24 $self = $UR::Context::current;
2911             }
2912              
2913 15         33 @UR::Context::Transaction::open_transaction_stack = ();
2914 15         38 @UR::Context::Transaction::change_log = ();
2915 15         21 $UR::Context::Transaction::log_all_changes = 0;
2916 15         30 $UR::Context::current = $UR::Context::process;
2917              
2918             my @objects =
2919 1241         1109 map { $self->all_objects_loaded_unsubclassed($_) }
2920 1361         3901 grep { $_->__meta__->is_transactional }
2921 15         84 grep { ! $_->isa('UR::Value') }
  1440         6153  
2922             sort UR::Object->__meta__->subclasses_loaded();
2923              
2924 15         107 for my $object (@objects) {
2925 972         5926 $object->__rollback__();
2926             }
2927              
2928 15         149 return 1;
2929             }
2930              
2931             our $IS_COMMITTING_DATABASE = 0;
2932             sub _commit_databases {
2933 45     45   87 my $class = shift;
2934              
2935             # Glue App::DB->commit() with UR::Context->_commit_databases()
2936             # and avoid endless recursion.
2937             # FIXME Remove this when we're totally off of the old API
2938 45 50       415 return 1 if $IS_COMMITTING_DATABASE;
2939 45         84 $IS_COMMITTING_DATABASE = 1;
2940 45 50       173 if ($App::DB::{'commit'}) {
2941 0 0       0 unless (App::DB->commit() ) {
2942 0         0 $IS_COMMITTING_DATABASE = 0;
2943 0         0 $class->error_message(App::DB->error_message());
2944 0         0 return;
2945             }
2946             }
2947 45         76 $IS_COMMITTING_DATABASE = 0;
2948              
2949 45         189 my @ds_in_order = _order_data_sources_for_saving($UR::Context::current->all_objects_loaded('UR::DataSource'));
2950 45         117 my @committed;
2951 45         118 foreach my $ds ( @ds_in_order ) {
2952 166 50       791 if ($ds->commit) {
2953 166         323 push @committed, $ds;
2954             } else {
2955 0         0 my $message = 'Data source ' . $ds->get_name . ' failed to commit: ' . join("\n\t", $ds->error_messages);
2956 0 0       0 if (@committed) {
2957             $message .= "\nThese data sources were successfully committed, resulting in a FRAGMENTED DISTRIBUTED TRANSACTION: "
2958 0         0 . join(', ', map { $_->get_name } @committed);
  0         0  
2959             }
2960 0         0 Carp::croak($message);
2961             }
2962             }
2963              
2964 45         222 return 1;
2965             }
2966              
2967              
2968             our $IS_ROLLINGBACK_DATABASE = 0;
2969             sub _rollback_databases {
2970 15     15   37 my $class = shift;
2971              
2972             # Glue App::DB->rollback() with UR::Context->_rollback_databases()
2973             # and avoid endless recursion.
2974             # FIXME Remove this when we're totally off of the old API
2975 15 50       48 return 1 if $IS_ROLLINGBACK_DATABASE;
2976 15         27 $IS_ROLLINGBACK_DATABASE = 1;
2977 15 50       56 if ($App::DB::{'rollback'}) {
2978 0 0       0 unless (App::DB->rollback()) {
2979 0         0 $IS_ROLLINGBACK_DATABASE = 0;
2980 0         0 $class->error_message(App::DB->error_message());
2981 0         0 return;
2982             }
2983             }
2984 15         24 $IS_ROLLINGBACK_DATABASE = 0;
2985              
2986 15 50       59 $class->_for_each_data_source("rollback")
2987             or die "FAILED TO ROLLBACK!: " . $class->error_message;
2988 15         49 return 1;
2989             }
2990              
2991             sub _disconnect_databases {
2992 0     0   0 my $class = shift;
2993 0         0 $class->_for_each_data_source("disconnect");
2994 0         0 return 1;
2995             }
2996              
2997             sub _for_each_data_source {
2998 15     15   33 my($class,$method) = @_;
2999              
3000 15         89 my @ds = $UR::Context::current->all_objects_loaded('UR::DataSource');
3001 15         38 foreach my $ds ( @ds ) {
3002 29 50       168 unless ($ds->$method) {
3003 0         0 $class->error_message("$method failed on DataSource ",$ds->get_name);
3004 0         0 return;
3005             }
3006             }
3007 15         82 return 1;
3008             }
3009              
3010             sub _get_committed_property_value {
3011 5     5   8 my $class = shift;
3012 5         7 my $object = shift;
3013 5         8 my $property_name = shift;
3014              
3015 5 50       14 if ($object->{'db_committed'}) {
    0          
3016 5         21 return $object->{'db_committed'}->{$property_name};
3017             } elsif ($object->{'db_saved_uncommitted'}) {
3018 0         0 return $object->{'db_saved_uncommitted'}->{$property_name};
3019             } else {
3020 0         0 return;
3021             }
3022             }
3023              
3024             sub _dump_change_snapshot {
3025 0     0   0 my $class = shift;
3026 0         0 my %params = @_;
3027              
3028 0         0 my @c = grep { $_->__changes__ } $UR::Context::current->all_objects_loaded('UR::Object');
  0         0  
3029              
3030 0         0 my $fh;
3031 0 0       0 if (my $filename = $params{filename})
3032             {
3033 0         0 $fh = IO::File->new(">$filename");
3034 0 0       0 unless ($fh)
3035             {
3036 0         0 $class->error_message("Failed to open file $filename: $!");
3037 0         0 return;
3038             }
3039             }
3040             else
3041             {
3042 0         0 $fh = "STDOUT";
3043             }
3044 0         0 require YAML;
3045 0         0 $fh->print(YAML::Dump(\@c));
3046 0         0 $fh->close;
3047             }
3048              
3049             sub reload {
3050 136     136 1 13154 my $self = shift;
3051              
3052             # this is here for backward external compatability
3053             # get() now goes directly to the context
3054            
3055 136         204 my $class = shift;
3056 136 100       420 if (ref $class) {
3057             # Trying to reload a specific object?
3058 44 50       153 if (@_) {
3059 0         0 Carp::confess("load() on an instance with parameters is not supported");
3060 0         0 return;
3061             }
3062 44         183 @_ = ('id' ,$class->id());
3063 44         101 $class = ref $class;
3064             }
3065              
3066 136         597 my ($rule, @extra) = UR::BoolExpr->resolve_normalized($class,@_);
3067            
3068 136 50       402 if (@extra) {
3069 0 0 0     0 if (scalar @extra == 2 and ($extra[0] eq "sql" or $extra[0] eq 'sql in')) {
      0        
3070 0         0 return $UR::Context::current->_get_objects_for_class_and_sql($class,$extra[1]);
3071             }
3072             else {
3073 0         0 die "Odd parameters passed directly to $class load(): @extra.\n"
3074             . "Processable params were: "
3075             . Data::Dumper::Dumper({ $rule->params_list });
3076             }
3077             }
3078 136         453 return $UR::Context::current->get_objects_for_class_and_rule($class,$rule,1);
3079             }
3080              
3081             ## This is old, untested code that we may wany to resurrect at some point
3082             #
3083             #our $CORE_DUMP_VERSION = 1;
3084             ## Use Data::Dumper to save a representation of the object cache to a file. Args are:
3085             ## filename => the name of the file to save to
3086             ## dumpall => boolean flagging whether to dump _everything_, or just the things
3087             ## that would actually be loaded later in core_restore()
3088             #
3089             #sub _core_dump {
3090             # my $class = shift;
3091             # my %args = @_;
3092             #
3093             # my $filename = $args{'filename'} || "/tmp/core." . UR::Context::Process->prog_name . ".$ENV{HOST}.$$";
3094             # my $dumpall = $args{'dumpall'};
3095             #
3096             # my $fh = IO::File->new(">$filename");
3097             # if (!$fh) {
3098             # $class->error_message("Can't open dump file $filename for writing: $!");
3099             # return undef;
3100             # }
3101             #
3102             # my $dumper;
3103             # if ($dumpall) { # Go ahead and dump everything
3104             # $dumper = Data::Dumper->new([$CORE_DUMP_VERSION,
3105             # $UR::Context::all_objects_loaded,
3106             # $UR::Context::all_objects_are_loaded,
3107             # $UR::Context::all_params_loaded,
3108             # $UR::Context::all_change_subscriptions],
3109             # ['dump_version','all_objects_loaded','all_objects_are_loaded',
3110             # 'all_params_loaded','all_change_subscriptions']);
3111             # } else {
3112             # my %DONT_UNLOAD =
3113             # map {
3114             # my $co = $_->__meta__;
3115             # if ($co and not $co->is_transactional) {
3116             # ($_ => 1)
3117             # }
3118             # else {
3119             # ()
3120             # }
3121             # }
3122             # $UR::Context::current->all_objects_loaded('UR::Object');
3123             #
3124             # my %aol = map { ($_ => $UR::Context::all_objects_loaded->{$_}) }
3125             # grep { ! $DONT_UNLOAD{$_} } keys %$UR::Context::all_objects_loaded;
3126             # my %aoal = map { ($_ => $UR::Context::all_objects_are_loaded->{$_}) }
3127             # grep { ! $DONT_UNLOAD{$_} } keys %$UR::Context::all_objects_are_loaded;
3128             # my %apl = map { ($_ => $UR::Context::all_params_loaded->{$_}) }
3129             # grep { ! $DONT_UNLOAD{$_} } keys %$UR::Context::all_params_loaded;
3130             # # don't dump $UR::Context::all_change_subscriptions
3131             # $dumper = Data::Dumper->new([$CORE_DUMP_VERSION,\%aol, \%aoal, \%apl],
3132             # ['dump_version','all_objects_loaded','all_objects_are_loaded',
3133             # 'all_params_loaded']);
3134             #
3135             # }
3136             #
3137             # $dumper->Purity(1); # For dumping self-referential data structures
3138             # $dumper->Sortkeys(1); # Makes quick and dirty file comparisons with sum/diff work correctly-ish
3139             #
3140             # $fh->print($dumper->Dump() . "\n");
3141             #
3142             # $fh->close;
3143             #
3144             # return $filename;
3145             #}
3146             #
3147             ## Read a file previously generated with core_dump() and repopulate the object cache. Args are:
3148             ## filename => name of the coredump file
3149             ## force => boolean flag whether to go ahead and attempt to load the file even if it thinks
3150             ## there is a formatting problem
3151             #sub _core_restore {
3152             # my $class = shift;
3153             # my %args = @_;
3154             # my $filename = $args{'filename'};
3155             # my $forcerestore = $args{'force'};
3156             #
3157             # my $fh = IO::File->new("$filename");
3158             # if (!$fh) {
3159             # $class->error_message("Can't open dump file $filename for restoring: $!");
3160             # return undef;
3161             # }
3162             #
3163             # my $code;
3164             # while (<$fh>) { $code .= $_ }
3165             #
3166             # my($dump_version,$all_objects_loaded,$all_objects_are_loaded,$all_params_loaded,$all_change_subscriptions);
3167             # eval $code;
3168             #
3169             # if ($@)
3170             # {
3171             # $class->error_message("Failed to restore core file state: $@");
3172             # return undef;
3173             # }
3174             # if ($dump_version != $CORE_DUMP_VERSION) {
3175             # $class->error_message("core file's version $dump_version differs from expected $CORE_DUMP_VERSION");
3176             # return 0 unless $forcerestore;
3177             # }
3178             #
3179             # my %DONT_UNLOAD =
3180             # map {
3181             # my $co = $_->__meta__;
3182             # if ($co and not $co->is_transactional) {
3183             # ($_ => 1)
3184             # }
3185             # else {
3186             # ()
3187             # }
3188             # }
3189             # $UR::Context::current->all_objects_loaded('UR::Object');
3190             #
3191             # # Go through the loaded all_objects_loaded, prune out the things that
3192             # # are in %DONT_UNLOAD
3193             # my %loaded_classes;
3194             # foreach ( keys %$all_objects_loaded ) {
3195             # next if ($DONT_UNLOAD{$_});
3196             # $UR::Context::all_objects_loaded->{$_} = $all_objects_loaded->{$_};
3197             # $loaded_classes{$_} = 1;
3198             #
3199             # }
3200             # foreach ( keys %$all_objects_are_loaded ) {
3201             # next if ($DONT_UNLOAD{$_});
3202             # $UR::Context::all_objects_are_loaded->{$_} = $all_objects_are_loaded->{$_};
3203             # $loaded_classes{$_} = 1;
3204             # }
3205             # foreach ( keys %$all_params_loaded ) {
3206             # next if ($DONT_UNLOAD{$_});
3207             # $UR::Context::all_params_loaded->{$_} = $all_params_loaded->{$_};
3208             # $loaded_classes{$_} = 1;
3209             # }
3210             # # $UR::Context::all_change_subscriptions is basically a bunch of coderef
3211             # # callbacks that can't reliably be dumped anyway, so we skip it
3212             #
3213             # # Now, get the classes to instantiate themselves
3214             # foreach ( keys %loaded_classes ) {
3215             # $_->class() unless m/::Ghost$/;
3216             # }
3217             #
3218             # return 1;
3219             #}
3220              
3221             1;
3222              
3223             =pod
3224              
3225             =head1 NAME
3226              
3227             UR::Context - Manage the current state of the application
3228              
3229             =head1 SYNOPSIS
3230              
3231             use AppNamespace;
3232              
3233             my $obj = AppNamespace::SomeClass->get(id => 1234);
3234             $obj->some_property('I am changed');
3235              
3236             UR::Context->get_current->rollback; # some_property reverts to its original value
3237              
3238             $obj->other_property('Now, I am changed');
3239              
3240             UR::Context->commit; # other_property now permanently has that value
3241              
3242              
3243             =head1 DESCRIPTION
3244              
3245             The main application code will rarely interact with UR::Context objects
3246             directly, except for the C and C methods. It manages
3247             the mappings between an application's classes, object cache, and external
3248             data sources.
3249              
3250             =head1 SUBCLASSES
3251              
3252             UR::Context is an abstract class. When an application starts up, the system
3253             creates a handful of Contexts that logically exist within one another:
3254              
3255             =over 2
3256              
3257             =item 1.
3258             L - A context to represent all the data reachable in the
3259             application's namespace. It connects the application to external data
3260             sources.
3261              
3262             =item 2.
3263             L - A context to represent the state of data within
3264             the currently running application. It handles the transfer of data to and
3265             from the Root context, through the object cache, on behalf of the application
3266             code.
3267              
3268             =item 3.
3269             L - A context to represent an in-memory transaction
3270             as a diff of the object cache. The Transaction keeps a list of changes to
3271             objects and is able to revert those changes with C, or apply them
3272             to the underlying context with C.
3273              
3274             =back
3275              
3276             =head1 CONSTRUCTOR
3277              
3278             =over 4
3279              
3280             =item begin
3281              
3282             my $trans = UR::Context::Transaction->begin();
3283              
3284             L instances are created through C.
3285              
3286             =back
3287              
3288             A L and L context will be created
3289             for you when the application initializes. Additional instances of these
3290             classes are not usually instantiated.
3291              
3292             =head1 METHODS
3293              
3294             Most of the methods below can be called as either a class or object method
3295             of UR::Context. If called as a class method, they will operate on the current
3296             context.
3297              
3298             =over 4
3299              
3300             =item get_current
3301              
3302             my $context = UR::Context::get_current();
3303              
3304             Returns the UR::Context instance of whatever is the most currently created
3305             Context. Can be called as a class or object method.
3306              
3307             =item query_underlying_context
3308              
3309             my $should_load = $context->query_underlying_context();
3310             $context->query_underlying_context(1);
3311              
3312             A property of the Context that sets the default value of the C<$should_load>
3313             flag inside C as described below. Initially,
3314             its value is undef, meaning that during a get(), the Context will query the
3315             underlying data sources only if this query has not been done before. Setting
3316             this property to 0 will make the Context never query data sources, meaning
3317             that the only objects retrievable are those already in memory. Setting the
3318             property to 1 means that every query will hit the data sources, even if the
3319             query has been done before.
3320              
3321             =item get_objects_for_class_and_rule
3322              
3323             @objs = $context->get_objects_for_class_and_rule(
3324             $class_name,
3325             $boolexpr,
3326             $should_load,
3327             $should_return_iterator
3328             );
3329              
3330             This is the method that serves as the main entry point to the Context behind
3331             the C, and C methods of L, and C method
3332             of UR::Context.
3333              
3334             C<$class_name> and C<$boolexpr> are required arguments, and specify the
3335             target class by name and the rule used to filter the objects the caller
3336             is interested in.
3337              
3338             C<$should_load> is a flag indicating whether the Context should load objects
3339             satisfying the rule from external data sources. A true value means it should
3340             always ask the relevant data sources, even if the Context believes the
3341             requested data is in the object cache, A false but defined value means the
3342             Context should not ask the data sources for new data, but only return what
3343             is currently in the cache matching the rule. The value C means the
3344             Context should use the value of its query_underlying_context property. If
3345             that is also undef, then it will use its own judgement about asking the
3346             data sources for new data, and will merge cached and external data as
3347             necessary to fulfill the request.
3348              
3349             C<$should_return_iterator> is a flag indicating whether this method should
3350             return the objects directly as a list, or iterator function instead. If
3351             true, it returns a subref that returns one object each time it is called,
3352             and undef after the last matching object:
3353              
3354             my $iter = $context->get_objects_for_class_and_rule(
3355             'MyClass',
3356             $rule,
3357             undef,
3358             1
3359             );
3360             my @objs;
3361             while (my $obj = $iter->());
3362             push @objs, $obj;
3363             }
3364              
3365             =item has_changes
3366              
3367             my $bool = $context->has_changes();
3368              
3369             Returns true if any objects in the given Context's object cache (or the
3370             current Context if called as a class method) have any changes that haven't
3371             been saved to the underlying context.
3372              
3373             =item commit
3374              
3375             UR::Context->commit();
3376              
3377             Causes all objects with changes to save their changes back to the underlying
3378             context. If the current context is a L, then the
3379             changes will be applied to whatever Context the transaction is a part of.
3380             if the current context is a L context, then C
3381             pushes the changes to the underlying L context, meaning
3382             that those changes will be applied to the relevant data sources.
3383              
3384             In the usual case, where no transactions are in play and all data sources
3385             are RDBMS databases, calling C will cause the program to begin
3386             issuing SQL against the databases to update changed objects, insert rows
3387             for newly created objects, and delete rows from deleted objects as part of
3388             an SQL transaction. If all the changes apply cleanly, it will do and SQL
3389             C, or C if not.
3390              
3391             commit() returns true if all the changes have been safely transferred to the
3392             underlying context, false if there were problems.
3393              
3394             =item rollback
3395              
3396             UR::Context->rollback();
3397              
3398             Causes all objects' changes for the current transaction to be reversed.
3399             If the current context is a L, then the
3400             transactional properties of those objects will be reverted to the values
3401             they had when the transaction started. Outside of a transaction, object
3402             properties will be reverted to their values when they were loaded from the
3403             underlying data source. rollback() will also ask all the underlying
3404             databases to rollback.
3405              
3406             =item clear_cache
3407              
3408             UR::Context->clear_cache();
3409              
3410             Asks the current context to remove all non-infrastructional data from its
3411             object cache. This method will fail and return false if any object has
3412             changes.
3413              
3414             =item resolve_data_source_for_object
3415              
3416             my $ds = $obj->resolve_data_source_for_object();
3417              
3418             For the given C<$obj> object, return the L instance that
3419             object was loaded from or would be saved to. If objects of that class do
3420             not have a data source, then it will return C.
3421              
3422             =item resolve_data_sources_for_class_meta_and_rule
3423              
3424             my @ds = $context->resolve_data_sources_for_class_meta_and_rule($class_obj, $boolexpr);
3425              
3426             For the given class metaobject and boolean expression (rule), return the list of
3427             data sources that will need to be queried in order to return the objects
3428             matching the rule. In most cases, only one data source will be returned.
3429              
3430             =item infer_property_value_from_rule
3431              
3432             my $value = $context->infer_property_value_from_rule($property_name, $boolexpr);
3433              
3434             For the given boolean expression (rule), and a property name not mentioned in
3435             the rule, but is a property of the class the rule is against, return the value
3436             that property must logically have.
3437              
3438             For example, if this object is the only TestClass object where C is
3439             the value 'bar', it can infer that the TestClass property C must
3440             have the value 'blah' in the current context.
3441              
3442             my $obj = TestClass->create(id => 1, foo => 'bar', baz=> 'blah');
3443             my $rule = UR::BoolExpr->resolve('TestClass', foo => 'bar);
3444             my $val = $context->infer_property_value_from_rule('baz', $rule);
3445             # val now is 'blah'
3446              
3447             =item object_cache_size_highwater
3448              
3449             UR::Context->object_cache_size_highwater(5000);
3450             my $highwater = UR::Context->object_cache_size_highwater();
3451              
3452             Set or get the value for the Context's object cache pruning high water
3453             mark. The object cache pruner will be run during the next C if the
3454             cache contains more than this number of prunable objects. See the
3455             L section below for more information.
3456              
3457             =item object_cache_size_lowwater
3458              
3459             UR::Context->object_cache_size_lowwater(5000);
3460             my $lowwater = UR::Context->object_cache_size_lowwater();
3461              
3462             Set or get the value for the Context's object cache pruning high water
3463             mark. The object cache pruner will stop when the number of prunable objects
3464             falls below this number.
3465              
3466             =item prune_object_cache
3467              
3468             UR::Context->prune_object_cache();
3469              
3470             Manually run the object cache pruner.
3471              
3472             =item reload
3473              
3474             UR::Context->reload($object);
3475             UR::Context->reload('Some::Class', 'property_name', value);
3476              
3477             Ask the context to load an object's data from an underlying Context, even if
3478             the object is already cached. With a single parameter, it will use that
3479             object's ID parameters as the basis for querying the data source. C
3480             will also accept a class name and list of key/value parameters the same as
3481             C.
3482              
3483             =item _light_cache
3484              
3485             UR::Context->_light_cache(1);
3486              
3487             Turn on or off the light caching flag. Light caching alters the behavior
3488             of the object cache in that all object references in the cache are made weak
3489             by Scalar::Util::weaken(). This means that the application code must keep
3490             hold of any object references it wants to keep alive. Light caching defaults
3491             to being off, and must be explicitly turned on with this method.
3492              
3493             =back
3494              
3495             =head1 Custom observer aspects
3496              
3497             UR::Context sends signals for observers watching for some non-standard aspects.
3498              
3499             =over 2
3500              
3501             =item precommit
3502              
3503             After C has been called, but before any changes are saved to the
3504             data sources. The only parameters to the Observer's callback are the Context
3505             object and the aspect ("precommit").
3506              
3507             =item commit
3508              
3509             After C has been called, and after an attempt has been made to save
3510             the changes to the data sources. The parameters to the callback are the
3511             Context object, the aspect ("commit"), and a boolean value indicating whether
3512             the commit succeeded or not.
3513              
3514             =item prerollback
3515              
3516             After C has been called, but before and object state is reverted.
3517              
3518             =item rollback
3519              
3520             After C has been called, and after an attempt has been made to
3521             revert the state of all the loaded objects. The parameters to the callback
3522             are the Context object, the aspect ("rollback"), and a boolean value
3523             indicating whether the rollback succeeded or not.
3524              
3525             =back
3526              
3527             =head1 Data Concurrency
3528              
3529             Currently, the Context is optimistic about data concurrency, meaning that
3530             it does very little to prevent clobbering data in underlying Contexts during
3531             a commit() if other processes have changed an object's data after the Context
3532             has cached and object. For example, a database has an object with ID 1 and
3533             a property with value 'bob'. A program loads this object and changes the
3534             property to 'fred', but does not yet commit(). Meanwhile, another program
3535             loads the same object, changes the value to 'joe' and does commit(). Finally
3536             the first program calls commit(). The final value in the database will be
3537             'fred', and no exceptions will be raised.
3538              
3539             As part of the caching behavior, the Context keeps a record of what the
3540             object's state is as it's loaded from the underlying Context. This is how
3541             the Context knows what object have been changed during C.
3542              
3543             If an already cached object's data is reloaded as part of some other query,
3544             data consistency of each property will be checked. If there are no
3545             conflicting changes, then any differences between the object's initial state
3546             and the current state in the underlying Context will be applied to the
3547             object's notion of what it thinks its initial state is.
3548              
3549             In some future release, UR may support additional data concurrency methods
3550             such as pessimistic concurrency: check that the current state of all
3551             changed (or even all cached) objects in the underlying Context matches the
3552             initial state before committing changes downstream. Or allowing the object
3553             cache to operate in write-through mode for some or all classes.
3554              
3555             =head1 Internal Methods
3556              
3557             There are many methods in UR::Context meant to be used internally, but are
3558             worth documenting for anyone interested in the inner workings of the Context
3559             code.
3560              
3561             =over 4
3562              
3563             =item _create_import_iterator_for_underlying_context
3564              
3565             $subref = $context->_create_import_iterator_for_underlying_context(
3566             $boolexpr, $data_source, $serial_number
3567             );
3568             $next_obj = $subref->();
3569              
3570             This method is part of the object loading process, and is called by
3571             L when it is determined that the requested
3572             data does not exist in the object cache, and data should be brought in from
3573             another, underlying Context. Usually this means the data will be loaded
3574             from an external data source.
3575              
3576             C<$boolexpr> is the L rule, usually from the application code.
3577              
3578             C<$data_source> is the L that will be used to load data from.
3579              
3580             C<$serial_number> is used by the object cache pruner. Each object loaded
3581             through this iterator will have $serial_number in its C<__get_serial> hashref
3582             key.
3583              
3584             It works by first getting an iterator for the data source (the
3585             C<$db_iterator>). It calls L to find out
3586             how data is to be loaded and whether this request spans multiple data
3587             sources. It calls L to get
3588             a list of closures to transform the primary data source's data into UR
3589             objects, and L (if necessary) to get
3590             more closures that can load and join data from the primary to the secondary
3591             data source(s).
3592              
3593             It returns a subref that works as an iterator, loading and returning objects
3594             one at a time from the underlying context into the current context. It
3595             returns undef when there are no more objects to return.
3596              
3597             The returned iterator works by first asking the C<$db_iterator> for the next
3598             row of data as a listref. Asks the secondary data source joiners whether
3599             there is any matching data. Calls the object fabricator closures to convert
3600             the data source data into UR objects. If any of the object requires
3601             subclassing, then additional importing iterators are created to handle that.
3602             Finally, the objects matching the rule are returned to the caller one at a
3603             time.
3604              
3605             =item _resolve_query_plan_for_ds_and_bxt
3606              
3607             my $query_plan = $context->_resolve_query_plan_for_ds_and_bxt(
3608             $data_source,
3609             $boolexpr_tmpl
3610             );
3611             my($query_plan, @addl_info) = $context->_resolve_query_plan_for_ds_and_bxt(
3612             $data_source,
3613             $boolexpr_tmpl
3614             );
3615              
3616             When a request is made that will hit one or more data sources,
3617             C<_resolve_query_plan_for_ds_and_bxt> is used to call a method of the same name
3618             on the data source. It returns a hashref used by many other parts of the
3619             object loading system, and describes what data source to use, how to query
3620             that data source to get the objects, how to use the raw data returned by
3621             the data source to construct objects and how to resolve any delegated
3622             properties that are a part of the rule.
3623              
3624             C<$data_source> is a L object ID. C<$coolexpr_tmpl> is a
3625             L object.
3626              
3627             In the common case, the query will only use one data source, and this method
3628             returns that data directly. But if the primary data source sets the
3629             C key on the data structure as may be the case
3630             when a rule involves a delegated property to a class that uses a different
3631             data source, then this methods returns an additional list of data. For
3632             each additional data source needed to resolve the query, this list will have
3633             three items:
3634              
3635             =over 2
3636              
3637             =item 1.
3638              
3639             The secondary data source ID
3640              
3641             =item 2.
3642              
3643             A listref of delegated L objects joining the primary
3644             data source to this secondary data source.
3645              
3646             =item 3.
3647              
3648             A L rule template applicable against the secondary
3649             data source
3650              
3651             =back
3652              
3653             =item _create_secondary_rule_from_primary
3654              
3655             my $new_rule = $context->_create_secondary_rule_from_primary(
3656             $primary_rule,
3657             $delegated_properties,
3658             $secondary_rule_tmpl
3659             );
3660              
3661             When resolving a request that requires multiple data sources,
3662             this method is used to construct a rule against applicable against the
3663             secondary data source. C<$primary_rule> is the L rule used
3664             in the original query. C<$delegated_properties> is a listref of
3665             L objects as returned by
3666             L linking the primary to the secondary data
3667             source. C<$secondary_rule_tmpl> is the rule template, also as returned by
3668             L.
3669              
3670             =item _create_secondary_loading_closures
3671              
3672             my($obj_importers, $joiners) = $context->_create_secondary_loading_closures(
3673             $primary_rule_tmpl,
3674             @addl_info);
3675              
3676             When reolving a request that spans multiple data sources,
3677             this method is used to construct two lists of subrefs to aid in the request.
3678             C<$primary_rule_tmpl> is the L rule template made
3679             from the original rule. C<@addl_info> is the same list returned by
3680             L. For each secondary data source, there
3681             will be one item in the two listrefs that are returned, and in the same
3682             order.
3683              
3684             C<$obj_importers> is a listref of subrefs used as object importers. They
3685             transform the raw data returned by the data sources into UR objects.
3686              
3687             C<$joiners> is also a listref of subrefs. These closures know how the
3688             properties link the primary data source data to the secondary data source.
3689             They take the raw data from the primary data source, load the next row of
3690             data from the secondary data source, and returns the secondary data that
3691             successfully joins to the primary data. You can think of these closures as
3692             performing the same work as an SQL C between data in different data
3693             sources.
3694              
3695             =item _cache_is_complete_for_class_and_normalized_rule
3696              
3697             ($is_cache_complete, $objects_listref) =
3698             $context->_cache_is_complete_for_class_and_normalized_rule(
3699             $class_name, $boolexpr
3700             );
3701              
3702             This method is part of the object loading process, and is called by
3703             L to determine if the objects requested
3704             by the L C<$boolexpr> will be found entirely in the object
3705             cache. If the answer is yes then C<$is_cache_complete> will be true.
3706             C<$objects_listef> may or may not contain objects matching the rule from
3707             the cache. If that list is not returned, then
3708             L does additional work to locate the
3709             matching objects itself via L
3710              
3711             It does its magic by looking at the C<$boolexpr> and loosely matching it
3712             against the query cache C<$UR::Context::all_params_loaded>
3713              
3714             =item _get_objects_for_class_and_rule_from_cache
3715              
3716             @objects = $context->_get_objects_for_class_and_rule_from_cache(
3717             $class_name, $boolexpr
3718             );
3719              
3720             This method is called by L when
3721             L<_cache_is_complete_for_class_and_normalized_rule> says the requested
3722             objects do exist in the cache, but did not return those items directly.
3723              
3724             The L C<$boolexpr> contains hints about how the matching data
3725             is likely to be found. Its C<_context_query_strategy> key will contain
3726             one of three values
3727              
3728             =over 2
3729              
3730             =item 1. all
3731              
3732             This rule is against a class with no filters, meaning it should return every
3733             member of that class. It calls C<$class-Eall_objects_loaded> to extract
3734             all objects of that class in the object cache.
3735              
3736             =item 2. id
3737              
3738             This rule is against a class and filters by only a single ID, or a list of
3739             IDs. The request is fulfilled by plucking the matching objects right out
3740             of the object cache.
3741              
3742             =item 3. index
3743              
3744             This rule is against one more more non-id properties. An index is built
3745             mapping the filtered properties and their values, and the cached objects
3746             which have those values. The request is fulfilled by using the index to
3747             find objects matching the filter.
3748              
3749             =item 4. set intersection
3750              
3751             This is a group-by rule and will return a ::Set object.
3752              
3753             =back
3754              
3755             =item _loading_was_done_before_with_a_superset_of_this_params_hashref
3756              
3757             $bool = $context->_loading_was_done_before_with_a_superset_of_this_params_hashref(
3758             $class_name,
3759             $params_hashref
3760             );
3761              
3762             This method is used by L
3763             to determine if the requested data was asked for previously, either from a
3764             get() asking for a superset of the current request, or from a request on
3765             a parent class of the current request.
3766              
3767             For example, if a get() is done on a class with one param:
3768              
3769             @objs = ParentClass->get(param_1 => 'foo');
3770              
3771             And then later, another request is done with an additional param:
3772              
3773             @objs2 = ParentClass->get(param_1 => 'foo', param_2 => 'bar');
3774              
3775             Then the first request must have returned all the data that could have
3776             possibly satisfied the second request, and so the system will not issue
3777             a query against the data source.
3778              
3779             As another example, given those two previously done queries, if another
3780             get() is done on a class that inherits from ParentClass
3781              
3782             @objs3 = ChildClass->get(param_1 => 'foo');
3783              
3784             again, the first request has already loaded all the relevant data, and
3785             therefore won't query the data source.
3786              
3787             =item _sync_databases
3788              
3789             $bool = $context->_sync_databases();
3790              
3791             Starts the process of committing all the Context's changes to the external
3792             data sources. _sync_databases() is the workhorse behind L.
3793              
3794             First, it finds all objects with changes. Checks those changed objects
3795             for validity with C<$obj-Einvalid>. If any objects are found invalid,
3796             then _sync_databases() will fail. Finally, it bins all the changed objects
3797             by data source, and asks each data source to save those objects' changes.
3798             It returns true if all the data sources were able to save the changes,
3799             false otherwise.
3800              
3801             =item _reverse_all_changes
3802              
3803             $bool = $context->_reverse_all_changes();
3804              
3805             _reverse_all_changes() is the workhorse behind L.
3806              
3807             For each class, it goes through each object of that class. If the object
3808             is a L, representing a deleted object, it converts the
3809             ghost back to the live version of the object. For other classes, it makes
3810             a list of properties that have changed since they were loaded (represented
3811             by the C hash key in the object), and reverts those changes
3812             by using each property's accessor method.
3813              
3814             =back
3815              
3816             =head1 The Object Cache
3817              
3818             The object cache is integral to the way the Context works, and also the main
3819             difference between UR and other ORMs. Other systems do no caching and
3820             require the calling application to hold references to any objects it
3821             is interested in. Say one part of the app loads data from the database and
3822             gives up its references, then if another part of the app does the same or
3823             similar query, it will have to ask the database again.
3824              
3825             UR handles caching of classes, objects and queries to avoid asking the data
3826             sources for data it has loaded previously. The object cache is essentially
3827             a software transaction that sits above whatever database transaction is
3828             active. After objects are loaded, any changes, creations or deletions exist
3829             only in the object cache, and are not saved to the underlying data sources
3830             until the application explicitly requests a commit or rollback.
3831              
3832             Objects are returned to the application only after they are inserted into
3833             the object cache. This means that if disconnected parts of the application
3834             are returned objects with the same class and ID, they will have references
3835             to the same exact object reference, and changes made in one part will be
3836             visible to all other parts of the app. An unchanged object can be removed
3837             from the object cache by calling its C method.
3838              
3839             Since changes to the underlying data sources are effectively delayed, it is
3840             possible that the application's notion of the object's current state does
3841             not match the data stored in the data source. You can mitigate this by using
3842             the C class or object method to fetch the latest data if it's a
3843             problem. Another issue to be aware of is if multiple programs are likely
3844             to commit conflicting changes to the same data, then whichever applies its
3845             changes last will win; some kind of external locking needs to be applied.
3846             Finally, if two programs attempt to insert data with the same ID columns
3847             into an RDBMS table, the second application's commit will fail, since that
3848             will likely violate a constraint.
3849              
3850             =head2 Object Change Tracking
3851              
3852             As objects are loaded from their data sources, their properties are
3853             initialized with the data from the query, and a copy of the same data is
3854             stored in the object in its C hash key. Anyone can ask the
3855             object for a list of its changes by calling C<$obj-Echanged>.
3856             Internally, changed() goes through all the object's properties, comparing
3857             the current values in the object's hash with the same keys under
3858             'db_committed'.
3859              
3860             Objects created through the C class method have no 'db_committed',
3861             and so the object knows it it a newly created object in this context.
3862              
3863             Every time an object is retrieved with get() or through an iterator, it is
3864             assigned a serial number in its C<__get_serial> hash key from the
3865             C<$UR::Context::GET_SERIAL> counter. This number is unique and increases
3866             with each get(), and is used by the L to expire the
3867             least recently requested data.
3868              
3869             Objects also track what parameters have been used to get() them in the hash
3870             C<$obj-E{__load}>. This is a copy of the data in
3871             C<$UR::Context::all_params_loaded-E{$template_id}>. For each rule
3872             ID, it will have a count of the number of times that rule was used in a get().
3873              
3874             =head2 Deleted Objects and Ghosts
3875              
3876             Calling delete() on an object is tracked in a different way. First, a new
3877             object is created, called a ghost. Ghost classes exist for every
3878             class in the application and are subclasses of L. For
3879             example, the ghost class for MyClass is MyClass::Ghost. This ghost object
3880             is initialized with the data from the original object. The original object
3881             is removed from the object cache, and is reblessed into the UR::DeletedRef
3882             class. Any attempt to interact with the object further will raise an
3883             exception.
3884              
3885             Ghost objects are not included in a get() request on the regular class,
3886             though the app can ask for them specifically using
3887             Cget(%params)>.
3888              
3889             Ghost classes do not have ghost classes themselves. Calling create() or
3890             delete() on a Ghost class or object will raise an exception. Calling other
3891             methods on the Ghost object that exist on the original, live class will
3892             delegate over to the live class's method.
3893              
3894             =head2 all_objects_are_loaded
3895              
3896             C<$UR::Context::all_objects_are_loaded> is a hashref keyed by class names.
3897             If the value is true, then L
3898             knows that all the instances of that class exist in the object cache, and
3899             it can avoid asking the underlying context/datasource for that class' data.
3900              
3901             =head2 all_params_loaded
3902              
3903             C<$UR::Context::all_params_loaded> is a two-level hashref. The first level
3904             is template (L) IDs. The second level is rule
3905             (L) IDs. The values are how many times that class and rule
3906             have been involved in a get(). This data is used by
3907             L
3908             to determine if the requested data will be found in the object cache for
3909             non-id queries.
3910              
3911             =head2 all_objects_loaded
3912              
3913             C<$UR::Context::all_objects_loaded> is a two-level hashref. The first level
3914             is class names. The second level is object IDs. Every time an object is
3915             created, defined or loaded from an underlying context, it is inserted into
3916             the C hash. For queries involving only ID properties,
3917             the Context can retrieve them directly out of the cache if they appear there.
3918              
3919             The entire cache can be purged of non-infrastructional objects by calling
3920             L.
3921              
3922             =head2 Object Cache Pruner
3923              
3924             The default Context behavior is to cache all objects it knows about for the
3925             entire life of the process. For programs that churn through large amounts
3926             of data, or live for a long time, this is probably not what you want.
3927              
3928             The Context has two settings to loosely control the size of the object
3929             cache. L and L.
3930             As objects are created and loaded, a count of uncachable objects is kept
3931             in C<$UR::Context::all_objects_cache_size>. The first part of
3932             L checks to see of the current size is
3933             greater than the highwater setting, and call L if so.
3934              
3935             prune_object_cache() works by looking at what C<$UR::Context::GET_SERIAL>
3936             was the last time it ran, and what it is now, and making a guess about
3937             what object serial number to use as a guide for removing objects by starting
3938             at 10% of the difference between the last serial and the current value,
3939             called the target serial.
3940              
3941              
3942             It then starts executing a loop as long as C<$UR::Context::all_objects_cache_size>
3943             is greater than the lowwater setting. For each uncachable object, if its
3944             C<__get_serial> is less than the target serial, it is weakened from any
3945             Les it may be a member of, and then weakened from the
3946             main object cache, C<$UR::Context::all_objects_loaded>.
3947              
3948             The application may lock an object in the cache by calling C<__strengthen__> on
3949             it, Likewise, the app may hint to the pruner to throw away an object as
3950             soon as possible by calling C<__weaken__>.
3951              
3952             =head1 SEE ALSO
3953              
3954             L, L, L,
3955             L, L, L
3956              
3957             =cut
3958