DatabaseContext.py :  » Database » Modeling-Framework » Modeling-0.9 » Modeling » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » Modeling Framework 
Modeling Framework » Modeling 0.9 » Modeling » DatabaseContext.py
# -*- 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.
#-----------------------------------------------------------------------------
from __future__ import nested_scopes

"""
DatabaseContext


  Updating/Locking strategies: __TBD

    - UPDATE_WITH_NO_LOCKING

    - UPDATE_WITH_OPTIMISTIC_LOCKING

    - UPDATE_WITH_PESSIMISTIC_LOCKING (unsupported yet)
    
  Snapshotting

    DatabaseContext uses its own local snapshotting mechanism only when it is
    asked to save changes ; when changes are made, the local snapshots are
    transmitted back to its Database. This means, in particular, that the
    local snapshots are *not* for use while fetching (local snapshots are
    anyway wiped out when the process of saving changes terminates)


  Notifications

    DatabaseContext as an observer

      The module registers 'handleNotification()' as an observer for the
      'ObjectStoreCoordinator.CooperatingObjectStoreNeeded'

    DatabaseChannelNeededNotification

      Posted by a DatabaseContext when it needs a DatabaseChannel.
      Listeners can register a new DatabaseChannel by calling
      the posting DatabaseContext's method registerChannel().

      See: DatabaseContext.availableChannel()
           DatabaseContext.registerChannel()

      Contents:

        - object:   the DatabaseContext which posted the notification

        - userInfo: None


  CVS information

    $Id: DatabaseContext.py 967 2006-02-25 13:26:05Z sbigaret $
  
"""

__version__='$Revision: 967 $'[11:-2]

from logging import info,error,trace,debug,warn
from NotificationFramework import NotificationCenter
NC=NotificationCenter

from mx.DateTime import DateFrom

import sys
#trace=lambda msg: sys.stderr.write(str(msg)+'\n')

# Framework
from Database import DistantPastTimeInterval,GeneralDatabaseException
from DatabaseChannel import DatabaseChannel
from EditingContext import EditingContext
from FaultHandler import AccessFaultHandler,AccessArrayFaultHandler
from GlobalID import KeyGlobalID,GlobalIDChangedNotification
from ClassDescription import ClassDescription
from FetchSpecification import FetchSpecification
from Qualifier import QualifierOperatorEqual,qualifierToMatchAllValues,KeyValueQualifier,AndQualifier
from DatabaseOperation import DatabaseOperation,\
     DATABASE_NOTHING_OPERATOR, DATABASE_INSERT_OPERATOR, \
     DATABASE_UPDATE_OPERATOR, DATABASE_DELETE_OPERATOR, \
     ADAPTOR_LOCK_OPERATOR, ADAPTOR_INSERT_OPERATOR, \
     ADAPTOR_UPDATE_OPERATOR, ADAPTOR_DELETE_OPERATOR 
from AdaptorOperation import AdaptorOperation
import SnapshotsHandling

# interfaces
from Modeling.CooperatingObjectStore import CooperatingObjectStore

# Notifications
DatabaseChannelNeededNotification='DatabaseChannelNeededNotification'

from ObjectStore import InvalidatedAllObjectsInStoreNotification
from ObjectStoreCoordinator import CooperatingObjectStoreNeededNotification

## Constants
UPDATE_WITH_NO_LOCKING=0
UPDATE_WITH_OPTIMISTIC_LOCKING=1
UPDATE_WITH_PESSIMISTIC_LOCKING=2
UPDATE_STRATEGIES=(UPDATE_WITH_NO_LOCKING,
                   UPDATE_WITH_OPTIMISTIC_LOCKING,
                   UPDATE_WITH_PESSIMISTIC_LOCKING)

## The following lock is used by all module's methods
## Remember that you should *NOT* directly access to module's variables
## since they are considered private (MT-safety)
from threading import RLock
DatabaseContextModule_lock=RLock()
lock=DatabaseContextModule_lock.acquire
unlock=DatabaseContextModule_lock.release

# module's variables: see at the end of module declaration

# Modules funcs/Class 'static' methods
def contextClassToRegister():
  """
  Returns the class of objects to be instanciated when an
  'ObjectStoreCoordinator' object posts a 'CooperatingObjectStoreNeeded'
  notification. Defaults to DatabaseContext.
  """
  lock()
  try:
    return __contextClassToRegister
  finally: unlock()
  
def defaultDelegate():
  """
  Returns the default delegate to be assigned to DatabaseContexts at
  instanciation-time. Not implemented yet (lacks an interface and specific
  treatments in class 'DatabaseContext'). Defaults to None.
  """
  lock()
  try:
    return __defaultDelegate
  finally: unlock()

def forceConnectionWithModel(aModel, overrides, anEditingContext):
  """
  Unimplemented yet
  """
  lock()
  try:
    raise 'Unimplemented', 'Unimplemented yet'
  finally: unlock()
  
def isSharedObjectLoadingEnabled():
  """
  Returns '0' (false): Shared Editing Context is not supported yet.
  """
  lock()
  try:
    return 0
  finally: unlock()

def registeredDatabaseContextForModel(aModel, anObjectStore):
  """
  Parameter 'anObjectStore' can be either an 'EditingContext' or a
  'ObjectStoreCoordinator'. Sends message 'rootObjectStore' to
  'anObjectStore' and returns the 'DatabaseContext' registered for that model.
  If none is found, a new DatabaseContext object is created, registered in
  the 'rootObjectStore', and returned.
  """
  lock()
  try:
    rootObjStore=anObjectStore.rootObjectStore()
    dbContexts=rootObjStore.cooperatingObjectStores()
    #import pdb ; pdb.set_trace()
    for dbContext in dbContexts:
      if dbContext.database().addModelIfCompatible(aModel):
        return dbContext #dbContext rendez-vous
    # not found: create it
    from Database import databaseForModel
    database=databaseForModel(aModel)
    dbContext=contextClassToRegister()(database)
    rootObjStore.addCooperatingObjectStore(dbContext)
    dbContext._coordinator=rootObjStore
    return dbContext
  finally: unlock()

def setContextClassToRegister(contextClass):
  """
  """
  lock()
  try:
    global __contextClassToRegister
    __contextClassToRegister=contextClass
  finally: unlock()

def setDefaultDelegate(defaultDelegate):
  """
  See defaultDelegate() for details
  """
  lock()
  try:
    raise 'Unimplemented', 'Unimplemented yet'
    #global __defaultDelegate
    #__defaultDelegate=defaultDelegate
  finally: unlock()

def setSharedObjectLoadingEnabled(bool):
  """
  Shared E.C. is not implemented yet, hence this raises 'Unimplemented'
  """
  lock()
  try:
    raise 'Unimplemented', 'Unsupported yet'
  finally: unlock()

def handleNotification(aNotification):
  """
  Registered at module's initialization time as an observer for the
  'ObjectStoreCoordinator.CooperatingObjectStoreNeededNotification'.
  The userInfo of that notification is a dictionary with only one key,
  whose corresponding value responds to the 'entityName()' message ; the model
  corresponding to that entity is searched within the default ModelSet, and it
  is then used to initialize the Database object to be associated with the
  newly created DatabaseContext. At last, that DatabaseContext is added to
  the list of cooperatingObjectStores in the 'ObjectStoreCoordinator' which
  sent this notification.
  """
  lock()
  try:
    trace('DatabaseContext was notified')
    notification_name=aNotification.name()
    if notification_name == CooperatingObjectStoreNeededNotification:
      trace('DatabaseContext got Coop.Obj.StoreNeeded')
      objStoreCoordinator=aNotification.object()
      # the userInfo dictionary for this notification is assumed to hold only
      # one row
      entity_name=aNotification.userInfo().get('entityName') \
                   or aNotification.userInfo().values()[0].entityName()
      # TBD: fix docstring and doc. itself
      from ModelSet import defaultModelSet
      model=defaultModelSet().modelForEntityNamed(entity_name)
      if model is None:
        raise RuntimeError, \
              'Unable to find a model for entity: %s'%entity_name
      from Database import databaseForModel
      database=databaseForModel(model)
      dbContext=contextClassToRegister()(database)
      objStoreCoordinator.addCooperatingObjectStore(dbContext)
      dbContext._coordinator=objStoreCoordinator
    else:
      error('DatabaseContext module got an unhandled notification: %s'%notification_name)
  finally:
    unlock()
    
# Registers handleNotification as an observer
NC.addObserver(None,
               handleNotification,
               CooperatingObjectStoreNeededNotification )

