The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

NoSQL::PL2SQL::Object - Private Perl extension for NoSQL::PL2SQL

SYNOPSIS

The NoSQL::PL2SQL::Object package is private. None of its methods or functions are part of the public interface.

Except for the TIE constructors, NoSQL::PL2SQL::Object methods are only called indirectly through the overloaded TIE operators and during destruction.

  use NoSQL::PL2SQL::Object ;

  ## Constructors

  TIEHASH
  TIEARRAY
  TIESCALAR

  $blessedvalue = $tied->mybless( $value )

mybless() is part of the constructor process, called immediately after the TIE constructor.

  ## Overloading Operators
  ## $tied refers to the first argument to these operators.

  my $tied = tied %$value ;     ## $value is an object or element

  FETCH
  STORE
  STORESIZE
  EXTEND
  FETCHSIZE
  POP
  PUSH
  SHIFT
  UNSHIFT
  SPLICE
  DELETE
  CLEAR
  EXISTS
  FIRSTKEY
  NEXTKEY

  DESTROY

The following methods are used as part of initialization.

  my $value = $tied->data() ;
  my $value = $tied->data( $key ) ;
  my $value = $tied->data( $key, 0 ) ;
  my $tied = $tied->data( $key, 1 ) ;

  $tied->load() ;
  $tied->loadscalarref( $tied->refto || $tied->{top} ) ;
  $tied->memorymap() ;

An object that implements NoSQL::PL2SQL is represented as a tree of NoSQL::PL2SQL::Object nodes. Each node is tied, so an operation on any object element calls one of the overloading operators, which actually performs the operation on its Object node.

When an object is retrieved using NoSQL::PL2SQL->SQLObject(), data is fetched from the RDB, the top Object Node is created, tied, optionally blessed, and returned to the caller as a representation of the original object. After this creation, the representation is nothing more than an empty top node Object. (object refers to the instantiation of the caller application; Object refers to a NoSQL::PL2SQL::Object- thus, an Object is a node in a tree that represents an object.)

Creation and loading occur separately Loading occurs when an object element is accessed. If the element is a container, the child nodes are created. If the element is a scalar, the Object is loaded with data. data() is called whenever an element is accessed, which in turn calls load() if necessary. memorymap() is also called during load. The memorymap keeps track of nodes with multiple parents- elements that are shared internal references.

A scalar reference is normally implemented as a data node. However, if the reference is shared, subsequent nodes are created as containers. If the first loaded node is a container, the data node is also loaded using loadscalarref().

The following methods are used as part of data access.

  my ( $value, $tied ) = @{ item( $value ) } ;
  my ( $value, $tied ) = @{ scalarref( $value ) } ;

  $self = $tied->self() ;
  $refitem = $tied->refrecord() ;
  $refto = $tied->refto() ;
  $nvp = $tied->record() ;
  $nvp = $tied->record( $recnumber ) ;

After loading a node, the data() method may call the item() or scalarref() method. data() can return either an element, its tied Object node, or in the case of a scalarref, a deferenced value using the output of these methods.

Elements are accessed using the overloading function of its node's parent. When elements are shared using internal references, their nodes have multiple parents. For consistency, modifications should always be applied via the same parent. In the overloading functions, therefore, the parent node (passed by the caller) may be replace with another parent using the self() method. Since references can be chained, self() calls refrecord() recursively. In a scalar reference container, the data source is a node identified in the refto property. To handle chained references, scalar references use the <refto()> method instead.

Most of an Object's properties are maintained in the original data structure returned by the RDB. These properties can be accessed using the record() method which returns the whole set as a hash reference. Nodes are linked, and the properties of a child or sibling node can be accessed by passing the refto or item link value as an argument.

When an element is modified, a node is either modified, added, deleted, or added and deleted (replaced), using the following operations:

1. The scalar data property is modified
2. A scalar is added
3. An untied object is added
4. A node reference is added
5. The node is deleted
  ## modify internal data
  $tied->update( $sqlname => $sqlvalue, ... ) ;

  ## add a scalar, untied object, or reference
  $tiedarraycontainer->resequence() ;
  $item = $containeritem->newelement()

  ## add a reference
  $tied = $tied->setreference()
  @tied = $tied->getkids()

  ## delete a node
  my @idlist = $tied->linklist( $type ) ; ## $type is 'item' or 'string'
  my $tiedtop = $tied->topnode() ;
  my $tiedany = $tied->topnode( $recno ) ;
  $value = $tiedtop->sqlclone()
  $ct = $tied->refcount() ;

The update() method affects the low level record data. It also sets the update property to indicate that a change must be written back to the RDB.

newlement() is called every time a node is added. The new element may contain a scalar, untied object, or reference to another node. When a reference is added, setreference() is used to increment the reference count. All of the nodes underneath the added reference's node must also be incremented. getkids() is called recursively to pull the complete set.

If an array element is added or deleted, the other elements need to be resequenced by calling the resequence() method on the container, which in turn calls update() on all of the child nodes to replace the index value.

