The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

OP::Persistence - Serialization mix-in

DESCRIPTION

Configurable class mix-in for storable OP objects.

Provides transparent support for various serialization methods.

SYNOPSIS

This package should not typically be used directly. Subclasses created with OP's create function will respond to these methods by default.

See __use<Feature>() and __dbiType(), under the Class Callback Methods section, for directions on how to disable or augment backing store options when subclassing.

PUBLIC CLASS METHODS

  • $class->load($id)

    Retrieve an object by ID from the backing store.

    This method will delegate to the appropriate private backend method. It returns the requested object, or throws an exception if the object was not found.

      my $object;
    
      try {
        $object = $class->load($id);
      } catch Error with {
        # ...
    
      };
  • $class->query($query)

    Runs the received query, and returns a statement handle which may be used to iterate through the query's result set.

    Reconnects to database, if necessary.

      sub($) {
        my $class = shift;
    
        my $query = sprintf(
          q| SELECT * FROM %s |,
          $class->tableName()
        );
    
        my $sth = $class->query($query);
    
        while ( my $object = $sth->fetchrow_hashref() ) {
          $class->__marshal($object);
    
          # Stuff ...
        }
    
        $sth->finish();
      }
  • $class->write($query)

    Runs the received query against the database, using the DBI do() method. Returns number of rows updated.

    Reconnects to database, if necessary.

      sub($$) {
        my $class = shift;
        my $value = shift;
    
        my $query = sprintf('update %s set foo = %s',
          $class->tableName(), $class->quote($value)
        )
    
        return $class->write($query);
      }
  • $class->selectBool($query)

    Returns the results of the received query, as a binary true or false value.

  • $class->selectSingle($query)

    Returns the first row of results from the received query, as a one-dimensional OP::Array.

      sub($$) {
        my $class = shift;
        my $name = shift;
    
        my $query = sprintf( q|
            SELECT mtime, ctime FROM %s WHERE name = %s
          |,
          $class->tableName(), $class->quote($name)
        );
    
        return $class->selectSingle($query);
      }
    
      #
      # Flat array of selected values, ie:
      #
      #   [ *userId, *ctime ]
      #
  • $class->selectMulti($query)

    Returns each row of results from the received query, as a one-dimensional OP::Array.

      my $query = "SELECT userId FROM session";
    
      #
      # Flat array of User IDs, ie:
      #
      #   [
      #     *userId,
      #     *userId,
      #     ...
      #   ]
      #
      my $userIds = $class->selectMulti($query);

    Returns a two-dimensional OP::Array of OP::Arrays, if * or multiple columns are specified in the query.

      my $query = "SELECT userId, mtime FROM session";
    
      #
      # Array of arrays, ie:
      #
      #   [
      #     [ *userId, *mtime ],
      #     [ *userId, *mtime ],
      #     ...
      #   ]
      #
      my $idsWithTime = $class->selectMulti($query);
  • $class->allIds()

    Returns an OP::Array of all object ids in the receiving class.

      my $ids = $class->allIds();
    
      #
      # for loop way
      #
      for my $id ( @{ $ids } ) {
        my $object = $class->load($id);
    
        # Stuff ...
      }
    
      #
      # collector way
      #
      $ids->each( sub {
        my $object = $class->load($_);
    
        # Stuff ...
      } );
  • $class->memberClass($attribute)

    Only applicable for attributes which were asserted as OP::ExtID().

    Returns the class of object referenced by the named attribute.

    Example A:

      #
      # In a class prototype, there was an ExtID assertion:
      #
      create "YourApp::Example" => {
        userId => OP::ExtID->assert( "YourApp::Example::User" ),
    
        # Stuff ...
      };
    
      #
      # Hypothetical object "Foo" has a "userId" attribute,
      # which specifies an ID in a user table:
      #
      my $exa = YourApp::Example->spawn("Foo");
    
      #
      # Retrieve the external object by ID:
      #
      my $userClass = YourApp::Example->memberClass("userId");
    
      my $user = $userClass->load($exa->userId());
  • $class->doesIdExist($id)

    Returns true if the received ID exists in the receiving class's table.

  • $class->doesNameExist($name)

    Returns true if the received ID exists in the receiving class's table.

      my $name = "Bob";
    
      my $object = $class->doesNameExist($name)
        ? $class->loadByName($name)
        : $class->new( name => $name, ... );
  • $class->loadByName($name)

    Loader for named objects. Works just as load().

      my $object = $class->loadByName($name);
  • $class->spawn($name)

    Loader for named objects. Works just as load(). If the object does not exist on backing store, a new object with the received name is returned.

      my $object = $class->spawn($name);
  • $class->idForName($name)

    Return the corresponding row id for the received object name.

      my $id = $class->idForName($name);
  • $class->nameForId($id)

    Return the corresponding name for the received object id.

      my $name = $class->nameForId($id);
  • $class->allNames()

    Returns a list of all object ids in the receiving class. Requires DBI.

      my $names = $class->allNames();
    
      $names->each( sub {
        my $object = $class->loadByName($_);
    
        # Stuff ...
      } );
  • $class->quote($value)

    Returns a DBI-quoted (escaped) version of the received value. Requires DBI.

      my $quoted = $class->quote($hairy);
  • $class->databaseName()

    Returns the name of the receiving class's database. Corresponds to the lower-cased first-level Perl namespace, unless overridden in subclass.

      do {
        #
        # Database name is "yourapp"
        #
        my $dbname = YourApp::Example->databaseName();
      };
  • $class->tableName()

    Returns the name of the receiving class's database table. Corresponds to the second-and-higher level Perl namespaces, using an underscore delimiter.

    Will probably want to override this, if subclass lives in a deeply nested namespace.

      do {
        #
        # Table name is "example"
        #
        my $table = YourApp::Example->tableName();
      };
    
      do {
        #
        # Table name is "example_job"
        #
        my $table = YourApp::Example::Job->tableName();
      };
  • $class->columnNames()

    Returns an OP::Array of all column names in the receiving class's table.

    This will be the same as the list returned by attributes(), minus any attributes which were asserted as Array or Hash and therefore live in a seperate linked table.

  • $class->elementClass($key);

    Returns the dynamic subclass used for an Array or Hash attribute.

    Instances of the element class represent rows in a linked table.

      #
      # File: Example.pm
      #
      create "YourApp::Example" => {
        testArray => OP::Array->assert( ... )
    
      };
    
      #
      # File: testcaller.pl
      #
      my $elementClass = YourApp::Example->elementClass("testArray");
    
      print "$elementClass\n"; # YourApp::Example::testArray

    In the above example, YourApp::Example::testArray is the name of the dynamic subclass which was allocated as a container for YourApp::Example's array elements.

  • $class->loadYaml($string)

    Load the received string containing YAML into an instance of the receiving class.

    Warns and returns undef if the load fails.

      my $string = q|---
      foo: alpha
      bar: bravo
      |;
    
      my $object = $class->loadYaml($string);
  • $class->loadJson($string)

    Load the received string containing JSON into an instance of the receiving class.

    Warns and returns undef if the load fails.

  • $class->restore($id, [$version]);

    Returns the object with the received ID from the RCS backing store. Does not call save, caller must do so explicitly.

    Uses the latest RCS revision if no version number is provided.

      do {
        my $self = $class->restore("LmBkxTee3hGGZgM418LRwQ==", "1.1");
    
        $self->save("Restoring from RCS archive");
      };

