# -*- coding: iso-8859-1 -*-
#-----------------------------------------------------------------------------
# Modeling Framework: an Object-Relational Bridge for python
#
# Copyright (c) 2001-2004 Sbastien Bigaret <sbigaret@users.sourceforge.net>
# All rights reserved.
#
# This file is part of the Modeling Framework.
#
# This code is distributed under a "3-clause BSD"-style license;
# see the LICENSE file for details.
#-----------------------------------------------------------------------------
"""
AdaptorOperation API
Constants
The following constants (except DATABASE_OPERATORS and ADAPTOR_OPERATORS
which gather them in two separate lists) identify the operations
DatabaseOperation and AdaptorOperation instances should perform::
DATABASE_DELETE_OPERATOR
DATABASE_INSERT_OPERATOR
DATABASE_NOTHING_OPERATOR
DATABASE_UPDATE_OPERATOR
DATABASE_OPERATORS
ADAPTOR_DELETE_OPERATOR
ADAPTOR_INSERT_OPERATOR
ADAPTOR_LOCK_OPERATOR
ADAPTOR_STOREDPROCEDURE_OPERATOR
ADAPTOR_UPDATE_OPERATOR
ADAPTOR_OPERATORS
Why a DATABASE_NOTHING_OPERATOR?
Suppose you have two entities, 'Writer' and 'Book', with a one-to-many
relationship between them ; now suppose that we fetched a writer and its
books, and that the only modification we made was the removal of one of
these books from the relationship. When it is asked to save the changes,
the EditingContext already has marked both the writer and the removed book
as ``updated'' ; however, in a relational database a one-to-many
relationship only relies upon a single foreign key: in the database, table
'WRITER' has a foreign key storing the primary key of a Book --the
relationship itself is said to be stored asymetrically, since only one
foreign key in a single table holds the necessary information for a
relationship and its inverse relationship. Hence, even if both the
'Writer' instance and the removed book are marked as changed/updated by
the EditingContext, only the database-row corresponding to the removed
book has to be changed (the corresponding foreign key becomes 'NULL'). A
DatabaseContext detects that and will promote the DatabaseOperation's
databaseOperator() for the writer from DATABASE_UPDATE_OPERATOR to
DATABASE_NOTHING_OPERATOR.
NB: same for a Writer.toMany relationship->Book, with no inverse
relationship. This is the very reason why a DatabaseOperation needs to
store toMany snapshots: in this case, the removed book didn't get notified
on any changes (this is ok and definitely a normal behaviour since at the
object level nothing has changed) --> storing the toMany relationships
allows the DatabaseContext to notify the changes to the other side (see
ObjectStoreCoordinator.forwardUpdateForObject() and
DatabaseContext.recordUpdateForObject()).
CVS information
$Id: DatabaseOperation.py 932 2004-07-20 06:21:57Z sbigaret $
"""
__version__='$Revision: 932 $'[11:-2]
# Constants
DATABASE_DELETE_OPERATOR = 11
DATABASE_INSERT_OPERATOR = 12
DATABASE_NOTHING_OPERATOR = 17
DATABASE_UPDATE_OPERATOR = 15
DATABASE_OPERATORS=(DATABASE_DELETE_OPERATOR,
DATABASE_INSERT_OPERATOR,
DATABASE_NOTHING_OPERATOR,
DATABASE_UPDATE_OPERATOR)
ADAPTOR_LOCK_OPERATOR = 1
ADAPTOR_INSERT_OPERATOR = 2
ADAPTOR_UPDATE_OPERATOR = 3
ADAPTOR_DELETE_OPERATOR = 4
ADAPTOR_STORED_PROCEDURE_OPERATOR = 5 # unsupported yet
ADAPTOR_OPERATORS=(ADAPTOR_LOCK_OPERATOR,
ADAPTOR_INSERT_OPERATOR,
ADAPTOR_UPDATE_OPERATOR,
ADAPTOR_DELETE_OPERATOR,
#ADAPTOR_STORED_PROCEDURE_OPERATOR,
)
#
class DatabaseOperation:
"""
A DatabaseOperation holds all the informations needed to make changes of
an object persistent --this can be either the creation of that object, an
update of its values, or the deletion of that object.
These informations are:
- the object itself, its GlobalID and its Entity,
- two kinds of snapshots: one which represent the object as it was last
fetched or saved to the database, and one which represent its current
state.
DatabaseOperations are created by a DatabaseContext when it is instructed to
examine the changes in an EditingContext's graph of objects (see
DatabaseContext.recordChangesInEditingContext()). After this, and normally
under the control of an ObjectStoreCoordinator, the DatabaseContext builds
specific AdaptorOperations for each DatabaseOperations ; the
AdaptorOperations are then forwarded to the appropriate AdaptorChannel which
is responsible for passing them to the database.
"""
def __init__(self, aGlobalID, anObject, anEntity):
"""
Initializer for a DatabaseOperation.
Raises ValueError if at least one of the arguments are None.
"""
if not (aGlobalID and anObject and anEntity):
raise ValueError, 'None of the three arguments should be None'
self.__gID=aGlobalID
self.__object=anObject
self.__entity=anEntity
self.__adaptorOperations=[]
self.__databaseOperator=None
self.__dbSnapshot={}
self.__newRow={}
self.__toManySnaphots={} # relationship's name -> (globalIDs,)
def adaptorOperations(self):
"""
Returns the set of AdaptorOperations bound to this DatabaseOperation.
This set is built by a DatabaseContext.
See also: addAdaptorOperation(), DatabaseContext.performChanges()
removeAdaptorOperation()
"""
return self.__adaptorOperations
def addAdaptorOperation(self, anAdaptorOperation):
"""
Binds a new AdaptorOperation to the DatabaseOperation().
See also: adaptorOperations(), DatabaseContext.performChanges(),
removeAdaptorOperation()
"""
self.__adaptorOperations.append(anAdaptorOperation)
def databaseOperator(self):
"""
Returns the database operator.
See also: DATABASE_OPERATORS, setDatabaseOperator()
"""
return self.__databaseOperator
def dbSnapshot(self):
"""
Returns the snapshot that represents the object as it was last fetched
from/saved to the database. DatabaseOperations whose operator is
DATABASE_INSERT_OPERATOR have obviously no such snapshot.
See also: setDBSnapshot() for a complete description of what a snapshot is.
"""
return self.__dbSnapshot
def entity(self):
"""
Returns the 'Entity' of the 'object()' this DatabaseOperation handles.
"""
return self.__entity
def globalID(self):
"""
Returns the 'GlobalID' of the 'object()' this DatabaseOperation handles.
"""
return self.__gID
def newRow(self):
"""
Returns a snapshot (dictionary) representing the object's current state
that should be made persistent.
See also: setNewRow() for a complete description of what a snapshot is.
"""
return self.__newRow
def object(self):
"""
Returns the object the DatabaseOperation is responsible for
"""
return self.__object
def primaryKeyDiffs(self):
"""
Returns a dictionary made of changes observed for the primary key(s)
of the object -- for a full description of this dictionary, see
rowDiffsForAttributes().
Returned value is always an empty dictionary if the DatabaseOperation's
operator() is not DATABASE_UPDATE_OPERATOR.
Implementation note:
This method is not used for the moment being. In fact, it is in direct
relation with the fact that the propagation of the primary key value is
not supported yet for to-one relationships ; however, this is the *only*
case where an object's primary key(s) can be changed.
"""
if self.operator() is not DATABASE_UPDATE_OPERATOR:
return {}
return self.rowDiffsForAttributes(self.__entity.primaryKeyAttributes())
def recordToManySnapshot(self, globalIDs, name):
"""
Registers the array of GlobalIDs corresponding to the objects that are in
relation with self.object(), for the supplied relationship.
Parameters:
globalIDs -- a sequence made of GlobalIDs, or '0' (integer 0) if the
relationship is still a fault.
name -- the name of the corresponding relationship
See also: recordToManySnapshots(), toManySnapshots()
"""
self.__toManySnaphots[name]=globalIDs
def recordToManySnapshots(self, toManySnapshots):
"""
Registers the toMany snapshots corresponding to the objects that are in
relation with self.object().
Parameter:
snapshots -- an dictionary whose keys are relationships' names, and
whose values are GlobalIDs sequence (a '0' (integer 0) value
represents a faulted relationship)
See also: recordToManySnapshot()
"""
if toManySnapshots is None: return
for name in toManySnapshots.keys():
self.recordToManySnapshot(toManySnapshots[name], name)
def removeAdaptorOperation(self, anAdaptorOperation):
"""
Removes an AdaptorOperation from the bound adaptorOperations()
Raises 'ValueError' in case 'anAdaptorOperation' is not in the object's
'adaptorOperations()'
Parameter:
anAdaptorOperation -- an AdaptorOperation object
See also: adaptorOperations(), addAdaptorOperation()
"""
self.__adaptorOperations.remove(anAdaptorOperation)
def rowDiffs(self):
"""
Returns the set of value that are different between the database napshot
and the newRow.
Returned value is a dictionary with attributes' names as keys, associated
with the new value.
"""
res={}
for key in self.__dbSnapshot.keys():
if self.__dbSnapshot[key]!=self.__newRow[key]:
res[key]=self.__newRow[key]
return res
def rowDiffsForAttributes(self, attributes):
"""
Same as rowDiffs(), but the returned dictionary is restricted to the
names of the supplied attributes.
Parameter:
attributes -- a sequence of Attribute object.
"""
res={}
for key in map(lambda attr: attr.name(), attributes):
if self.__dbSnapshot[key]!=self.__newRow[key]:
res[key]=self.__newRow[key]
return res
def setDatabaseOperator(self, aDatabaseOperator):
"""
Sets the DatabaseOperation's operator.
Parameter:
aDatabaseOperator -- one of the 'DATABASE_OPERATORS'
Raises 'ValueError' if 'aDatabaseOperator' is not within
DATABASE_OPERATORS, or if 'aDatabaseOperator' is
'DATABASE_INSERT_OPERATOR' (resp. 'DATABASE_DELETE_OPERATOR') and the
object's databae operator is already set to 'DATABASE_DELETE_OPERATOR'
(resp. 'DATABASE_INSERT_OPERATOR') [this is to make sure that the
re-insertion of a previously deleted object has been correctly handled
within the 'EditingContext', as it should be]
"""
if aDatabaseOperator not in DATABASE_OPERATORS:
raise ValueError, 'Invalid database operator'
if (aDatabaseOperator==DATABASE_INSERT_OPERATOR and \
self.__databaseOperator==DATABASE_DELETE_OPERATOR) or \
(aDatabaseOperator==DATABASE_DELETE_OPERATOR and \
self.__databaseOperator==DATABASE_INSERT_OPERATOR):
raise ValueError, 'Insert operation cannot be promoted to delete '\
'operations --and vice-versa'
self.__databaseOperator=aDatabaseOperator
def setDBSnapshot(self, dbSnapshot):
"""
Sets the DatabaseOperation's DB snaphost
Parameter:
dbSnapshot -- the snapshot of the object as it was when the object was
last fetched from/committed to the database.
See also: dbSnapshot()
"""
self.__dbSnapshot=dbSnapshot
def setNewRow(self, newRow):
"""
Sets the rows which reflects the state of the object about to be saved.
Parameter
'newRow' -- a dictionary whose keys are the attributes of the object's
entity (derived attribute shouldn't be included), along with
name of the entity's to-one relationships, and whose values are,
unsurprisingly, the corresponding values!
"""
self.__newRow=newRow
def toManySnapshots(self):
"""
See also: recordToManySnapshot()
"""
return self.__toManySnaphots
def __str__(self):
"""
"""
return "<DBOperation op: %s gid: %s newRow: %s dbSnapshot: %s at %s>"%(self.__databaseOperator, self.__gID, self.__newRow, self.__dbSnapshot, hex(id(self)))
def __repr__(self):
return self.__str__()
|