Update of /cvsroot/webware/Webware/MiddleKit/Run
In directory usw-pr-cvs1:/tmp/cvs-serv12774/MiddleKit/Run
Modified Files:
MiddleObject.py ObjectStore.py SQLObjectStore.py
Log Message:
experimental support for deleting MK objects. tests and documentation will come soon.
Index: MiddleObject.py
===================================================================
RCS file: /cvsroot/webware/Webware/MiddleKit/Run/MiddleObject.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -C2 -d -r1.6 -r1.7
*** MiddleObject.py 2001/07/10 04:48:10 1.6
--- MiddleObject.py 2001/09/20 22:16:51 1.7
***************
*** 3,7 ****
import ObjectStore
import sys, types
!
class MiddleObject(NamedValueAccess):
--- 3,7 ----
import ObjectStore
import sys, types
! from MiddleKit.Core.ObjRefAttr import ObjRefAttr
class MiddleObject(NamedValueAccess):
***************
*** 33,36 ****
--- 33,37 ----
self._mk_initing = 0
self._mk_inStore = 0
+ self._mk_backObjRefAttrs = None
def initFromRow(self, row):
***************
*** 124,127 ****
--- 125,168 ----
return allAttrs
+ def backObjRefAttrs(self):
+ """
+ Returns a list of all ObjRefAttrs in the given object model that can
+ potentially refer to this object. The list does NOT include attributes
+ inherited from superclasses.
+ """
+ if self._mk_backObjRefAttrs is None:
+ backObjRefAttrs = []
+ # Construct targetKlasses = a list of this object's klass and all superklasses
+ targetKlasses = []
+ super = self.klass()
+ while super:
+ targetKlasses.append(super.name())
+ super = super.superklass()
+ # Look at all klasses in the model
+ for klass in self.store().model().klasses().values():
+ # find all ObjRefAttrs of this klass that refer to one of our targetKlasses
+ for attr in klass.attrs():
+ if isinstance(attr, ObjRefAttr) and attr.className() in targetKlasses:
+ backObjRefAttrs.append(attr)
+ self._mk_backObjRefAttrs = backObjRefAttrs
+ return self._mk_backObjRefAttrs
+
+ def referencingObjectsAndAttrs(self):
+ """
+ Returns a list of tuples of (object, attr) for all objects that have
+ ObjRefAttrs that reference this object.
+
+ @@ gat: This is implemented correctly, but inefficiently. It does a full
+ query of all tables in the model. I don't understand the caching
+ ramifications well enough to know if I can safely use a WHERE clause to
+ reduce the load on the database.
+ """
+ referencingObjectsAndAttrs = []
+ for backObjRefAttr in self.backObjRefAttrs():
+ objects = self.store().fetchObjectsOfClass(backObjRefAttr.klass())
+ for object in objects:
+ if object.valueForAttr(backObjRefAttr) == self:
+ referencingObjectsAndAttrs.append((object, backObjRefAttr))
+ return referencingObjectsAndAttrs
## Debugging ##
Index: ObjectStore.py
===================================================================
RCS file: /cvsroot/webware/Webware/MiddleKit/Run/ObjectStore.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -C2 -d -r1.8 -r1.9
*** ObjectStore.py 2001/06/12 14:09:37 1.8
--- ObjectStore.py 2001/09/20 22:16:51 1.9
***************
*** 5,8 ****
--- 5,10 ----
from MiddleKit.Core.ModelUser import ModelUser
from MiddleKit.Core.Klass import Klass as BaseKlass
+ from MiddleKit.Core.ObjRefAttr import ObjRefAttr
+ from MiddleKit.Core.ListAttr import ListAttr
# ^^^ for use in _klassForClass() below
# Can't import as Klass or Core.ModelUser (our superclass)
***************
*** 14,17 ****
--- 16,54 ----
pass
+ class DeleteError(Exception):
+ ''' Base class for all delete exceptions '''
+ pass
+
+ class DeleteReferencedError(Exception):
+ '''
+ This is raised when you attempt to delete an object that is referenced by other objects with
+ onDeleteOther not set to detach or cascade. You can call referencingObjectsAndAttrs() to get a
+ list of tuples of (object, attr) for the particular attributes that caused the error.
+ And you can call object() to get the object that was trying to be deleted.
+ This might not be the same as the object originally being deleted if a cascading
+ delete was happening.
+ '''
+ def __init__(self, text, object, referencingObjectsAndAttrs):
+ Exception.__init__(self, text)
+ self._object = object
+ self._referencingObjectsAndAttrs = referencingObjectsAndAttrs
+ def referencingObjects(self):
+ return self._referencingObjectsAndAttrs
+
+ class DeleteObjectWithReferencesError(Exception):
+ '''
+ This is raised when you attempt to delete an object that references other objects,
+ with onDeleteSelf=deny. You can call attrs() to get a list of attributes
+ that reference other objects with onDeleteSelf=deny. And you can call object() to
+ get the object trying to be deleted that contains those attrs.
+ This might not be the same as the object originally being deleted if a cascading
+ delete was happening.
+ '''
+ def __init__(self, text, object, attrs):
+ Exception.__init__(self, text)
+ self._object = object
+ self._attrs = attrs
+ def attrs(self):
+ return self._attrs
class ObjectStore(ModelUser):
***************
*** 53,57 ****
def hasObject(self, object):
! raise NotImplementedError
key = object.key()
if key is None:
--- 90,94 ----
def hasObject(self, object):
! """ Checks if the object is in the store. Note: this does not check the persistent store. """
key = object.key()
if key is None:
***************
*** 95,104 ****
#self._objects[key] = object
! def deleteObject(self, object):
! ''' Restrictions: The object must be contained in the store and obviously you cannot remove it more than once. '''
assert self.hasObject(object)
! self.willChange()
! self._deletedObjects.append(object)
! del self._objects[key]
--- 132,251 ----
#self._objects[key] = object
! def deleteObject(self, object, checkOnly=0):
! """
! Restrictions: The object must be contained in the store and obviously
! you cannot remove it more than once. If checkOnly is true, then
! only do the check, don't actually change anything.
! """
! # First check if the delete is possible. Then do the actual delete. This avoids partially deleting
! # objects only to have an exception halt the process in the middle.
! self.doDeleteObject(object, 1)
! if not checkOnly:
! self.doDeleteObject(object, 0)
!
! def doDeleteObject(self, object, checkOnly):
! '''
! Do the work of deleting the object. If checkOnly is true then only do the checks, don't actually delete anything.
! If checkOnly is false then go ahead and delete, assuming all checks have already been done.
! '''
! # Some basic assertions
assert self.hasObject(object)
! assert object.key() is not None
!
! if checkOnly:
! print 'checking delete of %s.%d' % (object.klass().name(), object.serialNum())
! else:
! print 'deleting %s.%d' % (object.klass().name(), object.serialNum())
!
! # Deal with all other objects that reference or are referenced by this object. By default, you are not allowed
! # to delete an object that has an ObjRef pointing to it. But if the ObjRef has
! # onDeleteOther=detach, then that ObjRef attr will be set to None and the delete will be allowed;
! # and if onDeleteOther=cascade, then that object will itself be deleted and the delete
! # will be allowed.
! #
! # You _are_ by default allowed to delete an object that points to other objects (by List or ObjRef)
! # but if onDeleteSelf=deny it will be disallowed, or if onDeleteSelf=cascade the pointed-to
! # objects will themselves be deleted.
!
! # Get the objects/attrs that reference this object
! referencingObjectsAndAttrs = object.referencingObjectsAndAttrs()
!
! # Determine all referenced objects, constructing a list of (attr, referencedObject) tuples.
! referencedAttrsAndObjects = []
! for attr in object.klass().allAttrs():
! if isinstance(attr, ObjRefAttr):
! obj = object.valueForAttr(attr)
! if obj:
! referencedAttrsAndObjects.append((attr, obj))
! elif isinstance(attr, ListAttr):
! for obj in object.valueForAttr(attr):
! referencedAttrsAndObjects.append((attr, obj))
!
! # Check for onDeleteOther=deny
! badObjectsAndAttrs = []
! for referencingObject, referencingAttr in referencingObjectsAndAttrs:
! onDeleteOther = referencingAttr.get('onDeleteOther', 'deny')
! assert onDeleteOther in ['deny', 'detach', 'cascade']
! if onDeleteOther == 'deny':
! badObjectsAndAttrs.append((referencingObject, referencingAttr))
! if badObjectsAndAttrs:
! raise DeleteReferencedError(
! 'You tried to delete an object (%s.%d) that is referenced by other objects with onDeleteOther unspecified or set to deny'
! % (object.klass().name(), object.serialNum()),
! object,
! badObjectsAndAttrs)
!
! # Check for onDeleteSelf=deny
! badAttrs = []
! for referencedAttr, referencedObject in referencedAttrsAndObjects:
! onDeleteSelf = referencedAttr.get('onDeleteSelf', 'detach')
! assert onDeleteSelf in ['deny', 'detach', 'cascade']
! if onDeleteSelf == 'deny':
! badAttrs.append(referencedAttr)
! if badAttrs:
! raise DeleteObjectWithReferencesError(
! 'You tried to delete an object (%s.%d) that references other objects with onDeleteSelf set to deny'
! % (object.klass().name(), object.serialNum()),
! object,
! badAttrs)
!
! # cascade-delete objects with onDeleteOther=cascade
! for referencingObject, referencingAttr in referencingObjectsAndAttrs:
! onDeleteOther = referencingAttr.get('onDeleteOther', 'deny')
! if onDeleteOther == 'cascade':
! # @@ gat: should protect against infinite recursion here.
! if checkOnly:
! print 'checking cascade-delete of %s.%d' % (referencingObject.klass().name(), referencingObject.serialNum())
! else:
! print 'cascade-deleting %s.%d' % (referencingObject.klass().name(), referencingObject.serialNum())
! self.doDeleteObject(referencingObject, checkOnly=checkOnly)
!
! # Check if it's possible to cascade-delete objects with onDeleteSelf=cascade
! for referencedAttr, referencedObject in referencedAttrsAndObjects:
! onDeleteSelf = referencedAttr.get('onDeleteSelf', 'detach')
! if onDeleteSelf == 'cascade':
! # @@ gat: should protect against infinite recursion here.
! if checkOnly:
! print 'checking cascade-delete of %s.%d' % (referencedObject.klass().name(), referencedObject.serialNum())
! else:
! print 'cascade-deleting %s.%d' % (referencedObject.klass().name(), referencedObject.serialNum())
! self.doDeleteObject(referencedObject, checkOnly=checkOnly)
!
! # Detach objects with onDeleteOther=detach
! if not checkOnly:
! for referencingObject, referencingAttr in referencingObjectsAndAttrs:
! onDeleteOther = referencingAttr.get('onDeleteOther', 'deny')
! if onDeleteOther == 'detach':
! print 'setting %s.%d.%s to None' % (referencingObject.klass().name(), referencingObject.serialNum(), referencingAttr.name())
! referencingObject.setValueForAttr(referencingAttr, None)
!
! # Detach objects with onDeleteSelf=detach
! # This is actually a no-op. There is nothing that needs to be set to zero.
!
! # Ok, now that that's all taken care of, do the delete of this object.
! if not checkOnly:
! self.willChange()
! self._deletedObjects.append(object)
! del self._objects[object.key()]
Index: SQLObjectStore.py
===================================================================
RCS file: /cvsroot/webware/Webware/MiddleKit/Run/SQLObjectStore.py,v
retrieving revision 1.23
retrieving revision 1.24
diff -C2 -d -r1.23 -r1.24
*** SQLObjectStore.py 2001/08/17 13:41:29 1.23
--- SQLObjectStore.py 2001/09/20 22:16:51 1.24
***************
*** 161,168 ****
def commitDeletions(self):
! if len(self._deletedObjects):
! ## @@ 2000-09-24 ce: implement this
! print '>> whoops! deletions not implemented'
! print
--- 161,168 ----
def commitDeletions(self):
! for object in self._deletedObjects:
! sql = object.sqlDeleteStmt()
! self.executeSQL(sql)
! self._deletedObjects[:] = []
***************
*** 350,354 ****
''' Invoked by fetchObjRef() if there is no possible target object for the given objRef, e.g., a dangling reference. This method invokes self.warning() and includes the objRef as decimal, hexadecimal and class:obj numbers. '''
klassId, objSerialNum = objRefSplit(objRef)
! self.warning('Obj ref dangles. dec=%i hex=%x class:obj=%i:%i.' % (objRef, objRef, klassId, objSerialNum))
return None
--- 350,354 ----
''' Invoked by fetchObjRef() if there is no possible target object for the given objRef, e.g., a dangling reference. This method invokes self.warning() and includes the objRef as decimal, hexadecimal and class:obj numbers. '''
klassId, objSerialNum = objRefSplit(objRef)
! self.warning('Obj ref dangles. dec=%i hex=%x class.obj=%s.%i.' % (objRef, objRef, self.klassForId(klassId).name(), objSerialNum))
return None
***************
*** 431,434 ****
--- 431,444 ----
res.append('where %s=%d;' % (klass.sqlIdName(), idValue))
return ''.join(res)
+
+ def sqlDeleteStmt(self):
+ '''
+ Returns the SQL delete statement for MySQL of the form:
+ delete from table where idName=idValue;
+ Installed as a method of MiddleObject.
+ '''
+ klass = self.klass()
+ assert klass is not None
+ return 'delete from %s where %s=%d;' % (klass.sqlTableName(), klass.sqlIdName(), self.serialNum())
def sqlValueForName(self, name):
|