Provided by: libur-perl_0.470+ds-2_all bug

NAME

       UR::Context - Manage the current state of the application

SYNOPSIS

         use AppNamespace;

         my $obj = AppNamespace::SomeClass->get(id => 1234);
         $obj->some_property('I am changed');

         UR::Context->get_current->rollback; # some_property reverts to its original value

         $obj->other_property('Now, I am changed');

         UR::Context->commit; # other_property now permanently has that value

DESCRIPTION

       The main application code will rarely interact with UR::Context objects directly, except
       for the "commit" and "rollback" methods.  It manages the mappings between an application's
       classes, object cache, and external data sources.

SUBCLASSES

       UR::Context is an abstract class.  When an application starts up, the system creates a
       handful of Contexts that logically exist within one another:

       1. UR::Context::Root - A context to represent all the data reachable in the application's
       namespace.  It connects the application to external data sources.
       2. UR::Context::Process - A context to represent the state of data within the currently
       running application.  It handles the transfer of data to and from the Root context,
       through the object cache, on behalf of the application code.
       3. UR::Context::Transaction - A context to represent an in-memory transaction as a diff of
       the object cache.  The Transaction keeps a list of changes to objects and is able to
       revert those changes with "rollback()", or apply them to the underlying context with
       "commit()".

CONSTRUCTOR

       begin
             my $trans = UR::Context::Transaction->begin();

           UR::Context::Transaction instances are created through "begin()".

       A UR::Context::Root and UR::Context::Process context will be created for you when the
       application initializes.  Additional instances of these classes are not usually
       instantiated.