When an element is deleted, PL2SQL will attempt to delete the affected node records from the RDB by identifying all the descendents in a container node using NoSQL::PL2SQL::Perldata->descendants(); and all the linked nodes of a large scalar using the linklist() method. A reference count map is used so that referenced elements aren't destroyed. refcount() decrements the count and returns the result.

The referencecount map isn't accurate until all the nodes have been loaded. So CLEAR calls sqlcount(), which returns an untied copy of the element represented by the node $tied, and recursively loads all the descendant nodes. In order to load all nodes, sqlclone() must be called on the top node, using the topnode() method. Otherwise, topnode can fetch any node using a $recno argument.

Changes to the original object are never written to the RDB until the object is destroyed. When destroyed, the top Object node in the tree is destroyed along with its child nodes, until all the nodes have been destroyed. Each node is written as it is destroyed. The sequence is indeterminate, so the operation must be performed using only the node properties.

  my @nodes = $tied->updates() ;
  my $recno = $tied->lastitem() ;
  my $bool = $tied->equals() ;
  my @nvp = $tied->scalarok() ;

The DESTROY sequence is as follows:

1. Records belonging to deleted child nodes are deleted.
2. The node is converted to a NoSQL::PL2SQL::Node object via the updates() method. If the node's data is an untied object, or large scalar, the conversion may result in a set of Node objects.
3. NoSQL::PL2SQL::Node::combine() and NoSQL::PL2SQL::Node::insertall() methods are called on the node set.

When an object is created, NoSQL::PL2SQL::Node::insertall() keeps track of, and sets link values internally. When an object is updated, DESTROY must perform this housekeeping. The PL2SQL::Node::lastitem() method is used, for example, to identify the last node in a linked list.

updates() is responsible for generating Nodes that are eventually written into the RDB. When returned Nodes have no "id" property, the SQL engine NoSQL::PL2SQL::DBI will create new records to accomodate them. update() performs two tests to minimize overhead: equals() is used to see if the Object's data has been modified; scalarok() is used to see if a data node can be reused.

scalarok() returns varying output depending on the complexity of the change. If the value is completely unchanged, the result is an array containing a single undefined element. If the value is significantly changed, new NoSQL::PL2SQL::Node's need to be generated, and the method returns an empty array. If the value is slightly changed (eg a small scalar to another small scalar) the result is an nvp set that reflects the changed properties.

NoSQL::PL2SQL::Clone

NoSQL::PL2SQL::Clone is one of two helper classes that have been added in v1.2. The purpose of this class is to override the destructor so that the DESTROY method is called on what is otherwise a self-referencing object.

The Clone instance is always a reference to the top node. This destructor should always be called before any of the Object nodes are destroyed.

NoSQL::PL2SQL::Lock

NoSQL::PL2SQL::Lock is the other helper class. It's instantiated before the first node is destroyed and it's destruction method is called after the last Object node is destroyed.

The Lock instance is applied directly to that objects's record. Requests to create a second instance on that record, from any PL2SQL client, are blocked until the existing instance is destroyed.

NoSQL::PL2SQL is designed to make incremental updates: Only changed object elements are updated when the object is destroyed. But when a record is concurrently accessed, its state is indeterminate, and incremental updates cannot be successfully applied. In that case, the Lock object uses an incrementing header record to detect concurrent record access and performs a full update by writing all the nodes to the database instead.

EXPORT

None by default.

HISTORY

0.01

Original version; created by h2xs 1.23 with options

  -AXCO
        NoSQL::PL2SQL
0.02

Cleaned perldoc formatting issues

0.03

Fixed sqlclone()

0.04

Removed lastitem() method. PL2SQL::Perldata::lastitem() is now called during DESTROY()

0.05

Fixed: DESTROY() method sometimes loses global values to build sql properties of new nodes.

0.06

Fixed: DELETE() override throws error on a missing key

0.06

Added the package() method to Object, which functions the same as Perl's built-in ref function, except it must be explicitly overridden in subclasses.

Added the objectkey() method to Object as a convenience.

Added the header property to Object. The header references a data source table row that keeps track of locking activity.

Added the NoSQL::PL2SQL::Clone object. When a PL2SQL object is created, its reference is saved as a global property within the memorymap() operation. To avoid a destruction deadlock, the reference is saved as a blessed Clone object instead of an Object object. NoSQL::PL2SQL::Clone has the difficult job of cloning an object that's already been destroyed.

Added the NoSQL::PL2SQL::Lock object which blocks simultaneous record writes to the database. The Lock object also determines whether to perform a full or incremental update.

The sqlclone() method creates a cloned copy for each container element. When an element is an internal reference, sqlclone() needs to return the referenced clone. Some housekeeping code was added to implement this feature correctly. The changes includes a new method, innerclone(), which isolates the recursive operations.

0.08

FETCH() needs an exception for scalar references. Performed a little refactoring among data(), FETCH(), and DELETE() to isolate this requirement.

SEE ALSO

NoSQL::PL2SQL
NoSQL::PL2SQL::Node
http://pl2sql.tqis.com/

AUTHOR

Jim Schueler, <jim@tqis.com>

COPYRIGHT AND LICENSE

Copyright (C) 2012 by Jim Schueler

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.9 or, at your option, any later version of Perl 5 you may have available.