PRIVATE CLASS METHODS

The __use<Feature> methods listed below can be overridden simply by setting a class variable with the same name, as the examples illustrate.

  • $class->__useYaml()

    Return a true value to maintain a YAML backend for all saved objects.

    Default inherited value is auto-detected for the local system. Set class variable to override.

    YAML and DBI backing stores are not mutually exclusive. Classes may use either, both, or neither, depending on use case.

    Generally, one would use YAML if they are not also using DBI, and just wish to use a flatfile YAML backing store for all objects of a class:

      #
      # Flatfile backing store only-- no DBI:
      #
      create "YourApp::Example::NoDbi" => {
        __useYaml => true,
        __useDbi  => false,
      };

    The main reason to use both YAML and DBI would be to maintain RCS version archives for objects. When using YAML and DBI, OP writes out a YAML file upon save, but uses the database for everything but version restores:

      #
      # Maintain version history, but otherwise use DBI for everything:
      #
      create "YourApp::Example::DbiPlusRcs" => {
        __useYaml => true,
        __useRcs  => true,
        __useDbi  => true,
      };

    Use __yamlHost to enforce a master host for YAML/RCS archives.

  • $class->__useRcs()

    Returns a true value if the current class keeps revision history with its YAML backend, otherwise false.

    Default inherited value is false. Set class variable to override.

    __useRcs does nothing for classes which do not also use YAML. The RCS archive is derived from the physical files in the YAML backing store.

      create "YourApp::Example" => {
        __useYaml => true,
        __useRcs => true
    
      };
  • $class->__yamlHost()

    Optional; Returns the name of the RCS/YAML master host.

    If defined, the value for __yamlHost must *exactly* match the value returned by the local system's hostname command, including fully-qualified-ness, or any attempt to save will throw an exception. This feature should be used if you don't want files to accidentally be created on the wrong host/filesystem.

    Defaults to the yamlHost OP::Constants value (ships as undef), but may be overridden on a per-class basis.

      create "YourApp::Example" => {
        __useYaml  => true,
        __useRcs   => true,
        __yamlHost => "host023.example.com".
    
      };
  • $class->__useDbi()

    Returns a true value if the current class uses a SQL backing store, otherwise false.

    If __useDbi() returns true, the database type specified by __dbiType() will be used. See __dbiType() for how to override the class's database type.

    Default inherited value is auto-detected for the local system. Set class variable to override.

      create "YourApp::Example" => {
        __useDbi => false
      };
  • $class->__useForeignKeys()

    Returns a true value if the SQL schema should include foreign key constraints where applicable. Default is appropriate for the chosen DBI type.

  • $class->__useMemcached()

    Returns a TTL in seconds, if the current class should attempt to use memcached to minimize database load.

    Returns a false value if this class should bypass memcached.

    If the memcached server can't be reached at package load time, callers will load from the physical backing store.

    Default inherited value is 300 seconds (5 minutes). Set class variable to override.

      create "YourApp::CachedExample" => {
        #
        # Ten minute TTL on cached objects:
        #
        __useMemcached => 600
      };

    Set to false or 0 to disable caching.

      create "YourApp::NeverCachedExample" => {
        #
        # No caching in play:
        #
        __useMemcached => false
      };
  • $class->__dbiType()

    This method is unused if __useDbi returns false.

    Returns the constant of the DBI adapter to be used. Applies only if $class->__useDbi() returns a true value. Override in subclass to specify the desired backing store.

    Returns a constant from the OP::Enum::DBIType enumeration. See OP::Enum::DBIType for a list of supported constants.

    Default inherited value is auto-detected for the local system. Set class variable to override.

      create "YourApp::Example" => {
        __dbiType => OP::Enum::DBIType::SQLite
      };
  • $class->__baseAsserts()

    Assert base-level inherited assertions for objects. These include: id, name, mtime, ctime.

      __baseAsserts => sub($) {
        my $class = shift;
    
        my $base = $class->SUPER::__baseAsserts();
    
        $base->{parentId} = OP::Str->assert();
    
        return Clone::clone( $base );
      }
  • $class->__basePath()

    Return the base filesystem path used to store objects of the current class.

    By default, this method returns a directory named after the current class, under the directory specified by yamlRoot in the local .oprc file.

      my $base = $class->__basePath();
    
      for my $path ( <$base/*> ) {
        print $path;
        print "\n";
      }

    To override the base path in subclass if needed:

      __basePath => sub($) {
        my $class = shift;
    
        return join( '/', customPath, $class );
      }
  • $class->__baseRcsPath()

    Returns the base filesystem path used to store revision history files.

    By default, this just tucks "RCS" onto the end of __basePath().

  • $class->__primaryKey()

    Returns the name of the attribute representing this class's primary ID. Unless overridden, this method returns the string "id".

  • $class->__primaryKeyClass()

    Returns the object class used to represent this class's primary keys.

  • $class->__localLoad($id)

    Load the object with the received ID from the backing store.

      my $object = $class->__localLoad($id);
  • $class->__loadFromMemcached($id)

    Retrieves an object from memcached by ID. Returns nothing if the object wasn't there.

  • $class->__loadFromDatabase($id)

    Instantiates an object by id, from the database rather than the YAML backing store.

      my $object = $class->__loadFromDatabase($id);
  • $class->__marshal($hash)

    Loads any complex datatypes which were dumped to the database when saved, and blesses the received hash as an instance of the receiving class.

    Returns true on success, otherwise throws an exception.

      while ( my $object = $sth->fetchrow_hashref() ) {
        $class->__marshal($object);
      }
  • $class->__allIdsSth()

    Returns a statement handle to iterate over all ids. Requires DBI.

      my $sth = $class->__allIdsSth();
    
      while ( my ( $id ) = $sth->fetchrow_array() ) {
        my $object = $class->load($id);
    
        # Stuff ...
      }
    
      $sth->finish();
  • $class->__allNamesSth()

    Returns a statement handle to iterate over all names in class. Requires DBI.

      my $sth = $class->__allNamesSth();
    
      while ( my ( $name ) = $sth->fetchrow_array() ) {
        my $object = $class->loadByName($name);
    
        # Stuff ...
      }
    
      $sth->finish();
  • $class->__datetimeColumnType();

    Returns an override column type for ctime/mtime

  • $class->__beginTransaction();

    Begins a new SQL transation.

  • $class->__rollbackTransaction();

    Rolls back the current SQL transaction.

  • $class->__commitTransaction();

    Commits the current SQL transaction.

  • $class->__beginTransactionStatement();

    Returns the SQL used to begin a SQL transaction

  • $class->__commitTransactionStatement();

    Returns the SQL used to commit a SQL transaction

  • $class->__rollbackTransactionStatement();

    Returns the SQL used to rollback a SQL transaction

  • $class->__schema()

    Returns the SQL used to construct the receiving class's table.

  • $class->__concatNameStatement()

    Return the SQL used to look up name concatenated with the other attributes which it is uniquely keyed with.

  • $class->__statementForColumn($attr, $type, $foreign, $unique)

    Returns the chunk of SQL used for this attribute in the CREATE TABLE syntax.

  • $class->__cacheKey($id)

    Returns the key for storing and retrieving this record in Memcached.

      #
      # Remove a record from the cache:
      #
      my $key = $class->__cacheKey($object->get($class->__primaryKey()));
    
      $memd->delete($key);
  • $class->__dropTable()

    Drops the receiving class's database table.

      use YourApp::Example;
    
      YourApp::Example->__dropTable();
  • $class->__createTable()

    Creates the receiving class's database table

      use YourApp::Example;
    
      YourApp::Example->__createTable();
  • $class->__selectTableName()

    Returns a string representing the name of the class's current table.

    For DBs which support cross-database queries, this returns databaseName concatenated with tableName (eg. "yourdb.yourclass"), otherwise this method just returns the same value as tableName.

    Delegates to the class's vendor-specific override.

  • $class->__selectRowStatement($id)

    Returns the SQL used to select a record by id.

  • $class->__allNamesStatement()

    Returns the SQL used to generate a list of all record names

  • $class->__allIdsStatement()

    Returns the SQL used to generate a list of all record ids

  • $class->__loadFromQuery($query)

    Returns the first row of results from the received query, as an instance of the current class. Good for simple queries where you don't want to have to deal with while().

      sub($$) {
        my $class = shift;
        my $id = shift;
    
        my $query = $class->__selectRowStatement($id);
    
        my $object = $class->__loadFromQuery($query) || die $@;
    
        # Stuff ...
      }
  • $class->__dbh()

    Creates a new DB connection, or returns the one which is currently active.

      sub($$) {
        my $class = shift;
        my $query = shift;
    
        my $dbh = $class->__dbh();
    
        my $sth = $dbh->prepare($query);
    
        while ( my $hash = $sth->fetchrow_hashref() ) {
          # Stuff ...
        }
    
        $sth->finish();
      }
  • $class->__dbi()

    Returns the currently active GlobalDBI object, or undef if there isn't one.

  • $class->__doesIdExistStatement($id)

    Returns the SQL used to look up the presence of an ID in the current table

  • $class->__doesNameExistStatement($name)

    Returns the SQL used to look up the presence of a name in the current table

  • $class->__nameForIdStatement($id)

    Returns the SQL used to look up the name for a given ID

  • $class->__idForNameStatement($name)

    Returns the SQL used to look up the ID for a given name

  • $class->__serialType()

    Returns the database column type used for auto-incrementing IDs.

  • $class->__updateColumnNames();

    Returns an OP::Array of the column names to include with UPDATE statements.

  • $class->__selectColumnNames();

    Returns an OP::Array of the column names to include with SELECT statements.

  • $class->__insertColumnNames();

    Returns an OP::Array of the column names to include with INSERT statements.

  • $class->__quoteDatetimeInsert();

    Returns the SQL fragment used for unixtime->datetime conversion

  • $class->__quoteDatetimeSelect();

    Returns the SQL fragment used for datetime->unixtime conversion

  • $receiver->__dispatch($methodName, @args)

    Delegate the received class or instance method and arguments to the appropriate database-specific persistence module.

  • $class->__loadYamlFromId($id)

    Return the instance with the received id (returns new instance if the object doesn't exist on disk yet)

      my $object = $class->__loadYamlFromId($id);
  • $class->__loadYamlFromPath($path)

    Return an instance from the YAML at the received filesystem path

      my $object = $class->__loadYamlFromPath('/tmp/foobar.123');
  • $class->__getSourceByPath($path)

    Quickly return the file contents for the received path. Basically cat a file into memory.

      my $example = $class->__getSourceByPath("/etc/passwd");
  • $class->__fsIds()

    Return the id of all instances of this class on disk.

      my $ids = $class->__fsIds();
    
      $ids->each( sub {
        my $object = $class->load($_);
    
        # Stuff ...
      } );
  • $class->__checkYamlHost

    Throws an exception if the current host is not the correct place for YAML/RCS filesystem ops.

  • $class->__init()

    Override OP::Class->__init() to automatically create any missing tables in the database.

    Callers should invoke this at some point, if overriding in superclass.

PUBLIC INSTANCE METHODS

  • $self->save($comment)

    Saves self to all appropriate backing stores.

    Comment is ignored for classes not using RCS.

      $object->save("This is a checkin comment");
  • $self->presave();

    Abstract callback method invoked just prior to saving an object.

    Implement this method in subclass, if additional object sanity tests are needed.

  • $self->remove()

    Remove self's DB record, and unlink the YAML backing store if present.

    Does not delete RCS history, if present.

      $object->remove();
  • $self->revert([$version])

    Reverts self to the received version, which must be in the object's RCS file. This method is not usable unless __useRcs in the object's class is true.

    Uses the latest RCS revision if no version number is provided.

    Does not call save, caller must do this explicitly.

      do {
        $self->revert("1.20"); # Load previous version from RCS
    
        $self->save("Restoring from RCS archive");
      };
  • $self->exists()

    Returns true if this object has ever been saved.

      my $object = YourApp::Example->new();
    
      my $false = $object->exists();
    
      $object->save();
    
      my $true = $object->exists();
  • $self->key()

    Returns the value for this object's primary database key, since the primary key may not always be "id".

    Equivalent to:

      $self->get( $class->__primaryKey() );

    Which, in most cases, yields the same value as:

      $self->id();
  • $self->memberInstances($key)

    Only applicable for attributes which were asserted as ExtID().

    Returns an OP::Array of objects referenced by the named attribute.

    Equivalent to using load() on the class returned by class method memberClass(), for each ID in the relationship.

      my $exa = YourApp::Example->spawn("Foo");
    
      my $users = $exa->memberInstances("userId");
  • $self->setIdsFromNames($attr, @names)

    Convenience setter for attributes which contain either an ExtID or an Array of ExtIDs.

    Sets the value for the received attribute name in self to the IDs of the received names. If a name is provided which does not correspond to a named object in the foreign table, a new object is created and saved, and the new ID is used.

    If the named objects do not yet exist, and have required attributes other than "name", then this method will raise an exception. The referenced object will need to be explicitly saved before the referent.

      #
      # Set "parentId" in self to the ID of the object named "Mom":
      #
      $obj->setIdsFromNames("parentId", "Mom");
    
      #
      # Set "categoryIds" in self to the ID of the objects
      # named "Blue" and "Red":
      #
      $obj->setIdsFromNames("categoryIds", "Blue", "Red");
  • $self->revisions()

    Return an array of all of this object's revision numbers

  • $self->revisionInfo()

    Return a hash of info for this file's checkins

  • $self->head()

    Return the head revision number for self's backing store.

PRIVATE INSTANCE METHODS

  • $self->_newId()

    Generates a new ID for the current object. Default is GUID-style.

      _newId => sub {
        my $self = shift;
    
        return OP::Utility::newId();
      }
  • $self->_localSave($comment)

    Saves self to all applicable backing stores.

  • $self->_localSaveInsideTransaction($comment);

    Private backend method called by _localSave() when inside of a database transaction.

  • $self->_saveToMemcached

    Saves a copy of self to the memcached cluster

  • $self->_updateRowStatement()

    Returns the SQL used to run an "UPDATE" statement for the receiving object.

  • $self->_insertRowStatement()

    Returns the SQL used to run an "INSERT" statement for the receiving object.

  • $self->_deleteRowStatement()

    Returns the SQL used to run a "DELETE" statement for the receiving object.

  • $self->_updateRecord()

    Executes an INSERT or UPDATE for this object's record. Callback method invoked from _localSave().

    Returns number of rows on UPDATE, or ID of object created on INSERT.

  • $self->_quotedValues()

    Callback method used to construct the values portion of an UPDATE or INSERT query.

  • $self->_fsDelete()

    Unlink self's YAML datafile from the filesystem. Does not dereference self from memory.

  • $self->_fsSave()

    Save $self as YAML to a local filesystem. Path is $class->__basePath + $self->id();

  • $self->_path()

    Return the filesystem path to self's YAML data store.

  • $self->_saveToPath($path)

    Save $self as YAML to a the received filesystem path

  • $self->_rcsPath()

    Return the filesystem path to self's RCS history file.

  • $self->_checkout($rcs, $path)

    Performs the RCS co command on the received path, using the received Rcs object.

  • $self->_checkin($rcs, $path, [$comment])

    Performs the RCS ci command on the received path, using the received Rcs object.

  • $self->_rcs()

    Return an instance of the Rcs class corresponding to self's backing store.

SEE ALSO

Cache::Memcached::Fast, Rcs, YAML::Syck, GlobalDBI, DBI

This file is part of OP.