NAME

OME::Factory - database access class

Back to Top


SYNOPSIS

        use OME::Factory;
        my $factory = $session->Factory();
        my $project = $factory->loadObject("OME::Project",1);
        my $dataset = $factory->newObject("OME::Dataset",
                                          {
                                           name  => "New dataset",
                                           owner => $user
                                          });
        my @images = $factory->findObjects("OME::Image",
                                           name => "Image 4");
        my @Pixels = $factory->findObjects('@Pixels', PixelType => 'uint16' );

Back to Top


DESCRIPTION

The OME::Factory class provides a single interface through which the rest of OME interacts with the database. The objects returned by this factory are (in most cases) instances of some descendant of OME::DBObject.

All of the methods which can take in DBObjects as parameters will work properly whether passed in an actual DBObject or an integer database ID. This includes search criteria in the findObject and findObjects methods, and the data hash used to create new objects in newObject and newAttribute.

OME implements some extensions to Class::DBI. Please see the OME::DBObject module for more details.

Back to Top


OBJECTS VS. ATTRIBUTES

Several of the OME::Factory methods make a distinction between "objects" and "attributes". In this convention, an "object" is defined by an OME::DBObject subclass included in the OME source tree. All of the core OME database tables (PROJECTS, DATASETS, IMAGES, etc.) are "objects", and have predefined OME::DBObject subclasses (OME::Project, OME::Dataset, OME::Image, etc.). Methods such as newObject and loadObject operate on these core tables, and identify the specific OME::DBObject subclass by name.

Attribute tables, however, cannot have predefined OME::DBObject subclasses, since the semantic types available in OME can vary from time to time. However, OME stores enough information about each semantic type to construct OME::DBObject subclasses at runtime. (The real situation is slightly more complex than this because of the distinction between data tables and semantic types. See the OME::DataTable and OME::SemanticType modules for more details.) Methods such as newAttribute and loadAttribute operate on these user-defined semantic types, and identify the specific OME::DBObject subclass by the semantic type. Methods such as findObject and loadObject may also be used for SemanticTypes if $className is a Semantic Type name prefixed by '@' ('@Pixels') or an instance of OME::SemanticType. This strategy works for all Object methods except newObject.

Back to Top


OBTAINING A FACTORY

To retrieve an OME::Factory to use for accessing the database, the user must log in to OME. This is done via the OME::SessionManager class. Logging in via OME::SessionManager yields an OME::Session object. Each OME::Session object has an associated OME::Factory, which can be retrieved with the Factory method. The full process is summarized below:

        my $manager = OME::SessionManager->new();
        my $session = $manager->createSession($username,$password);
        my $factory = $session->Factory();

Back to Top


Database Handles and Transactions

Factory maintains its own database handle (DBH) to ensure that all DBObjects it generates are from the same transaction.

Back to Top


METHODS

obtainDBH

        my $dbh = $factory->obtainDBH();

This method returns the DBI database handle associated with this OME::Factory. You can use it to run arbitrary SQL commands within the Factory's own transaction.

newObject

        my $object = $factory->newObject($className,$dataHash);

Creates a new object with initial values specified by $dataHash. The keys of $dataHash should be columns in the corresponding database table. (By convention, foreign key fields should be referred to without any "_id" suffix if they are being specified by reference; with the suffix if they are being specified by ID number.) The values of $dataHash should be the initial values for the respective columns. The $dataHash should not contain a value for the primary key if the underlying table has a corresponding sequence; Class::DBI will fill in the primary key. Note that this method creates a row in the database corresponding to the new object, so any columns defined to be NOT NULL must be specified in $dataHash, or DBI will throw an error.

maybeNewObject

        my $object = $factory->maybeNewObject($className,$dataHash);

This works exactly like newObject, except that if an object in the database already exists with the given contents, it will be returned, and no new object will be created. This is extremely useful for adding items to a many-to-many map. For instance,

        # Add $image to $dataset
        my $map = $factory->
            maybeNewObject("OME::Image::DatasetMap",
                           {
                            dataset => $dataset,
                            image   => $image
                           });

newAttribute

        my $attribute = $factory->
            newAttribute($semanticType,$target,$module_execution,$dataHash);