METHODS

       Most of the methods below can be called as either a class or object method of UR::Context.
       If called as a class method, they will operate on the current context.

       get_current
             my $context = UR::Context::get_current();

           Returns the UR::Context instance of whatever is the most currently created Context.
           Can be called as a class or object method.

       query_underlying_context
             my $should_load = $context->query_underlying_context();
             $context->query_underlying_context(1);

           A property of the Context that sets the default value of the $should_load flag inside
           "get_objects_for_class_and_rule" as described below.  Initially, its value is undef,
           meaning that during a get(), the Context will query the underlying data sources only
           if this query has not been done before.  Setting this property to 0 will make the
           Context never query data sources, meaning that the only objects retrievable are those
           already in memory.  Setting the property to 1 means that every query will hit the data
           sources, even if the query has been done before.

       get_objects_for_class_and_rule
             @objs = $context->get_objects_for_class_and_rule(
                                   $class_name,
                                   $boolexpr,
                                   $should_load,
                                   $should_return_iterator
                               );

           This is the method that serves as the main entry point to the Context behind the
           "get()", and "is_loaded()" methods of UR::Object, and "reload()" method of
           UR::Context.

           $class_name and $boolexpr are required arguments, and specify the target class by name
           and the rule used to filter the objects the caller is interested in.

           $should_load is a flag indicating whether the Context should load objects satisfying
           the rule from external data sources.  A true value means it should always ask the
           relevant data sources, even if the Context believes the requested data is in the
           object cache,  A false but defined value means the Context should not ask the data
           sources for new data, but only return what is currently in the cache matching the
           rule.  The value "undef" means the Context should use the value of its
           query_underlying_context property.  If that is also undef, then it will use its own
           judgement about asking the data sources for new data, and will merge cached and
           external data as necessary to fulfill the request.

           $should_return_iterator is a flag indicating whether this method should return the
           objects directly as a list, or iterator function instead.  If true, it returns a
           subref that returns one object each time it is called, and undef after the last
           matching object:

             my $iter = $context->get_objects_for_class_and_rule(
                                      'MyClass',
                                      $rule,
                                      undef,
                                      1
                                  );
             my @objs;
             while (my $obj = $iter->());
                 push @objs, $obj;
             }

       has_changes
             my $bool = $context->has_changes();

           Returns true if any objects in the given Context's object cache (or the current
           Context if called as a class method) have any changes that haven't been saved to the
           underlying context.

       commit
             UR::Context->commit();

           Causes all objects with changes to save their changes back to the underlying context.
           If the current context is a UR::Context::Transaction, then the changes will be applied
           to whatever Context the transaction is a part of.  if the current context is a
           UR::Context::Process context, then "commit()" pushes the changes to the underlying
           UR::Context::Root context, meaning that those changes will be applied to the relevant
           data sources.

           In the usual case, where no transactions are in play and all data sources are RDBMS
           databases, calling "commit()" will cause the program to begin issuing SQL against the
           databases to update changed objects, insert rows for newly created objects, and delete
           rows from deleted objects as part of an SQL transaction.  If all the changes apply
           cleanly, it will do and SQL "commit", or "rollback" if not.

           commit() returns true if all the changes have been safely transferred to the
           underlying context, false if there were problems.

       rollback
             UR::Context->rollback();

           Causes all objects' changes for the current transaction to be reversed.  If the
           current context is a UR::Context::Transaction, then the transactional properties of
           those objects will be reverted to the values they had when the transaction started.
           Outside of a transaction, object properties will be reverted to their values when they
           were loaded from the underlying data source.  rollback() will also ask all the
           underlying databases to rollback.

       clear_cache
             UR::Context->clear_cache();

           Asks the current context to remove all non-infrastructional data from its object
           cache.  This method will fail and return false if any object has changes.

       resolve_data_source_for_object
             my $ds = $obj->resolve_data_source_for_object();

           For the given $obj object, return the UR::DataSource instance that object was loaded
           from or would be saved to.  If objects of that class do not have a data source, then
           it will return "undef".

       resolve_data_sources_for_class_meta_and_rule
             my @ds = $context->resolve_data_sources_for_class_meta_and_rule($class_obj, $boolexpr);

           For the given class metaobject and boolean expression (rule), return the list of data
           sources that will need to be queried in order to return the objects matching the rule.
           In most cases, only one data source will be returned.

       infer_property_value_from_rule
             my $value = $context->infer_property_value_from_rule($property_name, $boolexpr);

           For the given boolean expression (rule), and a property name not mentioned in the
           rule, but is a property of the class the rule is against, return the value that
           property must logically have.

           For example, if this object is the only TestClass object where "foo" is the value
           'bar', it can infer that the TestClass property "baz" must have the value 'blah' in
           the current context.

             my $obj = TestClass->create(id => 1, foo => 'bar', baz=> 'blah');
             my $rule = UR::BoolExpr->resolve('TestClass', foo => 'bar);
             my $val = $context->infer_property_value_from_rule('baz', $rule);
             # val now is 'blah'

       object_cache_size_highwater
             UR::Context->object_cache_size_highwater(5000);
             my $highwater = UR::Context->object_cache_size_highwater();

           Set or get the value for the Context's object cache pruning high water mark.  The
           object cache pruner will be run during the next "get()" if the cache contains more
           than this number of prunable objects.  See the "Object Cache Pruner" section below for
           more information.

       object_cache_size_lowwater
             UR::Context->object_cache_size_lowwater(5000);
             my $lowwater = UR::Context->object_cache_size_lowwater();

           Set or get the value for the Context's object cache pruning high water mark.  The
           object cache pruner will stop when the number of prunable objects falls below this
           number.

       prune_object_cache
             UR::Context->prune_object_cache();

           Manually run the object cache pruner.

       reload
             UR::Context->reload($object);
             UR::Context->reload('Some::Class', 'property_name', value);

           Ask the context to load an object's data from an underlying Context, even if the
           object is already cached.  With a single parameter, it will use that object's ID
           parameters as the basis for querying the data source.  "reload" will also accept a
           class name and list of key/value parameters the same as "get".

       _light_cache
             UR::Context->_light_cache(1);

           Turn on or off the light caching flag.  Light caching alters the behavior of the
           object cache in that all object references in the cache are made weak by
           Scalar::Util::weaken().  This means that the application code must keep hold of any
           object references it wants to keep alive.  Light caching defaults to being off, and
           must be explicitly turned on with this method.

Custom observer aspects

       UR::Context sends signals for observers watching for some non-standard aspects.

       precommit
         After "commit()" has been called, but before any changes are saved to the data sources.
         The only parameters to the Observer's callback are the Context object and the aspect
         ("precommit").

       commit
         After "commit()" has been called, and after an attempt has been made to save the changes
         to the data sources.  The parameters to the callback are the Context object, the aspect
         ("commit"), and a boolean value indicating whether the commit succeeded or not.

       prerollback
         After "rollback()" has been called, but before and object state is reverted.

       rollback
         After "rollback()" has been called, and after an attempt has been made to revert the
         state of all the loaded objects.  The parameters to the callback are the Context object,
         the aspect ("rollback"), and a boolean value indicating whether the rollback succeeded
         or not.

Data Concurrency

       Currently, the Context is optimistic about data concurrency, meaning that it does very
       little to prevent clobbering data in underlying Contexts during a commit() if other
       processes have changed an object's data after the Context has cached and object.  For
       example, a database has an object with ID 1 and a property with value 'bob'.  A program
       loads this object and changes the property to 'fred', but does not yet commit().
       Meanwhile, another program loads the same object, changes the value to 'joe' and does
       commit().  Finally the first program calls commit().  The final value in the database will
       be 'fred', and no exceptions will be raised.

       As part of the caching behavior, the Context keeps a record of what the object's state is
       as it's loaded from the underlying Context.  This is how the Context knows what object
       have been changed during "commit()".

       If an already cached object's data is reloaded as part of some other query, data
       consistency of each property will be checked.  If there are no conflicting changes, then
       any differences between the object's initial state and the current state in the underlying
       Context will be applied to the object's notion of what it thinks its initial state is.

       In some future release, UR may support additional data concurrency methods such as
       pessimistic concurrency: check that the current state of all changed (or even all cached)
       objects in the underlying Context matches the initial state before committing changes
       downstream.  Or allowing the object cache to operate in write-through mode for some or all
       classes.

Internal Methods

       There are many methods in UR::Context meant to be used internally, but are worth
       documenting for anyone interested in the inner workings of the Context code.

       _create_import_iterator_for_underlying_context
             $subref = $context->_create_import_iterator_for_underlying_context(
                                     $boolexpr, $data_source, $serial_number
                                 );
             $next_obj = $subref->();

           This method is part of the object loading process, and is called by
           "get_objects_for_class_and_rule" when it is determined that the requested data does
           not exist in the object cache, and data should be brought in from another, underlying
           Context.  Usually this means the data will be loaded from an external data source.

           $boolexpr is the UR::BoolExpr rule, usually from the application code.

           $data_source is the UR::DataSource that will be used to load data from.

           $serial_number is used by the object cache pruner.  Each object loaded through this
           iterator will have $serial_number in its "__get_serial" hashref key.

           It works by first getting an iterator for the data source (the $db_iterator).  It
           calls "_resolve_query_plan_for_ds_and_bxt" to find out how data is to be loaded and
           whether this request spans multiple data sources.  It calls
           "__create_object_fabricator_for_loading_template" to get a list of closures to
           transform the primary data source's data into UR objects, and
           "_create_secondary_loading_closures" (if necessary) to get more closures that can load
           and join data from the primary to the secondary data source(s).

           It returns a subref that works as an iterator, loading and returning objects one at a
           time from the underlying context into the current context.  It returns undef when
           there are no more objects to return.

           The returned iterator works by first asking the $db_iterator for the next row of data
           as a listref.  Asks the secondary data source joiners whether there is any matching
           data.  Calls the object fabricator closures to convert the data source data into UR
           objects.  If any of the object requires subclassing, then additional importing
           iterators are created to handle that.  Finally, the objects matching the rule are
           returned to the caller one at a time.

       _resolve_query_plan_for_ds_and_bxt
             my $query_plan = $context->_resolve_query_plan_for_ds_and_bxt(
                                               $data_source,
                                               $boolexpr_tmpl
                                           );
             my($query_plan, @addl_info) = $context->_resolve_query_plan_for_ds_and_bxt(
                                                            $data_source,
                                                            $boolexpr_tmpl
                                                        );

           When a request is made that will hit one or more data sources,
           "_resolve_query_plan_for_ds_and_bxt" is used to call a method of the same name on the
           data source.  It returns a hashref used by many other parts of the object loading
           system, and describes what data source to use, how to query that data source to get
           the objects, how to use the raw data returned by the data source to construct objects
           and how to resolve any delegated properties that are a part of the rule.

           $data_source is a UR::DataSource object ID.  $coolexpr_tmpl is a
           UR::BoolExpr::Template object.

           In the common case, the query will only use one data source, and this method returns
           that data directly.  But if the primary data source sets the
           "joins_across_data_sources" key on the data structure as may be the case when a rule
           involves a delegated property to a class that uses a different data source, then this
           methods returns an additional list of data.  For each additional data source needed to
           resolve the query, this list will have three items:

           1.
             The secondary data source ID

           2.
             A listref of delegated UR::Object::Property objects joining the primary data source
             to this secondary data source.

           3.
             A UR::BoolExpr::Template rule template applicable against the secondary data source

       _create_secondary_rule_from_primary
             my $new_rule = $context->_create_secondary_rule_from_primary(
                                          $primary_rule,
                                          $delegated_properties,
                                          $secondary_rule_tmpl
                                      );

           When resolving a request that requires multiple data sources, this method is used to
           construct a rule against applicable against the secondary data source.  $primary_rule
           is the UR::BoolExpr rule used in the original query.  $delegated_properties is a
           listref of UR::Object::Property objects as returned by
           "_resolve_query_plan_for_ds_and_bxt()" linking the primary to the secondary data
           source.  $secondary_rule_tmpl is the rule template, also as returned by
           "_resolve_query_plan_for_ds_and_bxt()".

       _create_secondary_loading_closures
             my($obj_importers, $joiners) = $context->_create_secondary_loading_closures(
                                                          $primary_rule_tmpl,
                                                          @addl_info);

           When reolving a request that spans multiple data sources, this method is used to
           construct two lists of subrefs to aid in the request.  $primary_rule_tmpl is the
           UR::BoolExpr::Template rule template made from the original rule.  @addl_info is the
           same list returned by "_resolve_query_plan_for_ds_and_bxt".  For each secondary data
           source, there will be one item in the two listrefs that are returned, and in the same
           order.

           $obj_importers is a listref of subrefs used as object importers.  They transform the
           raw data returned by the data sources into UR objects.

           $joiners is also a listref of subrefs.  These closures know how the properties link
           the primary data source data to the secondary data source.  They take the raw data
           from the primary data source, load the next row of data from the secondary data
           source, and returns the secondary data that successfully joins to the primary data.
           You can think of these closures as performing the same work as an SQL "join" between
           data in different data sources.

       _cache_is_complete_for_class_and_normalized_rule
             ($is_cache_complete, $objects_listref) =
                 $context->_cache_is_complete_for_class_and_normalized_rule(
                               $class_name, $boolexpr
                           );

           This method is part of the object loading process, and is called by
           "get_objects_for_class_and_rule" to determine if the objects requested by the
           UR::BoolExpr $boolexpr will be found entirely in the object cache.  If the answer is
           yes then $is_cache_complete will be true.  $objects_listef may or may not contain
           objects matching the rule from the cache.  If that list is not returned, then
           "get_objects_for_class_and_rule" does additional work to locate the matching objects
           itself via "_get_objects_for_class_and_rule_from_cache"

           It does its magic by looking at the $boolexpr and loosely matching it against the
           query cache $UR::Context::all_params_loaded

       _get_objects_for_class_and_rule_from_cache
             @objects = $context->_get_objects_for_class_and_rule_from_cache(
                                      $class_name, $boolexpr
                                  );

           This method is called by "get_objects_for_class_and_rule" when
           _cache_is_complete_for_class_and_normalized_rule says the requested objects do exist
           in the cache, but did not return those items directly.

           The UR::BoolExpr $boolexpr contains hints about how the matching data is likely to be
           found.  Its "_context_query_strategy" key will contain one of three values

           1. all
             This rule is against a class with no filters, meaning it should return every member
             of that class.  It calls "$class->all_objects_loaded" to extract all objects of that
             class in the object cache.

           2. id
             This rule is against a class and filters by only a single ID, or a list of IDs.  The
             request is fulfilled by plucking the matching objects right out of the object cache.

           3. index
             This rule is against one more more non-id properties.  An index is built mapping the
             filtered properties and their values, and the cached objects which have those
             values.  The request is fulfilled by using the index to find objects matching the
             filter.

           4. set intersection
             This is a group-by rule and will return a ::Set object.

       _loading_was_done_before_with_a_superset_of_this_params_hashref
             $bool = $context->_loading_was_done_before_with_a_superset_of_this_params_hashref(
                                   $class_name,
                                   $params_hashref
                               );

           This method is used by "_cache_is_complete_for_class_and_normalized_rule" to determine
           if the requested data was asked for previously, either from a get() asking for a
           superset of the current request, or from a request on a parent class of the current
           request.

           For example, if a get() is done on a class with one param:

             @objs = ParentClass->get(param_1 => 'foo');

           And then later, another request is done with an additional param:

             @objs2 = ParentClass->get(param_1 => 'foo', param_2 => 'bar');

           Then the first request must have returned all the data that could have possibly
           satisfied the second request, and so the system will not issue a query against the
           data source.

           As another example, given those two previously done queries, if another get() is done
           on a class that inherits from ParentClass

             @objs3 = ChildClass->get(param_1 => 'foo');

           again, the first request has already loaded all the relevant data, and therefore won't
           query the data source.

       _sync_databases
             $bool = $context->_sync_databases();

           Starts the process of committing all the Context's changes to the external data
           sources.  _sync_databases() is the workhorse behind "commit".

           First, it finds all objects with changes.  Checks those changed objects for validity
           with "$obj->invalid".  If any objects are found invalid, then _sync_databases() will
           fail.  Finally, it bins all the changed objects by data source, and asks each data
           source to save those objects' changes.  It returns true if all the data sources were
           able to save the changes, false otherwise.

       _reverse_all_changes
             $bool = $context->_reverse_all_changes();

           _reverse_all_changes() is the workhorse behind "rollback".

           For each class, it goes through each object of that class.  If the object is a
           UR::Object::Ghost, representing a deleted object, it converts the ghost back to the
           live version of the object.  For other classes, it makes a list of properties that
           have changed since they were loaded (represented by the "db_committed" hash key in the
           object), and reverts those changes by using each property's accessor method.

The Object Cache

       The object cache is integral to the way the Context works, and also the main difference
       between UR and other ORMs.  Other systems do no caching and require the calling
       application to hold references to any objects it is interested in.  Say one part of the
       app loads data from the database and gives up its references, then if another part of the
       app does the same or similar query, it will have to ask the database again.

       UR handles caching of classes, objects and queries to avoid asking the data sources for
       data it has loaded previously.  The object cache is essentially a software transaction
       that sits above whatever database transaction is active.  After objects are loaded, any
       changes, creations or deletions exist only in the object cache, and are not saved to the
       underlying data sources until the application explicitly requests a commit or rollback.

       Objects are returned to the application only after they are inserted into the object
       cache.  This means that if disconnected parts of the application are returned objects with
       the same class and ID, they will have references to the same exact object reference, and
       changes made in one part will be visible to all other parts of the app.  An unchanged
       object can be removed from the object cache by calling its "unload()" method.

       Since changes to the underlying data sources are effectively delayed, it is possible that
       the application's notion of the object's current state does not match the data stored in
       the data source.  You can mitigate this by using the "load()" class or object method to
       fetch the latest data if it's a problem.  Another issue to be aware of is if multiple
       programs are likely to commit conflicting changes to the same data, then whichever applies
       its changes last will win; some kind of external locking needs to be applied.  Finally, if
       two programs attempt to insert data with the same ID columns into an RDBMS table, the
       second application's commit will fail, since that will likely violate a constraint.

   Object Change Tracking
       As objects are loaded from their data sources, their properties are initialized with the
       data from the query, and a copy of the same data is stored in the object in its
       "db_committed" hash key.  Anyone can ask the object for a list of its changes by calling
       "$obj->changed".  Internally, changed() goes through all the object's properties,
       comparing the current values in the object's hash with the same keys under 'db_committed'.

       Objects created through the "create()" class method have no 'db_committed', and so the
       object knows it it a newly created object in this context.

       Every time an object is retrieved with get() or through an iterator, it is assigned a
       serial number in its "__get_serial" hash key from the $UR::Context::GET_SERIAL counter.
       This number is unique and increases with each get(), and is used by the "Object Cache
       Pruner" to expire the least recently requested data.

       Objects also track what parameters have been used to get() them in the hash
       "$obj->{__load}".  This is a copy of the data in
       "$UR::Context::all_params_loaded->{$template_id}".  For each rule ID, it will have a count
       of the number of times that rule was used in a get().

   Deleted Objects and Ghosts
       Calling delete() on an object is tracked in a different way.  First, a new object is
       created, called a ghost.  Ghost classes exist for every class in the application and are
       subclasses of UR::Object::Ghost.  For example, the ghost class for MyClass is
       MyClass::Ghost.  This ghost object is initialized with the data from the original object.
       The original object is removed from the object cache, and is reblessed into the
       UR::DeletedRef class.  Any attempt to interact with the object further will raise an
       exception.

       Ghost objects are not included in a get() request on the regular class, though the app can
       ask for them specifically using "MyClass::Ghost->get(%params)".

       Ghost classes do not have ghost classes themselves.  Calling create() or delete() on a
       Ghost class or object will raise an exception.  Calling other methods on the Ghost object
       that exist on the original, live class will delegate over to the live class's method.

   all_objects_are_loaded
       $UR::Context::all_objects_are_loaded is a hashref keyed by class names.  If the value is
       true, then "_cache_is_complete_for_class_and_normalized_rule" knows that all the instances
       of that class exist in the object cache, and it can avoid asking the underlying
       context/datasource for that class' data.

   all_params_loaded
       $UR::Context::all_params_loaded is a two-level hashref.  The first level is template
       (UR::BoolExpr::Template) IDs.  The second level is rule (UR::BoolExpr) IDs.  The values
       are how many times that class and rule have been involved in a get().  This data is used
       by "_loading_was_done_before_with_a_superset_of_this_params_hashref" to determine if the
       requested data will be found in the object cache for non-id queries.

   all_objects_loaded
       $UR::Context::all_objects_loaded is a two-level hashref.  The first level is class names.
       The second level is object IDs.  Every time an object is created, defined or loaded from
       an underlying context, it is inserted into the "all_objects_loaded" hash.  For queries
       involving only ID properties, the Context can retrieve them directly out of the cache if
       they appear there.

       The entire cache can be purged of non-infrastructional objects by calling "clear_cache".

   Object Cache Pruner
       The default Context behavior is to cache all objects it knows about for the entire life of
       the process.  For programs that churn through large amounts of data, or live for a long
       time, this is probably not what you want.

       The Context has two settings to loosely control the size of the object cache.
       "object_cache_size_highwater" and "object_cache_size_lowwater".  As objects are created
       and loaded, a count of uncachable objects is kept in $UR::Context::all_objects_cache_size.
       The first part of "get_objects_for_class_and_rule" checks to see of the current size is
       greater than the highwater setting, and call "prune_object_cache" if so.

       prune_object_cache() works by looking at what $UR::Context::GET_SERIAL was the last time
       it ran, and what it is now, and making a guess about what object serial number to use as a
       guide for removing objects by starting at 10% of the difference between the last serial
       and the current value, called the target serial.

       It then starts executing a loop as long as $UR::Context::all_objects_cache_size is greater
       than the lowwater setting.  For each uncachable object, if its "__get_serial" is less than
       the target serial, it is weakened from any UR::Object::Indexes it may be a member of, and
       then weakened from the main object cache, $UR::Context::all_objects_loaded.

       The application may lock an object in the cache by calling "__strengthen__" on it,
       Likewise, the app may hint to the pruner to throw away an object as soon as possible by
       calling "__weaken__".

SEE ALSO

       UR::Context::Root, UR::Context::Process, UR::Object, UR::DataSource, UR::Object::Ghost,
       UR::Observer