[Bio] / Sprout / ERDBObject.pm Repository:
ViewVC logotype

View of /Sprout/ERDBObject.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (download) (as text) (annotate)
Mon Jan 19 21:46:21 2009 UTC (10 years, 10 months ago) by parrello
Branch: MAIN
CVS Tags: rast_rel_2009_02_05, rast_rel_2009_03_26
Changes since 1.6: +185 -266 lines
ERDB 2.0 support

package ERDBObject;

    use strict;
    use DBKernel;
    use Tracer;

=head1 Entity-Relationship Database Package Instance Object

=head2 Introduction

This package defines the instance object for the Entity-Relationship Database
Package ([[ErdbPm]]. This object can be created directly, returned by the
C<Fetch> method of the B<ERDBQuery> object, or returned by the C<Cross> method
of this object. An object created directly is considered I<transient>. An object
created by one of the database methods is considered I<persistent>.

An instance object allows the user to access the fields in the current instance.
The instance consists of zero or more entity and/or relationship objects and a
map of field names to locations. Some entity fields require additional queries
to the database. If the entity object is present, the additional queries are
executed automatically. Otherwise, the value is treated as missing.

Each [[ERDBObjectPm]] has at least one object called the I<target object>. This
can be specified directly in the constructor or it can be computed implicity
from the query that created the object. This ojbect name is used as the
default when parsing field names.

=head2 Public Methods

=head3 new

    my $dbObject = ERDBObject->new($erdb, $objectName, \%fields);

Create a new transient object. A transient object maps fields to values, but is
not read from  a database. The parameter list should be an entity name
followed by a set of key-value pairs. Each key should be in the
[[ErdbPm#Standard_Field_Name_Format]].

=over 4

=item erdb

[[ErdbPm]] object for accessing the database. If undefined, the object will
be considered transient.

=item objectName

Default object name that should be used when resolving field name specifiers.
If undefined, no object will be considered the default.

=item fields

Reference to a hash mapping field names to field values. For a multi-valued
field, the value should be a list reference.

=back

=cut

sub new {
    # Get the parameters.
    my ($class, $erdb, $objectName, $fields) = @_;
    # Create the value hash.
    my %values;
    # Loop through the fields.
    for my $fieldName (keys %$fields) {
        # Normalize the field name.
        my $normalizedName = ERDB::ParseFieldName($fieldName, $objectName);
        # Get the field value.
        my $list = $fields->{$fieldName};
        # Convert it to a list. A single-valued field is stored as a singleton
        # list.
        if (ref $list ne 'ARRAY') {
            $list = [$list];
        }
        # Store the field.
        $values{$normalizedName} = $list;
    }
    # Create this object.
    my $retVal = {
                  _db => $erdb,
                  _targetObject => $objectName,
                  _values => \%values,
                  _aliases => {}
                 };
    # Bless and return it.
    bless $retVal, $class;
    return $retVal;
}

=head3 Attributes

    my @attrNames = $dbObject->Attributes();

This method will return a sorted list of the attributes present in this object.
The list can be used in the L</Values> method to get all the values stored.

If the ERDBObject was created by a database query, the attributes returned will
only be those which occur on the primary relation. Additional fields may get
loaded into the object if the client has asked for them in a L</Value> or
L</Values> command. Initially, however, only the primary fields-- each of which
has one and only one value-- will be found in the attribute list.

=cut
#: Return Type @;
sub Attributes {
    # Get the parameters.
    my ($self) = @_;
    # Get the keys of the value hash.
    my @retVal = sort keys %{$self->{_values}};
    # Return the result.
    return @retVal;
}

=head3 HasField

    my $flag = $dbObject->HasField($fieldSpec);

Return TRUE if this object has the specified field available, else FALSE.
This method can be used to determine if a value is available without
requiring an additional database query.

=over 4

=item fieldSpec

A standard field specifier. See [[ErdbPm#Standard_Field_Name_Format]]. The
default table name is the object's target entity.

=item RETURN

Returns TRUE if there's a value for the field in this object, else FALSE.

=back

=cut

sub HasField {
    # Get the parameters.
    my ($self, $fieldName) = @_;
    # Parse the field name.
    my $normalizedName = ERDB::ParseFieldName($fieldName, $self->{_targetObject});
    # Get the field hash.
    my $fields = $self->{_values};
    # Return the result.
    return exists $fields->{$normalizedName};
}

=head3 AddValues

    $dbObject->AddValues($name, @values);

Add one or more values to a specified field.

=over 4

=item name

Name of the field to receive the new values, in the [[ErdbPm#Standard_Field_Name_Format]].
If the field does not exist, it will be created.

=item values

List of values to put in the field.

=back

=cut

sub AddValues {
    # Get the parameters.
    my ($self, $name, @values) = @_;
    # Parse the value name.
    my $normalizedName = ERDB::ParseFieldName($name, $self->{_targetObject});
    # Get the field hash.
    my $fields = $self->{_values};
    # Add the new values.
    push @{$fields->{$name}}, @values;
}

=head3 PrimaryValue

    my $value = $dbObject->PrimaryValue($name);

Return the primary value of a field. This will be its first value in a standard
value list.

This method is a more convenient version of L</Value>. Basically, the call

    my ($value) = $dbObject->Value($name);

is equivalent to

    my $value = $dbObject->PrimaryValue($name);

but the latter is syntactically more convenient.

=over 4

=item name

Name of the field whose value is desired, in the [[ErdbPm#Standard_Field_Name_Format]].

=item RETURN

Returns the value of the specified field, or C<undef> if the field has no value.

=back

=cut

sub PrimaryValue {
    # Get the parameters.
    my ($self, $name) = @_;
    # Get the value.
    my ($retVal) = $self->Value($name);
    # Return it.
    return $retVal;
}

=head3 Value

    my @values = $dbObject->Value($attributeName);

Return a list of the values for the specified attribute.

=over 4

=item attributeName

Name of the desired attribute, in the [[ErdbPm#Standard_Field_Name_Format]].

=item RETURN

Returns a list of the values for the specified attribute.

=back

=cut

sub Value {
    # Get the parameters.
    my ($self, $attributeName) = @_;
    # Declare the return variable.
    my @retVal = ();
    # Normalize the field name.
    my ($alias, $fieldName) = ERDB::ParseFieldName($attributeName, $self->{_targetObject});
    my $normalizedName = "$alias($fieldName)";
    # Look for the field in the values hash.
    my $fieldHash = $self->{_values};
    my $retValRef = $fieldHash->{$normalizedName};
    Trace("retValRef for $attributeName is \"$retValRef\".") if T(Fields => 3);
    if (defined $retValRef) {
        # Here we have the field already, so return it.
        @retVal = @{$retValRef};
    } else {
        # Here the field is not in the hash. If we don't have a database, we are
        # done. The user will automatically get an empty list handed back to him.
        if (defined $self->{_db}) {
            # We have a database, so we can look for the value in a secondary
            # relation.
            my $erdb = $self->{_db};
            # Our first task is to compute the real entity name. This is the
            # same as the alias name unless it has an entry in the alias table.
            my $entityName = $self->{_aliases}->{$alias}->[0] || $alias;
            # Insure we have an ID for this entity.
            my $idName = "$alias(id)";
            if (! exists $fieldHash->{$idName}) {
                Confess("Cannot retrieve a field for \"$alias\": no ID present.");
            } else {
                # Get the field's data structure.
                my $fieldData = $erdb->_FindField($attributeName, $entityName);
                # Get the ID value. The field hash points to a list of IDs, but of
                # course, there is only one, so we just take the first.
                my $idValue = $fieldHash->{$idName}[0];
                # We need to encode the ID because we're using it as a query parameter.
                my $id = $erdb->EncodeField("$entityName(id)", $idValue);
                # Determine the name of the relation that contains this field.
                my $relationName = $fieldData->{relation};
                # Compute the actual name of the field in the database.
                my $fixedFieldName = ERDB::_FixName($fieldName);
                # Create the SELECT statement for the desired relation and execute it.
                my $command = "SELECT $fixedFieldName FROM $relationName WHERE id = ?";
                my $sth = $erdb->_GetStatementHandle($command, [$id]);
                # Loop through the query results creating a list of the values found.
                my $rows = $sth->fetchall_arrayref;
                for my $row (@{$rows}) {
                    # Note we decode the value before stuffing it in the result list.
                    my $realValue = $erdb->DecodeField("$entityName($fieldName)", $row->[0]);
                    push @retVal, $realValue;
                }
            }
            # Put the list in the field hash for future use.
            $fieldHash->{$normalizedName} = \@retVal;
        }
    }
    # Return the field values found.
    return @retVal;
}

=head3 Values

    my @values = $dbObject->Values(\@attributeNames);

This method returns a list of all the values for a list of field specifiers.
Essentially, it calls the L</Value> method for each element in the parameter
list and returns a flattened list of all the results.

For example, let us say that C<$feature> contains a feature with two links and a
translation. The following call will put the feature links in C<$link1> and
C<$link2> and the translation in C<$translation>.

    my ($link1, $link2, $translation) = $feature->Values(['Feature(link)', 'Feature(translation)']);

=over 4

=item attributeNames

Reference to a list of attribute names, or a space-delimited string of attribute names.

=item RETURN

Returns a flattened list of all the results found for each specified field.

=back

=cut

sub Values {
    # Get the parameters.
    my ($self, $attributeNames) = @_;
    # Create the return list.
    my @retVal = ();
    # Create the attribute name list.
    my @attributes;
    if (ref $attributeNames eq 'ARRAY') {
        @attributes = @$attributeNames;
    } else {
        @attributes = split /\s+/, $attributeNames;
    }
    # Loop through the specifiers, pushing their values into the return list.
    for my $specifier (@attributes) {
        push @retVal, $self->Value($specifier);
    }
    # Return the resulting list.
    return @retVal;
}

=head2 Internal Methods

=head3 _new

    my $erdbObject = ERDBObject->_new($dbquery, @values);

Create a new instance object from a query result.

=over 4

=item dbquery

[[ERDBQueryPm]] object for the relevant query.

=item values

List of values returned by the query. These are used as the values for the new
object.

=back

=cut

use constant FROMTO => { 'from-link' => 'to-link', 'to-link' => 'from-link' };

sub _new {
    # Get the parameters.
    my ($class, $dbquery, @values) = @_;
    # Pull out the ERDB object and the relationship map from the query object.
    my $erdb = $dbquery->{_db};
    my $relationMap = $dbquery->{_objectNames};
    # Get the metadata.
    my $metadata = $erdb->{_metaData};
    # Create the field hash table.
    my %fieldHash = ();
    # Create the alias table.
    my %aliases = ();
    # Check for a search relevance field in the results.
    if ($dbquery->{_fullText}) {
        # Create the special search relevance field from the first element of
        # the row values. Note that the object name for this field is the
        # stored in the query object's _fullText property.
        my $relevanceName = "$dbquery->{_fullText}(search-relevance)";
        $fieldHash{$relevanceName} = [shift @values];
    }
    # We put the target object name in here. It's the alias name (position 0)
    # for the first object in the map (also position 0).
    my $targetObject = $relationMap->[0][0];
    # Loop through the object names, extracting each object's fields. We will
    # strip each field from the value array and add it to the hash table using
    # the field's standard-format name.
    for my $mappingTuple (@{$relationMap}) {
        # Get the real object name for this mapped name.
        my ($alias, $objectName, $converseFlag) = @{$mappingTuple};
        # Get the relation descriptor for this object.
        my $relationData = $erdb->FindRelation($objectName);
        # Loop through the field list.
        for my $field (@{$relationData->{Fields}}) {
            # Get the current value from the array.
            my $thisValue = shift @values;
            # Get the current field's name.
            my $fieldName = $field->{name};
            # If we're converse, swap FROM and TO.
            if ($converseFlag && exists FROMTO->{$fieldName}) {
                $fieldName = FROMTO->{$fieldName};
            }
            # Decode the value.
            my $realValue = $erdb->DecodeField("$objectName($fieldName)", $thisValue);
            my $fieldKey = "$alias($fieldName)";
            # Add the field's name and value to the hash table.
            $fieldHash{$fieldKey} = [$realValue];
            Trace("$fieldKey = '$thisValue'") if T(Fields => 3);
        }
    }
    # Create the result object.
    my $self = { _db => $erdb,
                _targetObject => $targetObject,
                _values => \%fieldHash,
                _aliases => \%aliases };
    # Bless and return it.
    bless $self, $class;
    return $self;
}

=head3 DB

    my $erdb = $dbObject->DB();

Return the database for this result object.

=cut

sub DB {
    # Get the parameters.
    my ($self) = @_;
    # Return the result.
    return $self->{_db};
}

1;

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3