Creates a new attribute object. Note that this is not technically a DBObject subclass, since attributes can (conceivably) live in multiple data tables. Each attribute is associated with one DBObject per data table is resides in. (For more information on this, see OME::SemanticType.

The target of the attribute (dataset, image, or feature) should not be specified in $dataHash. Rather, is should be passed in the $target parameter. The appropriate key will be added to the $dataHash depending on the granularity of the semantic type. Similarly, the module execution that this attribute should be associated with should be passed in the $module_execution parameter, not the $dataHash.

Since semantic type packages are created dynamically, semantic types are not referred to by class name, like objects are. The $semanticType parameter should be either an instance of OME::SemanticType (which is an OME::DBObject, and can be obtained via any of the *Object methods), or the name of an semantic type. Note that:

        my $attribute = $factory->
            newAttribute("Stack mean",$image,$hash);

is exactly equivalent to:

        my $type = $factory->
            findObject("OME::SemanticType",
                       name => "Stack mean");
        my $attribute = $factory->
            newAttribute($type,$image,$hash);

newAttributes

        my $attributes = $factory->
            newAttributes($target,$module_execution,
                          $semanticType1,$dataHash1,
                          $semanticType2,$dataHash2,
                          $semanticType3,$dataHash3,
                          ...);

Creates several new attribute objects. This method differs from newAttribute in that it creates several attributes which are expected to live in a single set of data rows (one data row per data table). This method returns an array reference of the attribute objects that were created.

The target of the attribute (dataset, image, or feature) should not be specified in the $dataHashes. Rather, is should be passed in the $target parameter. The appropriate key will be added to the $dataHashes depending on the granularity of the semantic type. Similarly, the module execution that this attribute should be associated with should be passed in the $module_execution parameter, not the $dataHashes.

All of the semantic types given as input must have the same granularity. Further, since the attributes will be stored in a single set of data rows, any semantic elements which map to the same data column must have the same value in all of the data hashes. If any of these conditions aren't met, an error is thrown and no new attributes are created.

As in the case of newAttribute, each semantic type can be specified by name or as an instance of the OME::SemanticType manpage. If any types are specified by name, and a semantic type of that name does not exist, an error will be thrown and no new attributes will be created.

maybeNewAttribute

        my $attribute = $factory->
            maybeNewAttribute($semanticType,$target,$module_execution,$dataHash);

This works exactly like newObject, except that if an object in the database already exists with the given contents, it will be returned, and no new object will be created.

loadObject

        my $object = $factory->loadObject($className,$id);

Returns a DBObject instance corresponding to the row in $className's table with $id for its primary key. Returns undef if there is no row with that primary key.

loadAttribute

        my $attribute = $factory->loadAttribute($semanticType,$id);

Loads in the attribute with the specified primary key. As in the case of newAttribute, $semanticType can be either an semantic type name or an instance of OME::SemanticType. Since all of the data rows that make up an attribute are required to have the same primary key value, this method works by calling loadObject on all of the data table classes that make up the given semantic type, and then creating a new semantic type instance with those data rows.

objectExists

        my $boolean = $factory->objectExists($className,%criteria);

Returns true if there is at least one row in $className's database table which matches the given search criteria.

findObject

        my $object = $factory->findObject($className,%criteria);

Returns the object in $className's table which matches the search criteria. Returns undef if no object matches. If more than one object matches, one of them will be returned; it is undefined which one it will be.

findObjects

        my $iterator = $factory->findObjects($className,%criteria);
        while (my $object = $iterator->next()) {
            # Do something with the objects one at a time
        }
        my @objects = $factory->findObjects($className,%criteria);
        # Do something with the objects all at once

In list context, returns all of the objects in $className's table matching the search criteria. In scalar context, returns an iterator whose next() method will return those objects one at a time. This iterator is provided by the Class::DBI module.

objectExistsLike

        my $object = $factory->objectExistsLike($className,%criteria);

Works exactly like the objectExists method, but uses the SQL LIKE operator for comparison, rather than the = operator.

findObjectLike

        my $object = $factory->findObjectLike($className,%criteria);

Works exactly like the findObject method, but uses the SQL LIKE operator for comparison, rather than the = operator.

findObjectsLike

        my $iterator = $factory->findObjectsLike($className,%criteria);
        while (my $object = $iterator->next()) {
            # Do something with the objects one at a time
        }
        my @objects = $factory->findObjectsLike($className,%criteria);
        # Do something with the objects all at once

Works exactly like the findObjects method, but uses the SQL LIKE operator for comparison, rather than the = operator.

findAttributes

        my $iterator = $factory->findAttributes($semanticType,$target);
        while (my $attribute = $iterator->next()) {
            # Do something with the attributes one at a time
        }
        my @attributes = $factory->findAttributes($semanticType,$target);
        # Do something with the attributes all at once

Finds the attributes of a given type referring to a given target. As in the case of newAttribute, $semanticType can be either an semantic type name or an instance of OME::SemanticType. The target must be an OME::Dataset, OME::Image, or OME::Feature object, depending on the granularity of the type.

Back to Top


SEARCH CRITERIA

The objectExists, findObject, findObjects, findObjectLike, and findObjectsLike methods all take in search criteria as their last parameters. These criteria are used to build the WHERE clause of the SQL statement used to retrieve the objects in question. You can think of these criteria as similar to the data hash used to create objects: The keys should be column names (without the "_id" suffix for foreign keys), the values should be the search criteria values. When calling the methods, these criteria can be passed in directly in the parameter list, or as a hash reference. For instance:

        my @programs = $factory->
            findObjects("OME::Modules",
                        module_type => "OME::ModuleExecution::CLIHandler",
                        category    => "Statistics");

Also note that these methods are not intended to support arbitrarily complex SQL; that's what SQL is for. As such, all of the criteria will be ANDed together in the WHERE clause.

A wildcard criteria will search through all unspecified search fields. For example,

        my @images = $factory->
            findObjects("OME::Image",
                        *          => "foo",
                        created    => ['like', "2006-01-09%" ],
                        owner.FirstName => 'Josiah');

will find all images that were imported on January 9, 2006 by someone named Josiah that include "foo" somewhere in their name, description, or any image field besides 'created'.

A couple of special syntaxes are supported for the value in a search clause:

        dataset.name => 'Foo'

inserts a JOIN clause into the query. This allows you to follow references in the objects in your search criteria. Inferred references are supported as well as declared has-one and has-many relationships. Other examples:

        my @images = $factory->findObjects( 'OME::Image', {
                'dataset_links.dataset.name' => 'Worms',
        });

will find all images that belong to a dataset named 'Worms'.

        my @images = $factory->findObjects( 'OME::Image', {
                'ClassificationList.Category.Name' => 'Day 1',
        });

will find all images that have a Classification ST who's Category Name is 'Day 1'. The ClassificationList is an inferred has-many relationship between Classification and OME::Image. The Classification ST has a Category reference, so this is an explicit has-one relationship.

        my @CGs = $factory->findObjects( '@CategoryGroup', {
                        'CategoryList.ClassificationList.image.dataset_links.dataset_id' => $datasetID,
                        __distinct => 'id',
                });

will find all CategoryGroups used in a dataset with id $datasetID. Note the path necessary to get from an image attribute to a dataset. Without the __distinct in the criteria, we would get one CategoryGroup per image in this dataset.

        my @datasets = $factory->findObjects ('OME::Dataset', {
                        'image_links.image.ClassificationList.Category.CategoryGroup.Name' => 'Worm Age',
                        __distinct => 'id',
                });

conversely, this will find all datasets with images classified with any category in the 'Worm Age' category group. Somewhat unintuitively, without the __distinct criterium, one dataset will be returned for each image thus classified.

        my @classifications = $factory->findObjects( '@Classification', {
                        'image.dataset_links.dataset.name' => 'Worms',
                });

This will return all classifications for all images in the dataset named 'Worms'.

        __distinct => ['id','name']
        __distinct => 'id'
        
The distinct criterium will ensure that the specified fields of the base class are unique
in the set of objects returned.
        __order => ['id','name']

tells the Factory to order the results of the query. The contents of the array ref should be the columns to order by. Like search criteria, these columns can involve foreign key joins.

        __order => '!name'

By default, search returns with ascending order. Use a exclamation mark as a prefix to specify descending order.

        id => ['>',3]

tells the Factory to use a different SQL operator in this clause. This clause, for instance, would return all objects with a primary key ID greater than 3.

        id => ['in',[1,2,3]]

The in operator is another special case -- SQL expects a list in this case, so the value for the query clause should be an array reference of values. This clause would return objects with a primary key of 1, 2, or 3.

Note that these shortcuts mean that the findObjectsLike method is technically superfluous -- the exact same functionality can be achieved with findObjects. For instance,

        $factory->findObjectsLike("OME::Module",
                                  {
                                   module_type => 'OME::%',
                                   name        => '% (Matlab)'
                                  });

is exactly equivalent to

        $factory->findObjects("OME::Module",
                              {
                               module_type => ['like','OME::%'],
                               name        => ['like','% (Matlab)']
                              });

The *Like methods are provided as a convenience.

lockTable

        $factory->lockTable("OME::Analysis::Engine::Job");

Gets an exclusive lock on the DBObject's table. In Posgress there is no corresponding unlock table and the lock is released at the end of the transaction.

Back to Top