#
class DatabaseContext(CooperatingObjectStore):
  """

  """

  def __init__(self, aDatabase):
    """

    Initialization also creates it own AdaptorContext to work with, by sending
    to aDatabase.adaptor() the message createAdaptorContext().
    
    Parameter 'aDatabase' should be a 'Modeling.Database' object
    """
    aDatabase.registerContext(self)
    self._database=aDatabase
    self._adaptorContext=aDatabase.adaptor().createAdaptorContext()
    self._delegate=defaultDelegate()  ## delegate interface TBD
    self._coordinator=None
    self._channels=[]
    self._instanceLock=RLock()

    ### Next variables are for use during the process of saving changes
    ### see prepareForSaveWithCoordinator()
    
    # updateStrategy
    self.__updateStrategy=UPDATE_WITH_NO_LOCKING

    # the EC on behalf of which the changes should be saved
    self._editingContext=None

    # GlobalIDs we are responsible for
    self._inserted_gIDs=[]
    self._updated_gIDs=[]
    self._deleted_gIDs=[]

    # PKs for newly inserted objects
    # See AdaptorChannel.primaryKeysForNewRowsWithEntity()
    self._pks_for_inserted_gIDs={} # gID -> PK value(s)

    # Set of DatabaseOperation
    self._dbOperations=[]
    self._pending_dbOperations={} # gid -> DatabaseOperation
    
    # local cache for snapshots, initialized in prepareForSaveWithCoordinator
    self.__snapshots=None
    
  def adaptorContext(self):
    """
    Returns the underlying AdaptorContext. It is determined at initialization
    time and cannot change since then.
    """
    return self._adaptorContext
  
  def availableChannel(self):
    """
    Returns an available DatabaseChannel, i.e. one in its registeredChannels()
    which is not busy (==has no fetch in progress). If all registeredChannels
    are busy or if the DatabaseContext has no registered channels, it posts
    the notification 'DatabaseChannelNeededNotification' ; if it can find an
    available channel afterwards, it simply returns it. Otherwise, a new
    DatabaseChannel is created, registered and returned.

    See also:
      DatabaseChannel.isFetchInProgress()
    """
    self.lock()
    try:
      for channel in self._channels:
        if not channel.isFetchInProgress():
          return channel
      NC.postNotification(DatabaseChannelNeededNotification, self)
      for channel in self._channels:
        if not channel.isFetchInProgress():
          return channel
      # None registered, let's create one
      channel=DatabaseChannel(self)
      self.registerChannel(channel)
      return channel
    finally:
      self.unlock()
  
  def batchFetchRelationship(self, aRelationship, objects, anEditingContext):
    """
    """
    #- fetch the objects / fire the faults, whatever
    #- get their Gids
    #- build qualifier to fetch related objects 'pk in %s'%pks
    #  (cf. msg Lazy Initialization Part II)
    #- fetch related objects
    #- now we *know* we've got them all: populate them in the db cache w/
    #    Database.recordSnapshotForSourceGlobalID()
    #--> logically AccessArrayFaultHandler do not need to fetch the db any more
    if not objects: return
    ec=anEditingContext

    if aRelationship.isToOne():
      src_entityName=objects[0].entityName()
      dst_pk_name=aRelationship.destinationAttributes()[0].name()
      src_gids=[o.globalID() for o in objects]
      src_fk_name=aRelationship.sourceAttributes()[0].name()
      src_fks=[self.database().snapshotForGlobalID(gid)[src_fk_name]
               for gid in src_gids]
      from Modeling.Qualifier import KeyValueQualifier,QualifierOperatorIn
      q=KeyValueQualifier(dst_pk_name,
                          QualifierOperatorIn,
                          src_fks)
      dst_objs=ec.fetch(aRelationship.destinationEntityName(), q)
      
    else: # to many
      src_entityName=objects[0].entityName()
      dst_attr_name=aRelationship.destinationAttributes()[0].name()
      src_gids=[o.globalID() for o in objects]
      src_pk_name=aRelationship.sourceAttributes()[0].name()
      src_pks=[gid.keyValues()[src_pk_name] for gid in src_gids]
      
      inv_rel=aRelationship.inverseRelationship()
      if inv_rel is None: # TBD: anyInverseRelationship
        raise 'aRelationship should have an inverse'
      
      from Modeling.Qualifier import KeyValueQualifier,QualifierOperatorIn
      q=KeyValueQualifier(inv_rel.name()+'.'+src_pk_name,
                          QualifierOperatorIn,
                          src_pks)
      dst_objs=ec.fetch(aRelationship.destinationEntityName(), q)
      db=self.database()
      d={} #key: srcGlobalID, rel: [list of rel_objs]
      for dst_obj in dst_objs:
        dst_gid=dst_obj.globalID()
        snap=db.snapshotForGlobalID(dst_gid)
        src_gid=KeyGlobalID(src_entityName, {src_pk_name: snap[dst_attr_name]})
        l=d.setdefault(src_gid, [])
        l.append(dst_gid)
      
      rel_name=aRelationship.name()
      for src_gid in d.keys():
        db.recordSnapshotForSourceGlobalID(d[src_gid], src_gid, rel_name)
    
  def database(self):
    """
    returns Database object bound to this databaseContext
    """
    return self._database
  
  def delegate(self):
    """
    Returns the delegate for this object.
    """
    self.lock()
    try:
      return self._delegate
    finally:
      self.unlock()
  
  def dispose(self):
    """
    Breaks the reference cycle that exists between 'self' and its 'Database'
    object.
    """
    self._database.unregisterContext(self)
    self._database=None
    
  def editingContextDidForgetObjectWithGlobalID(self,
                                                anEditingContext, aGlobalID):
    """
    """
    self._database.decrementSnapshotCountForGlobalID(aGlobalID)
    
  def forgetAllLocks(self):
    """
    Unimplemented
    """
    self.__unimplemented__()

  def forgetLocksForObjectsWithGlobalIDs(self, gIDs):
    """
    Unimplemented
    """
    self.__unimplemented__()

  def forgetSnapshotForGlobalID(self, aGlobalID):
    """
    Deletes all references for aGlobalID in the local snapshot table.

    Parameter:

      aGlobalID -- a GlobalID instance

    Raises KeyError is aGlobalID is not registered, or RuntimeError if no
    process of saving changes is in progress
    
    """
    if not self.__snapshots:
      raise RuntimeError, 'No save in progress'
    self.__snapshots.forgetSnapshotForGlobalID(aGlobalID)
    
  def forgetSnapshotsForGlobalIDs(self, gIDs):
    """
    Deletes all references for each GlobalID in gIDs in the local snapshot
    table.

    Parameter:

      gIDs -- a sequence of GlobalID instances

    Raises KeyError is one GlobalID in 'gIDs' is not registered, or
    RuntimeError if no process of saving changes is in progress
    
    """
    if not self.__snapshots:
      raise RuntimeError, 'No save in progress'
    self.__snapshots.forgetSnapshotForGlobalIDs(gIDs)

  def handleDroppedConnection(self):
    """
    Unimplemented
    """
    self.__unimplemented__()

  def hasBusyChannels(self):
    """
    Returns 1 (true) if any of the channels() answers true to the message
    'isFetchInProgress()', false otherwise.

    See also: DatabaseChannel.isFetchInProgress()
    """
    self.lock()
    try:
      for channel in self._channels:
        if channel.isFetchInProgress():
          return 1
      return 0
    finally:
      self.unlock()
  
  def localSnapshotForGlobalID(self, aGlobalID):
    """
    Returns the snapshot locally registered for the supplied GlobalID.

    If no snapshot was registered for that name and GlobalID, or if no process
    of saving changes is in progress, simply returns None.
    """
    try:
      return self.__snapshots.snapshotForGlobalID(aGlobalID)
    except AttributeError:
      return None
    
  def localSnapshotForSourceGlobalID(self, aGlobalID, aName):
    """
    Returns the toMany snapshot locally registered for the supplied GlobalID
    and name.

    Parameters:

      aGlobalID -- a GlobalID instance

      aName -- the name of the relationship

    If no toMany snapshot was registered for that name and GlobalID, or
    if no process of saving changes is in progress, simply returns None.
    """
    try:
      return self.__snapshots.snapshotForSourceGlobalID(aGlobalID, aName)
    except AttributeError:
      return None
    
  def lock(self):
    """
    Acquire the (reentrant) lock of the instance. Each call to 'lock()' must
    be balanced with a call to 'unlock()'.

    See also: unlock()
    """
    self._instanceLock.acquire()

  def missingObjectGlobalIDs(self):
    """
    Unimplemented yet
    """
    self.__unimplemented__()

  def recordSnapshotForGlobalID(self, snapshot, aGlobalID):
    """

    See SnapshotsHandling/recordSnapshotForGlobalID()
    """
    return self.__snapshots.recordSnapshotForGlobalID(snapshot, aGlobalID)
    
  def recordSnapshotForSourceGlobalID(self, gids, aGlobalID, aName):
    """

    See SnapshotsHandling.recordSnapshotForSourceGlobalID()
    """
    return self.__snapshots.recordSnapshotForSourceGlobalID(aGlobalID)

  def recordSnapshots(self, snapshots):
    """

    See SnapshotsHandling.recordSnapshots()
    """
    return self.__snapshots.recordSnapshots(snapshots)
    

  def recordToManySnapshots(self, snapshots):
    """

    See SnapshotsHandling.recordToManySnapshots()
    """
    return self.__snapshots.recordToManySnapshots(snapshots)

  def recordUpdateForObject(self, aDatabaseObject, changes):
    """
    Called by the ObjectStoreCoordinator when an other DatabaseContext
    (including self) needs to forward changes on a given object --this can
    happen, for example, when a toMany relationship that has no inverse to-one
    relationship is modified in a given object 'obj1', because in this case
    the modification implies the modification of a foreign key stored in one
    or more objects 'obj<i>!=obj1'.

    This method is for internal use, you should not need to call it by
    hand. The following explanations are implementation details.

    DatabaseContexts are designed in such a way that it can receive such
    messages before or after recordChangesInEditingContext() has processed its
    own changes. More precisely, when calling this method on self,
    recordChangesInEditingContext() takes care to process all its changes
    (inserted, updated or deleted objects) before it may call this method.

    As a consequence:

      - if recordChangesInEditingContext() has not been called yet, it just
        stores the pending changes to be applied, by simply storing 'changes'
        in a pending database operation's newRow(). These changes will be then
        processed when recordChangesInEditingContext() is called.

      - otherwise, the behaviour depends on whether the method
        recordChangesInEditingContext() already created a database operation
        for the object. If it did, then this database operation is updated
        according to the supplied changes; otherwise a brand new database
        oeration is created and local snapshots are registered for the object.

    Parameters:

      aDatabaseObject -- the database object for which changes are notified

      changes -- a dictionary of {attributeName: value}

    See also: ObjectStoreCoordinator.forwardUpdateForObject()
              recordChangesInEditingContext()
    """
    #self.__unimplemented_()
    # search for DB OP TBD
    original_gid=object_gid=aDatabaseObject.globalID()
    if object_gid.isTemporary():
      object_gid=self.__temporaryGID_to_KeyGlobalID[object_gid]
    trace('recordUpdateForObject() aDatabaseObject: %s gid: %s changes: %s'%(aDatabaseObject, object_gid, changes))
    dbOp=None

    if not self._dbOperations: 
      # recordChangesInEditingContext() not called yet: just store changes
      # for later use
      trace('recordUpdateForObject() CALLED BEFORE recordChangesInEditingContext()')
      entity=self._database.entityNamed(object_gid.entityName())
      dbOp=DatabaseOperation(object_gid, aDatabaseObject, entity)
      dbOp.setDatabaseOperator(DATABASE_UPDATE_OPERATOR)
      dbOp.setNewRow(changes)
      pendingDBops=self._pending_dbOperations.setdefault(object_gid, [])
      pendingDBops.append(dbOp)
      return

    # from this point on, we know recordChangesInEditingContext() was called:
    # we then either find the relevant dbOperation, or create our own
    #
    for op in self._dbOperations: # TBD optimize / filter et/ou acces par dict.
      if op.globalID() == object_gid:
        trace('recordUpdate() Found dbOp %s'%op)
        dbOp=op
        break

    if dbOp is None:
      trace('recordUpdate() Creating dbOp')
      entity=self._database.entityNamed(object_gid.entityName())
      dbOp=DatabaseOperation(object_gid, aDatabaseObject, entity)
      dbOp.setDatabaseOperator(DATABASE_UPDATE_OPERATOR)
      self._dbOperations.append(dbOp)
      toManyKeys=aDatabaseObject.toManyRelationshipKeys()
      snapshot=aDatabaseObject.snapshot()
      snapshot.update(changes)
      snapshot, toManySnapshots=\
                self._objectSnapshotToDBOperatorSnapshots(snapshot, object_gid,
                                                          toManyKeys, entity)
      self.recordSnapshotForGlobalID(snapshot, object_gid)
      self.recordToManySnapshots({object_gid: toManySnapshots})
      dbOp.setNewRow(snapshot)
      trace('recordUpdate snapshot:%s  dbOp.newRow():%s'%(snapshot, dbOp.newRow()))
      dbOp.recordToManySnapshots(toManySnapshots)
      if aDatabaseObject.globalID().isTemporary():
        dbOp.setDBSnapshot({})
      else:
        dbOp.setDBSnapshot(self.database().snapshotForGlobalID(object_gid))


    else:
      dbOp.newRow().update(changes)
      trace('YYYYYYYYYYYYYYYY %s %s'%(object_gid,self.snapshotForGlobalID(object_gid)))
      #self.recordSnapshotForGlobalID(dbOp.newRow(), object_gid)
    new_row=dbOp.newRow()
    trace('recordUpdateForObject(): new_row: %s'%new_row)
    
    
  def registerChannel(self, aDBchannel):
    """
    Adds the supplied DatabaseChannel to the list of registeredChannels().
    Raises ValueError if aDBchannel.databaseContext() is not self.
    If aDBChannel is already registered it silently returns.

    See also: registeredChannels(), unregisterChannel()
    """
    self.lock()
    try:
      if aDBchannel.databaseContext() is not self:
        raise ValueError, 'Tried to register a DatabaseChannel whose parent '\
              'DatabaseContext is not self'
      if aDBchannel not in self._channels:
        self._channels.append(aDBchannel)
    finally:
      self.unlock()
      
  def registeredChannels(self):
    """
    Return the list of registered DatabaseChannels.
    
    See also: registerChannels(), unregisterChannel()
    """
    self.lock()
    try:
      return tuple(self._channels)
    finally:
      self.unlock()
      
  def registerLockedObjectWithGlobalID(self, aGlobalID):
    """
    Unimplemented
    """

  def setDelegate(self, aDelegate):
    """
    Sets the object's delegate (not enabled yet)
    """
    self.lock()
    try:
      self._delegate=aDelegate
    finally:
      self.unlock()

  def setUpdateStrategy(self, aStrategy):
    """
    Sets the DatabaseContext's update strategy

    Raise RuntimeError if the DatabaseContext is currently saving changes.
    """
    self.__unimplemented__()
    
  def snapshotForGlobalID(self, aGlobalID, timestamp=DistantPastTimeInterval):
    """
    
    Returns the snapshot for 'aGlobalID'. Snapshots are searched within the
    local set of snapshots, then in the object's database.

    Returned value is 'None' if no snapshot was recorded for 'aGlobalID' or
    if a snapshot exists with a smaller timestamp than parameter 'timestamp'
    
    """
    snapshot=self.localSnapshotForGlobalID(aGlobalID)
    if snapshot is None:
      snapshot=self.database().snapshotForGlobalID(aGlobalID, timestamp)
    return snapshot

  def snapshotForSourceGlobalID(self, aGlobalID, relationshipName,
                                timestamp=DistantPastTimeInterval):
    """
    Returns the snapshot for 'aGlobalID' and the toMany relationship
    'relationshipName'. Snapshots are searched within the local set
    of snapshots, then in the object's database.
    
    Returned value is 'None' if no snapshot was recorded for 'aGlobalID' and
    'relationshipName' or if a snapshot exists with a smaller timestamp than
    parameter 'timestamp'

    """
    snapshot=self.localSnapshotForSourceGlobalID(aGlobalID, aRelationshipName)
    if not snapshot:
      snapshot=self.database().snapshotForSourceGlobalID(aGlobalID, relationshipName, timestamp)
    return snapshot
  
  def unlock(self):
    """
    Releases the (reentrant) lock of the instance.

    See also: lock()
    """
    self._instanceLock.release()

  def unregisterChannel(self, aDBChannel):
    """
    Unregister 'aDBChannel' from the list of registered DatabaseChannels.

    See also: registerChannel(), registeredChannels()
    """
    self.lock()
    try:
      self._channels.remove(aDBChannel)
    finally:
      self.unlock()
      
  def updateStrategy(self):
    """
    Returns the DatabaseContext's updateStrategy

    Default is UPDATE_WITH_NO_LOCKING
    """
    return self.__updateStrategy
  
  ##
  ## CooperatingObjectStore API
  ##
  def commitChanges(self):
    """
    Commits the transaction in progress --that transaction was opened when the
    ObjectStore got one of the two message 'saveChangesInEditingContext()' or
    'performChanges()'.

    This is the penultimate step of save changes.

    Raises 'RuntimeError' if the DatabaseContext is not saving changes.

    See also: saveChangesInEditingContext(),
              prepareForSaveWithCoordinator(), recordChangesInEditingContext(),
              performChanges(),
              rollbackChanges()
              finalizeCommitChanges()
    """
    if not self.isSavingChanges():
      raise RuntimeError, "Invalid state: DatabaseContext isn't saving changes"

    # Commit DB transaction
    self.__channel.adaptorContext().commitTransaction()
    # Note: _endTransaction() is NOT called here, but at the end of
    #       finalizeCommitChanges()
    
  def coordinator(self):
    """
    Returns the current ObjectStoreCoordinator which which the DatabaseContext
    cooperates during the process of saving changes. The coordinator is set
    when prepareForSaveWithCoordinator() is called, and it is reset whenever
    the process aborts --either with commitChanges() or rollbackChanges().

    Outside a process of save changes, this returns None.

    See also: prepareForSaveWithCoordinator(), commitChanges(),
              rollbackChanges(), _endTransaction()
    """
    return self._coordinator
  
  def finalizeCommitChanges(self):
    """
    Called by the ObjectStoreCoordinator to re-synchronize the local snapshots
    with the (application-wide) Database's snapshots and to clean everything
    up.

    This is the last step of saving changes. DatabaseContext's implementation
    forwards all changes made to its Database then empties/invalidates the
    internal state used during the process of saving changes. Last, it
    forwards the new KeyGlobalIDs (which replace the TemporaryGlobalIDs
    initially assigned to inserted objects) to the EditingContext.

    You should never called this by yourself, it is automatically called by
    the framework.

    """
    if not self.isSavingChanges():
      raise RuntimeError, "Invalid state: DatabaseContext isn't saving changes"
    
    # Forwards snapshot changes to Database
    self.database().forgetSnapshotsForGlobalIDs(self._deleted_gIDs)
    
    self.database().setTimestampToNow()
    self.database().recordSnapshots(self.__snapshots.snapshots())
    self.database().recordToManySnapshots(self.__snapshots.toManySnapshots())
    
    #trace('_inserted_gIDs: %s'%self._inserted_gIDs)
    #trace('_pks_for_inserted_gIDs: %s'%self._pks_for_inserted_gIDs)
    for gID in self._inserted_gIDs:
      obj=self._editingContext.objectForGlobalID(gID)
      keyGID=self.__temporaryGID_to_KeyGlobalID[gID]
      debug('finalizeCommitChanges %s %s->%s'%(obj,gID,keyGID))
      NC.postNotification(GlobalIDChangedNotification, gID, {gID: keyGID})

    # Cleans up temporary state
    self._endTransaction()
    
  def handlesFetchSpecification(self, aFetchSpecification):
    """
    Tells whether the DatabaseContext is responsible for the fetch
    specification. A DatabaseContext is responsible for a fetch specification
    if the fetchSpec's entity is registered in the entities set managed by the
    dbContext.database()
    """
    if self._database.entityNamed(aFetchSpecification.entityName()):
      return 1
    return 0
  
  def ownsEntityName(self, entityName):
    """
    Tells whether the DatabaseContext is responsible for the objects of the
    supplied entity. Namely: if the entity is registered in the entities set
    managed by the dbContext.database()
    """
    try:
      # will fail if aGlobalID has no method entityName (TemporaryGlobalID)
      if self._database.entityNamed(entityName):
        return 1
    except:
      pass
    return 0
  
  def ownsGlobalID(self, aGlobalID):
    """
    Tells whether the DatabaseContext is responsible for the object. A
    DatabaseContext is responsible for an object if the global id's entity is
    registered in the entities set managed by the dbContext.database()
    """
    try:
      # will fail if aGlobalID has no method entityName (TemporaryGlobalID)
      if self._database.entityNamed(aGlobalID.entityName()):
        return 1
    except:
      pass
    return 0
  
  def ownsObject(self, anObject):
    """
    Tells whether the DatabaseContext is responsible for the object. A
    DatabaseContext is responsible for an object if the object's entity is
    registered in the entities set managed by the dbContext.database()
    """
    cd=ClassDescription.classDescriptionForObject(anObject)
    if self._database.entityNamed(cd.entityName()):
        return 1
    return 0
    

  def performChanges(self):
    """
    This is the third and last step (apart from commitChanges() or
    rollbackChanges()) being triggered by the ObjectStoreCoordinator when the
    latter was instructed to save the changes made in an EditingContext.
    The 'EditingContext' instance this method works with is the one supplied
    when 'prepareForSaveWithCoordinator()' was called.

    It depends on prepareForSaveWithCoordinator() and
    recordChangesInEditingContext() having been called before ; you should
    never need to call this method by hand.

    What we do here is derive AdaptorOperations from the DatabaseOperations
    built on the previous step [recordChangesInEditingContext()]. These
    AdaptorOperations are ordered and sent to an available AdaptorChannel
    as the parameter for message performAdaptorOperation()

    Raises 'RuntimeError' if the DatabaseContext is not saving changes.
    """
    if not self.isSavingChanges():
      raise RuntimeError, "Invalid state: DatabaseContext isn't saving changes"

    self.lock()
    try:
      self.__channel=self.availableChannel().adaptorChannel()
      if self.__channel is None:
        raise RuntimeError, 'Unable to find an available AdaptorChannel'
      self.__channel.adaptorContext().beginTransaction()
      
      _adaptorOperations=[]
      trace('performChanges: dbOps: %s'%self._dbOperations)
      #import pprint
      #pprint.pprint(self._dbOperations)
      
      for dbOp in self._dbOperations:
        # Simple: No locking, just operations
        _adOps = self.__databaseOperationToAdaptorOperations(dbOp)
        if not _adOps:
          pass
          # Note: the following statement was initially coded:
          #self.forgetSnapshotForGlobalID(dbOp.globalID())
          # meaning: if the object was marked as updated but, in fact, no changes
          # needs to be forwarded to the database, then we simply discard the
          # update and forget the snapshots. Seems Ok...
          # BUT: this leads to a buggy behaviour when an object has been marked
          # as updated because at least one of its toMany-relationships-related
          # objects as been removed (and possibly deleted): sure, since the
          # information about that dropped relationship is stored in the
          # destination object (in the FK), there is no changes to forward to
          # the database as far as the source object (=self) is concerned.
          # However, if we forget the snapshots about that object, the
          # Database's snapshotForSourceGlobalID() will not be updated and will
          # be out of sync.
          # cf. test_EditingContext_Global.test_16_toManySnapshotsUpdated()
        else:
          _adaptorOperations += _adOps
      # order dbOperations NB: __TBD delegate
      _adaptorOperations.sort()
      trace("adaptorOperations: %s"%str(_adaptorOperations))
      #import pdb ; pdb.set_trace()
      self.__channel.performAdaptorOperations(_adaptorOperations)
    finally:
      self.unlock()
  
  def __databaseOperationToAdaptorOperations(self, databaseOperation):
    """
    Internally used to build a list of AdaptorOperations from a
    DatabaseOperation.
    
    """
    dbOperator=databaseOperation.databaseOperator()
    if dbOperator is DATABASE_NOTHING_OPERATOR:
      return []

    elif dbOperator is DATABASE_INSERT_OPERATOR:
      adOp=AdaptorOperation(databaseOperation.entity(),
                            ADAPTOR_INSERT_OPERATOR)
      adOp.setChangedValues(databaseOperation.newRow())
      trace('dbOptoAdOp: new row: %s'%databaseOperation.newRow())
      return [adOp]

    elif dbOperator is DATABASE_DELETE_OPERATOR:
      adOp=AdaptorOperation(databaseOperation.entity(),
                            ADAPTOR_DELETE_OPERATOR)
      entity=databaseOperation.entity()
      gID=databaseOperation.globalID()
      qual=entity.qualifierForPrimaryKey(gID.keyValues())
      adOp.setQualifier(qual)
      return [adOp]
    
    elif dbOperator is DATABASE_UPDATE_OPERATOR:
      rowDiffs=databaseOperation.rowDiffs()
      if not rowDiffs:
        trace('NO ROW DIFF %s'%databaseOperation)
        return []
      adOp=AdaptorOperation(databaseOperation.entity(),
                            ADAPTOR_UPDATE_OPERATOR)
      entity=databaseOperation.entity()
      adOp.setChangedValues(rowDiffs)
      gID=databaseOperation.globalID()
      qual=entity.qualifierForPrimaryKey(gID.keyValues())
      adOp.setQualifier(qual)
      return [adOp]
    
    else:
      raise ValueError, 'Unknown databaseOperation.databaseOperator(): %s'%str(dbOperator)

  def prepareForSaveWithCoordinator(self, aCoordinator, anEditingContext):
    """
    
    This is the first step being triggered by the ObjectStoreCoordinator when
    it is instructed to save the changes made in an EditingContext.

    See also: recordChangesInEditingContext(), performChanges(),
              commitChanges(), rollbackChanges(),
              EditingContext.saveChanges()

    (Copied from CooperatingObjectStore interface)

    DatabaseContext's implementation examines objects in 'anEditingContext' ;
    all inserted, updated or deleted objects that are under its control
    (cf. ownsGlobalID()) are kept internally --until the process ends with
    either commitChanges() or rollbackChanges(). The DatabaseContext
    afterwards cooperates with an available AdaptorChannel to generate
    primary keys for newly inserted objects.

    Raises 'RuntimeError' if the DatabaseContext is not saving changes.

    Parameters:

      aCoordinator -- the ObjectStoreCoordinator which called the method.

      anEditingContext -- the EditingContext asking for its changes to be
        saved.

    Conformance to the CooperatingObjectStore interface.

    See also: coordinator()
    """
    self.__snapshots=DatabaseContext.SnapshotsTable()
    self._coordinator=aCoordinator
    self._editingContext=ec=anEditingContext
    self.__temporaryGID_to_KeyGlobalID={}
    # first, extract from the EditingContext the objects that we
    # handle here
    for gID in ec.insertedGlobalIDs():
      # NB: cannot use ownsGlobalID on a TemporaryGlobalID
      if self.ownsObject(ec.objectForGlobalID(gID)):
        self._inserted_gIDs.append(gID)
    for gID in ec.updatedGlobalIDs():
      if self.ownsGlobalID(gID):
        self._updated_gIDs.append(gID)
    for gID in ec.deletedGlobalIDs():
      if self.ownsGlobalID(gID):
        self._deleted_gIDs.append(gID)

    # get the primary keys for inserted objects

    # this could be made more efficient if PKs were asked within a single
    # round-trip to the DB
    self.lock()
    try:
      channel=self.availableChannel().adaptorChannel()
      for gID in self._inserted_gIDs:
        entity=ec.objectForGlobalID(gID).classDescription().entity()
        pk=channel.primaryKeysForNewRowsWithEntity(1, entity)
        self._pks_for_inserted_gIDs[gID]=pk[0]
        
        newGid=KeyGlobalID(entity.name(), self._pks_for_inserted_gIDs[gID])
        self.__temporaryGID_to_KeyGlobalID[gID]=newGid
    finally:
      self.unlock()
  
  def _recordChangesInEditingContext(self, gID, obj, dbOperator):
    """
    Called by recordChangesInEditingContext() for each inserted, updated and
    deleted object it examines. This method is for internal use only, you
    should not need to call it by hand.

    This method creates the corresponding DatabaseOperation for the object,
    adds it to the DatabaseContext's database operations. It also registers
    the local snapshots for that objects (whose local snapshots will be passed
    to the database() obect when changes are saved). It also examines any
    pending DatabaseOperations that have been received by
    recordUpdateForObject() before recordChangesInEditingContext() was called
    (these changes were submitted by other DatabaseContexts). Last, it also
    examines the resulting DatabaseOperation and set its operator to
    DATABASE_NOTHING_OPERATOR if appropriate.

    Parameters:

      gID -- the globalID of the object, either temporary or not. If it is
        temporary, it will be translated into the non-temporary GlobalID
        the object will receive after it's been changed.

      obj -- the object to register

      dbOperator -- the database operator to be used for the created
        DatabaseOperation

    See also: _examineToManySnapshots()
    """
    entity=self.database().entityForObject(obj)

    if dbOperator==DATABASE_INSERT_OPERATOR or gID.isTemporary():
      # Build and use a regular (Key)GlobalID
      gID=KeyGlobalID(entity.name(), self._pks_for_inserted_gIDs[gID])
    else:
      pass
      
    #self.__temporaryGID_to_KeyGlobalID[tempGID]=gID
    dbOp=DatabaseOperation(gID, obj, entity)
    dbOp.setDatabaseOperator(dbOperator)
    self._dbOperations.append(dbOp)

    # Now for the snapshotting part
    objSnapshot=obj.snapshot()
    toManyKeys=obj.toManyRelationshipKeys()
    snapshot, toManySnapshots=\
              self._objectSnapshotToDBOperatorSnapshots(objSnapshot, gID,
                                                        toManyKeys, entity)

    # get pending operations received by recordUpdateForObject()
    pending_ops=self._pending_dbOperations.get(gID, [])
    for pending_op in pending_ops:
      snapshot.update(pending_op.newRow())
    if pending_ops:
      del self._pending_dbOperations[gID]
      
    self.recordSnapshotForGlobalID(snapshot, gID)
    self.recordToManySnapshots({gID: toManySnapshots})
    dbOp.setNewRow(snapshot)
    dbOp.recordToManySnapshots(toManySnapshots)

    if dbOperator==DATABASE_INSERT_OPERATOR:
      dbOp.setDBSnapshot({})
    else:
      dbOp.setDBSnapshot(self.database().snapshotForGlobalID(gID))

    if dbOperator==DATABASE_UPDATE_OPERATOR:
      # requires:
      # dbOp.__dbSnapshot/setDBSnapshot + .__newRow/setNewRow
      rowDiffs=dbOp.rowDiffs()
      if not rowDiffs:
        trace('recordChangesInEditingContext() ->NOTHING obj:%s gID:%s'%(obj, gID))
        dbOp.setDatabaseOperator(DATABASE_NOTHING_OPERATOR)

    trace('_recordChangesInEditingContext returning: %s, %s'%(gID, toManySnapshots))
    return gID, toManySnapshots

  def recordChangesInEditingContext(self):
    """
    Second step being triggered by the ObjectStoreCoordinator when the latter
    was instructed to save the changes made in an EditingContext.
    The 'EditingContext' instance this method works with is the one supplied
    when 'prepareForSaveWithCoordinator()' was called.

    This method has two distinct tasks:

      - it builds the set of DatabaseOperations needed to make the changes
        persistent,

      - it also registers every changed objects into the local snapshotting
        table. **IMPORTANT**: the local snapshotting mechanism does not have
        any references to TemporaryGlobalID objects: new objects are detected
        and a brand new GlobalID is built for them, based upon the PKs values
        that were computed at the previous step
        (cf. prepareForSaveWithCoordinator()). This applies to ``regular''
        snapshots and to ``toMany snapshots'' as well.
        
    You should never need to call this method by yourself, it is automatically
    called by the ObjectStoreCoordinator in charge.

    See also: prepareForSaveWithCoordinator(), performChanges(),
              commitChanges(), rollbackChanges(),
              EditingContext.saveChanges()
              recordUpdateForObject()

    Raises 'RuntimeError' if the DatabaseContext is not saving changes.

    (Copied from CooperatingObjectStore interface)

    DatabaseContext implementation builds all necessary DatabaseOperation
    objects for the observed changes. These DatabaseOperations are then
    examined to check whether they do not imply changes to other objects in
    relation with the one they were build for, and if it is the case, the
    DatabaseContext forwards these changes to the ObjectStoreCoordinator.
    You can also refer to DatabaseOperation 's documentation for a more
    complete discussion on this topic.
    
    This method also always add a fake DatabaseOperation to its own set, whose
    operator is DATABASE_NOTHING_OPERATOR and whose globalID, object and
    entity all equals to the string 'fake' --this is used by
    recordUpdateForObject(), see this method for a complete discussion on
    this.

    See also: DatabaseOperation,
              _recordChangesInEditingContext(), _examineToManySnapshots()
    """
    if not self.isSavingChanges():
      raise RuntimeError, "Invalid state: DatabaseContext isn't saving changes"

    # recordUpdateForObject() uses self._dbOperations to check whether
    # we've been already called, so we add a fake dbOperation making NOTHING
    # See recordUpdateForObject() docstring for details
    fake_dbOp=DatabaseOperation('fake', 'fake', 'fake')
    fake_dbOp.setDatabaseOperator(DATABASE_NOTHING_OPERATOR)
    self._dbOperations.append(fake_dbOp)

    # this holds tomany snapshots, returned by _recordChangesInEditingContext()
    # for use by the last phase, when invoking _examineToManySnapshots()
    toManySnaps={}

    for tempGID in self._inserted_gIDs:
      obj=self._editingContext.objectForGlobalID(tempGID)
      toManySnaps[tempGID]=self._recordChangesInEditingContext(tempGID, obj, DATABASE_INSERT_OPERATOR)

    for gID in self._updated_gIDs:
      obj=self._editingContext.objectForGlobalID(gID)
      toManySnaps[gID]=self._recordChangesInEditingContext(gID, obj, DATABASE_UPDATE_OPERATOR)
      
    for gID in self._deleted_gIDs:
      obj=self._editingContext.objectForGlobalID(gID)
      toManySnaps[gID]=self._recordChangesInEditingContext(gID, obj, DATABASE_DELETE_OPERATOR)


    # process the remaining pending databaseOperation
    for pending_gid, pending_dbOps in self._pending_dbOperations.items():
      trace('recordUpdate(): processing pending gid: %s'%pending_gid)
      assert(not pending_gid.isTemporary()) ##
      obj=self._editingContext.objectForGlobalID(gid)
      toManySnaps[gID]=self._recordChangesInEditingContext(pending_gid, obj, DATABASE_UPDATE_OPERATOR)
      self._updated_gIDs.append(pending_gid)
        
      
    assert(self._pending_dbOperations == {}) # reset

    # Forwards possible changes (to recordUpdateForObject()) at the end
    for tempGID in self._inserted_gIDs:
      obj=self._editingContext.objectForGlobalID(tempGID)
      self._examineToManySnapshots(obj,
                                    toManySnaps[tempGID][0],
                                    toManySnaps[tempGID][1])
    for gID in self._updated_gIDs:
      obj=self._editingContext.objectForGlobalID(gID)
      self._examineToManySnapshots(obj, gID, toManySnaps[gID][1])

    for gID in self._deleted_gIDs:
      obj=self._editingContext.objectForGlobalID(gID)
      self._examineToManySnapshots(obj, gID, toManySnaps[gID][1])

     
  def _examineToManySnapshots(self, object, aGlobalID, toManySnapshots):
    """
    Examine the toMany snapshot computed by recordChangesInEditingContext()
    for an object, and takes the responsability to invoke
    ObjectStoreCoordinator.forwardUpdateForObject() for any object that should
    be notified of changes they couldn't be aware of yet. This is the case
    when an object modifies a to-many relationship which has no inverse: in
    this case we have to notify the added or removed objects that the foreign
    key storing the information for the to-many rel. should be changed.

    This method is for internal use, you should not need to call it by hand.

    Note: this method does NOT react to any modifications of to-many
    relationships which have an inverse to-one relationship defined in the
    model. It is the developer's responsability to maintain the consistency of
    the graph of objects hold by an EditingContext; when this is done, as
    expected, the added or removed objects have already been notified that
    something has changed and shouldn't be notified twice.
    
    Parameters:

      object -- the object to which the toManySnapshots belongs

      aGlobalID -- the object's non-temporary global id

      toManySnapshots -- the to-many snapshots to be examined
      
    """
    changes={}
    ec=self._editingContext

    for key in toManySnapshots.keys():
      rel=object.classDescription().entity().relationshipNamed(key)
      assert(rel.isToMany())

      # to-many rel
      if rel.inverseRelationship():
        continue
      db_snap=self.database().snapshotForSourceGlobalID(aGlobalID, key)
      if list(db_snap) == toManySnapshots[key]:
        continue
      
      trace('############### %s != %s'%(list(db_snap), toManySnapshots[key]))
      inv_rel=rel.anyInverseRelationship()
      added_gids=[gid for gid in toManySnapshots[key] if gid not in db_snap]
      removed_gids=[gid for gid in db_snap if gid not in toManySnapshots[key]]
      trace('ADDED: %s'% added_gids)
      trace('REMOVED: %s'% removed_gids)
      modified_gids=added_gids+removed_gids

      ###
      def forwardChangesToObjects(self, inv_rel, removed):
        for join in inv_rel.joins():
          src_attr=join.sourceAttributeName()
          dst_attr=join.destinationAttributeName()
          if removed:
            changes[src_attr]=None
          else:
            changes[src_attr]=aGlobalID.keyValues()[dst_attr]

        real_gid=gid
        for g in self.__temporaryGID_to_KeyGlobalID.keys():
          if self.__temporaryGID_to_KeyGlobalID[g]==gid:
            real_gid=g
            break
        
        related_object=ec.faultForGlobalID(real_gid, ec)
        trace('_examineToManySnapshots() related object: %s real_gid: %s'%(related_object, real_gid))
        self._coordinator.forwardUpdateForObject(related_object, changes)
      ###
        
      for gid in added_gids:
        forwardChangesToObjects(self, inv_rel, removed=0)
                                
      for gid in removed_gids:
        forwardChangesToObjects(self, inv_rel, removed=1)
                                
  def _objectSnapshotToDBOperatorSnapshots(self, objectSnapshot,
                                           globalID, toManyKeys, entity):
    """
    Internally used to convert a CustomObject.snapshot() to a snapshot a
    DatabaseOperator can fed with.

    Returns a tuple of two elements:

      - the snapshot, as described in DatabaseOperation.setNewRow() ; if
        globalID.isTemporary() evaluates to false, the returned snapshot gets
        the primary key(s) as well, extracted from the object's globalID or,
        for new objects, from the PKs generated during 
        prepareForSaveWithCoordinator().

      - the toMany snapshots, as described in
        DatabaseOperation.recordToManySnapshots()

    In conformance to what is stated in recordToManySnapshots(), this method
    examines all toMany snapshots and replaces the temporary GlobalIDs, if
    any, with the corresponding ``regular'' KeyGlobalID.

    """
    toManySnap={}
    snap=objectSnapshot.copy()

    for key in toManyKeys:
      _sn=objectSnapshot[key]
      from CustomObject import Snapshot_ToManyFault
      if not isinstance(_sn, Snapshot_ToManyFault): 
        # 'faulted toMany' are instances of Snapshot_ToManyFault when returned
        # by CustomObject.snapshot()
        toManySnap[key]=objectSnapshot[key]
        # replace any TemporaryGlobalID with its corresponding new value
        toManySnap_key=tuple(toManySnap[key])
        for temp_gid in toManySnap_key:
          if temp_gid.isTemporary():
            toManySnap[key].remove(temp_gid)
            toManySnap[key].append(self.__temporaryGID_to_KeyGlobalID[temp_gid])
            trace('Mapping %s to %s'%(str(temp_gid), str(self.__temporaryGID_to_KeyGlobalID[temp_gid])))
      del snap[key]

    #toManySnap={globalID: toManySnap}

    if not globalID.isTemporary(): # __TBD remove this test (normally useless)
      snap.update(globalID.keyValues())
    else:
      raise RuntimeError, 'OOOOPS, shouldnt happen!!'
      #snap.update(self._pks_for_inserted_gIDs[globalID])

    # Remove keys corresponding to a to-one relationship and
    # replace them with the appropriate (source-)attributes' keys and values
    for key in snap.keys():
      rel=entity.relationshipNamed(key)
      if rel and rel.isToOne():
        gID=snap[key]
        del snap[key]
        d={}
        srcAttrs=rel.sourceAttributes()
        srcAttrNames=[attr.name() for attr in srcAttrs]
        pkValues={}
        if gID is None:
          for attr in srcAttrs:
            destinationName=rel.destinationAttributeForSourceAttribute(attr).name()
            pkValues[destinationName]=None
        elif gID.isTemporary():
          # Replace with the newly assigned one
          pkValues=self._pks_for_inserted_gIDs[gID]
        else:
          pkValues=gID.keyValues()

        #import pdb ; pdb.set_trace()
        for idx in range(len(srcAttrs)):
          snap[srcAttrNames[idx]]=pkValues[rel.destinationAttributeForSourceAttribute(srcAttrs[idx]).name()]

    # Last, check that all FKs are included, as expected
    # It can happen that it is not the case when a toMany rel. has no inverse
    # --> see test_EditingContext_Global_Inheritance,
    #     test: test_10_toMany_with_no_inverse__db_snapshot_is_correct()
    for attr in entity.attributesNames():
      snap.setdefault(attr, None)
      
    return (snap, toManySnap)
    
  def rollbackChanges(self):
    """
    Rollbacks any changes sent at the previous step (cf. performChanges()) and
    cleans up the DatabaseContext's internal variables which support the
    process of saving changes.
    
    Tou should not call this method by yourself, it is automatically triggered
    by the framework, when appropriate.
    
    Raises 'RuntimeError' if the DatabaseContext is not saving changes.

    See also: commitChanges()
    """
    if not self.isSavingChanges():
      raise RuntimeError, "Invalid state: DatabaseContext isn't saving changes"

    self.__channel.adaptorContext().rollbackTransaction()
    self._endTransaction()
    
  def valuesForKeys(self, keys, aDatabaseObject):
    """
    Unimplemented: see recordUpdateForObject() for details
    """
    self.__unimplemented__()


  def _endTransaction(self):
    """
    Private method internally used by either methods finalizeCommitChanges()
    (called after commitChanges()) or rollbackChanges() to empty the internal
    cache (holding the EditingContext, the ObjectStoreCoordinator if any, the
    generated primary keys, the list of globalIDs of objects that had changes
    and the local cache for snapshots).

    Note that the underlying adaptor channel that was opened and used during
    performChanges is closed here (hence at the end of the transaction, either
    after commit or rollback).

    See also: isSavingChanges()
    """
    self._coordinator=None
    self._editingContext=None
    self._inserted_gIDs=[]
    self._updated_gIDs=[]
    self._deleted_gIDs=[]
    self._pks_for_inserted_gIDs={}
    self._dbOperations=[]
    self._pending_dbOperations={}
    self.__snapshots=None
    self.__channel.closeChannel()
    self.__channel=None
    self.__temporaryGID_to_KeyGlobalID=None
    
  def isSavingChanges(self):
    """
    Tells whether the DatabaseContext is currently saving changes.

    See also: prepareForSaveWithCoordinator(), saveChangesInEditingContext(),
              _endTransaction()
    """
    return self.__snapshots is not None

  ##
  ## ObjectStore API
  ##

  def arrayFaultWithSourceGlobalID(self, gID, aRelationshipName,
                                   anEditingContext):
    """
    """
    ## remember: pass object to be weakreferenced by toMany fault Handler
    object=anEditingContext.objectForGlobalID(gID)
    return AccessArrayFaultHandler(gID, aRelationshipName,
                                   self, anEditingContext, object)

  # defined in superclass???
  #def editingContextDidForgetObjectWithGlobalID(self, aContext, aGlobalID):
  # """
  # """
  
  def faultForGlobalID(self, keyGlobalID, anEditingContext):
    """
    Creates and registers a new Fault for the supplied globalID.

    Raises 'ValueError' if keyGlobalID is invalid (i.e. it does not responds
    to message 'entityName()' --typically, if it is not a 'KeyGlobalID')
    
    Parameters:

      keyGlobalID -- a 'KeyGlobalID'

      anEditingContext -- the editing context in which the fault should be
        registered.
      
    """
    try:
      entityName=keyGlobalID.entityName()
    except AttributeError:
      raise ValueError, \
            "Parameter keyGlobalID does not responds to message 'entityName()'"
    cd=ClassDescription.classDescriptionForName(entityName)
    
    cd_root=cd.rootClassDescription()
    root_keyGlobalID=KeyGlobalID(cd_root.entityName(), keyGlobalID.keyValues())

    obj=anEditingContext.objectForGlobalID(root_keyGlobalID)
    if obj:
      trace('Got it:%s w/ root_keyGlobalID: %s'%(obj,root_keyGlobalID))
      return obj
    
    fault=cd_root.createInstanceWithEditingContext(anEditingContext)
    faultHandler=AccessFaultHandler(root_keyGlobalID, self,
                                    anEditingContext)
    fault.turnIntoFault(faultHandler)
    trace('Registering fault w/ GlobalID: %s'%str(root_keyGlobalID))
    anEditingContext.recordObject(fault, root_keyGlobalID)
    #trace('cd: %s / cd_root: %s'%(cd, cd_root))
    # inheritance support
    trace('Adding ec as an observer for GlobalIDChangedNotification & GlobalID: %s'%root_keyGlobalID)
    NC.addObserver(anEditingContext, EditingContext.handleNotification,
                   GlobalIDChangedNotification, root_keyGlobalID)
    return fault
    
  def faultForRawRow(self, row, entityName, anEditingContext):
    """
    Turns a row (dictionary) into a real object. Any row, such as the one
    returned by a fetch when raw rows is activated, can be turned into a
    real object given that it contains the primary keys.

    Parameters:

      row -- a dictionary. This dictionary should have the entity's primary
        keys in its keys (and their corresponding values)

      entityName -- the name of the entity the row represents

      anEditingContext -- The EditingContext in which the object should be
        registered. Defaults to self if None or omitted.

    See also: EditingContext.fetch(), FetchSpecification.setFetchesRawRows
    
    """
    entity=self._database.entityNamed(entityName)
    pks_names=entity.primaryKeyAttributeNames()
    pks={}
    for pk in pks_names:
      if not row.has_key(pk):
        raise ValueError("Cannot convert row to object: row should at least contain entity %s's primary key(s) but pk '%s' is not there"%(entityName, pk))
      pks[pk]=row[pk]
    gid=KeyGlobalID(entityName, pks)
    return self.faultForGlobalID(gid, anEditingContext)
  
  def handlesObject(self, anObject):
    """
    Simply invokes ownsObject() on itself.

    Conformance to the ObjectStore API.
    """
    return self.ownsObject(anObject)
  
  def initializeObject(self, aDatabaseObject, aGlobalID, anEditingContext):
    """

    Parameters:

      aGlobalID -- KeyGlobalID (non temporary)
      
    """
    if anEditingContext.objectForGlobalID(aGlobalID)!=aDatabaseObject:
      raise ValueError, 'aDatabaseObject %s is not registered within the '\
            'EditingContext %s for globalID: %s'%(repr(aDatabaseObject),
                                                  repr(anEditingContext),
                                                  repr(aGlobalID))
    debug('initializeObject %s %s'%(aGlobalID, aDatabaseObject))
    self.database().incrementSnapshotCountForGlobalID(aGlobalID)

      
    entity=self._database.entityNamed(aGlobalID.entityName())
 
    attrs=entity.classProperties_attributes()
    rels=entity.classProperties_relationships()
    classPropsNames=entity.classPropertiesNames()
   
    snapshot=self._database.snapshotForGlobalID(aGlobalID)

    aDatabaseObject.prepareForInitializationWithKeys(classPropsNames)
    for attr in attrs:
      attrName=attr.name()
      value=snapshot[attrName]
      if value and attr.type() == 'DateTime':
        value=attr.convertStringToAttributeType(value)
        
      aDatabaseObject.takeStoredValueForKey(value, attrName)

    # faults initialization

    #cd=aDatabaseObject.classDescription()
    for rel in rels:
      #destCD=cd.classDescriptionForDestinationKey(rel.name())

      # Now we have to initialize the object's relationships
      if rel.isToOne():
        #import pdb ; pdb.set_trace()
        destKeyValues={}
        for join in rel.joins():

          # This test is to make sure this will not be forgot when this
          # feature will be added
          # NB: for a PK to PK toOne relationship, we have no choice but
          # build a toOne fault for the destination
          # (which is not the case for FK-to-PK toOne relationship)
          if join.sourceAttributeName() in \
             rel.sourceEntity().primaryKeyAttributeNames():
            raise 'Unimplemented', 'toOne relationship joining PKs to PKs is '\
                  'is not supported yet '
          
          if snapshot[join.sourceAttributeName()]:
            destKeyValues[join.destinationAttributeName()]=snapshot[join.sourceAttributeName()]

        if destKeyValues:
          faultGlobalID=KeyGlobalID(rel.destinationEntityName(),
                                    destKeyValues)

          # Maybe we already have that object?
          # If true, we *must* use it (EditingContext/uniquing)
          destObject=anEditingContext.objectForGlobalID(faultGlobalID)

          if destObject is None:
            ## No such object was found within the editingContext: time to
            ## create a toOne fault
            destObject=self.faultForGlobalID(faultGlobalID, anEditingContext)
        else:
          destObject=None
        aDatabaseObject.takeStoredValueForKey(destObject, rel.name())
          
      else: # toMany fault
        #faultHandler=AccessArrayFaultHandler(aGlobalID, rel.name(),
        #                                     self, anEditingContext)
        faultHandler=self.arrayFaultWithSourceGlobalID(aGlobalID,
                                                       rel.name(),
                                                       anEditingContext)
        aDatabaseObject.takeStoredValueForKey(faultHandler, rel.name())
    
  def invalidateAllObjects(self):
    """
    Unimplemented yet
    """
    raise 'Unimplemented yet'
    #NC.postNotification(InvalidatedAllObjectsInStoreNotification, self)
  
  def invalidateObjectsWithGlobalIDs(self, gIDs):
    """
    Unimplemented yet.
    """
    self.__unimplemented__()

  def isObjectLockedWithGlobalID(self, aGlobalID, anEditingContext):
    """
    Locking is not supported yet: raises 'Unimplemented'
    """
    self.__unimplemented__()
    
  def lockObjectWithGlobalID(self, aGlobalID, anEditingContext):
    """
    Locking is not supported yet: raises 'Unimplemented'
    """
    self.__unimplemented__()
    
  def objectsForSourceGlobalID(self, srcGlobalID, aRelationshipName,
                               anEditingContext):
    """
    This method is dedicated for use by a toMany fault handler.

    See also: AccessArrayFaultHandler
    """
    gids=self.database().snapshotForSourceGlobalID(srcGlobalID,
                                                   aRelationshipName)
    ec=anEditingContext
    objects=[]
    # First check that the corresponding data were not already fetched
    if gids:
      # already fetched
      trace("snapshotForSrcGlobalID(%s,%s): %s"%(str(srcGlobalID),
                                                 aRelationshipName,
                                                 str(gids)))
      for gid in gids:
        object=ec.objectForGlobalID(gid)
        if object:
          trace('E.C. already has an object for gid: %s'%gid)
          objects.append(object)
        else:
          trace("E.C. hasn't any object for gid: %s"%gid)
          object=self.faultForGlobalID(gid, ec)
          # Do we need to trigger the fault? I dont think so
          objects.append(object)
    else:
      trace("Building qualifier")
      # build the qualifier
      cd=ClassDescription.classDescriptionForName(srcGlobalID.entityName())
      srcRel=cd.entity().relationshipNamed(aRelationshipName)
      srcSnapshot=self.snapshotForGlobalID(srcGlobalID)
      if not srcSnapshot:
        raise GeneralDatabaseException,\
              'Unable to find snapshot for globalID: %s'%str(srcGlobalID)
      #qualifiers=[]
      dict={}
      for join in srcRel.joins():
        value=srcSnapshot[join.sourceAttributeName()]
        #qual=KeyValueQualifier(join.destinationAttributeName(),
        #                       operator=QualifierOperatorEqual,
        #                       value=value)
        #qualifiers.append(qualifiers)
        dict[join.destinationAttributeName()]=value
      #qualifier=AndQualifier(qualifiers)
      qualifier=qualifierToMatchAllValues(dict)
      fetchSpec=FetchSpecification(entityName=srcRel.destinationEntityName(),
                                   qualifier=qualifier,
                                   deepFlag=1) # !!!
      #__TBD: recordToManySnapshot
      # fetch
      # TBD Note: we fetch against self, i.e. a DBContext, NOT the EC
      # TBD       This seems reasonable, but additional testing must be done
      # TBD       for being sure, especially w.r.t. a toMany fault being
      # TBD       fired when the set of objects in relation possibly
      # TBD       contains an object marked for deletion.
      # TBD   --> See comments in test_EC_Global's
      # TBD                        test_06_deleteRule_nullify() for details
      objects=self.objectsWithFetchSpecification(fetchSpec, ec)

    return objects
  
  def objectsCountWithFetchSpecification(self, aFetchSpec, anEditingContext):
    """
    Returns the approximate number of objects that would be returned by
    objectsWithFetchSpecification() if called with the very same parameters.
    
    About ``approximate'': the number returned is in fact the upper bound ; as
    the objects are not actually fetched against the database, it is not
    possible to determine whether some have already been deleted within
    anEditingContext (see objectsWithFetchSpecification() as well).

    Parameters:

      aFetchSpecification -- a FetchSpecification object describing the
        objects to be fetched

      anEditingContext -- the EditingContext in which the objects would be
        fetched. This is in fact an optional argument: since no objects are
        fetched it is not really needed.

    See also: DatabaseChannel.selectCountObjectsWithFetchSpecification()
    """
    entitiesNames=[aFetchSpec.entityName()]
    if aFetchSpec.isDeep():
      import ModelSet
      defaultModelSet=ModelSet.defaultModelSet()
      entity=defaultModelSet.entityNamed(aFetchSpec.entityName())
      entitiesNames+=entity.allSubEntitiesNames()
      
    objects=[]
    count=c=0
    for entityName in entitiesNames:
      aFetchSpec.setEntityName(entityName)

      self.lock()
      try:
        channel=self.availableChannel()
        c=channel.selectCountObjectsWithFetchSpecification(aFetchSpec,
                                                           anEditingContext)
      finally:
        self.unlock()
      count+=int(c) # SQLite
    
    return count

  def objectsWithFetchSpecification(self, aFetchSpec, anEditingContext):
    """
    Fetches and returns a sequence of objects corresponding to the supplied
    fetch specification 'aFetchSpec'. These objects are also recorded within
    'anEditingContext' 's uniquing table.

    If an object is already in the EditingContext, that object is returned,
    **not** the one that was fetched --unless the FetchSpecification
    'aFetchSpec' is refreshing objects (**not** tested yet).

    It does NOT discard any objects that might be deleted in the
    'anEditingContext', neither does it returns the objects that were inserted
    in anEditingContext but not saved yet ; it is the responsability of
    EditingContext.objectsWithFetchSpecification() to do this.

    Parameters:

      aFetchSpecification -- a FetchSpecification object describing the
        objects to be fetched

      anEditingContext -- the EditingContext in which the fetched objects
        should be registered.

    Returns: the fetched objects, all of which are registered within
    anEditingContext.

    See also: objectsCountWithFetchSpecification(),
              EditingContext.objectsWithFetchSpecification()

    """
    entitiesNames=[aFetchSpec.entityName()]
    if aFetchSpec.isDeep():
      import ModelSet
      defaultModelSet=ModelSet.defaultModelSet()
      entity=defaultModelSet.entityNamed(aFetchSpec.entityName())
      entitiesNames+=entity.allSubEntitiesNames()
      
    objects=[]
    original_entity_name=aFetchSpec.entityName()
    try:
      for entityName in entitiesNames:
        aFetchSpec.setEntityName(entityName)

        self.lock()
        try:
          channel=self.availableChannel()
          channel.selectObjectsWithFetchSpecification(aFetchSpec,
                                                      anEditingContext)
        finally:
          self.unlock()
          
        object=channel.fetchObject()
        while object:
          objects.append(object)
          object=channel.fetchObject()
    finally:
      # Fixes bug #753147: aFetchSpec shouldn't be changed
      aFetchSpec.setEntityName(original_entity_name)
    return objects
  
  def refaultObject(self, aDatabaseObject, aGlobalID, anEditingContext):
    """
    """
    self.__unimplemented__()
    
  def rootObjectStore(self):
    """
    Raises RuntimeError unconditionally: a DatabaseContext is not (yet)
    designed to be the 'rootObjectStore()' of an 'ObjectStore' hierarchy. The
    only possible root for an hierarchy of ObjectStores is an
    'ObjectStoreCoordinator'.

    See also: saveChangesInEditingContext()
    """
    raise RuntimeError, 'DatabaseContext cannot respond to this message'
  
  def saveChangesInEditingContext(self, anEditingContext):
    """
    Unimplemented: a DatabaseContext is not (yet) designed to be the
    'rootObjectStore()' of an 'ObjectStore' hierarchy. The only possible root
    for an hierarchy of ObjectStores is an 'ObjectStoreCoordinator'.

    See also: rootObjectStore()
    """
    raise 'Unimplemented', 'DatabaseContext is not designed to be in direct '\
          'interaction with an EditingContext. Please use an '\
          "'ObjectStoreCoordinator' as the rootObjectStore() instead."
    # self.__snapshots=DatabaseContext.SnapshotsTable()
    # ...
    
  #
  def __del__(self):
    self.dispose()

  def __unimplemented__(self):
    "Raises Unimplemented..."
    raise 'Unimplemented', 'Not supported yet'


  class SnapshotsTable(SnapshotsHandling.SnapshotsTable):
    """
    Internally used to handle local snapshots.

    """
    
    def isSnapshotRefCountingEnabled(self):
      """
      Return false: we do not care about snapshot reference counting here,
      since the snapshots are only used when saving changes and are
      discarded after the transaction finishes.
      
      Overrides
      'SnapshotsHandling.SnapshotsTable.isSnapshotRefCountingEnabled()'
      """
      return 0

    def disableSnapshotRefCounting(self):
      """
      
      Overrides 'SnapshotsHandling.SnapshotsTable.disableSnapshotRefCounting()'
      """
      pass

def __abstract__():
  raise 'AbstractMethod', 'Left intentionally unimplemented in this class, '\
        'subclass should override this method'
  
##
## Module's variables
##
__defaultDelegate=None
__contextClassToRegister=DatabaseContext
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.