Update of /cvsroot/sqlobject/SQLObject/SQLObject
In directory sc8-pr-cvs1:/tmp/cvs-serv3728/SQLObject
Modified Files:
Tag: cascadegeddon-branch
Col.py DBConnection.py SQLObject.py
Log Message:
Implemented cascade deletes and restriction of deletes. For postgres, it does create a column restriction as well. XXX destroySelf always tries to do the manual delete, even though postgres will do the dirty job for us. Tested with Postgres and SQLLite, all tests pass
Index: Col.py
===================================================================
RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/Col.py,v
retrieving revision 1.32
retrieving revision 1.32.2.1
diff -C2 -d -r1.32 -r1.32.2.1
*** Col.py 12 Nov 2003 17:04:55 -0000 1.32
--- Col.py 3 Dec 2003 21:58:04 -0000 1.32.2.1
***************
*** 34,38 ****
columnDef=None,
validator=None,
! immutable=False):
# This isn't strictly true, since we *could* use backquotes or
--- 34,39 ----
columnDef=None,
validator=None,
! immutable=False,
! cascade=None):
# This isn't strictly true, since we *could* use backquotes or
***************
*** 50,53 ****
--- 51,60 ----
self.immutable = immutable
+ # cascade can be one of:
+ # None: no constraint is generated
+ # True: a CASCADE constraint is generated
+ # False: a RESTRICT constraint is generated
+ self.cascade = cascade
+
if type(constraints) not in (type([]), type(())):
constraints = [constraints]
***************
*** 343,346 ****
--- 350,372 ----
kw['name'] = style.instanceAttrToIDAttr(kw['name'])
SOKeyCol.__init__(self, **kw)
+
+ def postgresCreateSQL(self):
+ from SQLObject import findClass
+ sql = SOKeyCol.postgresCreateSQL(self)
+ if self.cascade is not None:
+ other = findClass(self.foreignKey)
+ tName = other._table
+ idName = other._idName
+ action = self.cascade and 'CASCADE' or 'RESTRICT'
+ constraint = ('CONSTRAINT %(tName)s_exists '
+ 'FOREIGN KEY(%(colName)s) '
+ 'REFERENCES %(tName)s(%(idName)s) '
+ 'ON DELETE %(action)s' %
+ {'tName':tName,
+ 'colName':self.dbName,
+ 'idName':idName,
+ 'action':action})
+ sql = ', '.join([sql, constraint])
+ return sql
class ForeignKey(KeyCol):
Index: DBConnection.py
===================================================================
RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v
retrieving revision 1.55
retrieving revision 1.55.2.1
diff -C2 -d -r1.55 -r1.55.2.1
*** DBConnection.py 12 Nov 2003 17:06:34 -0000 1.55
--- DBConnection.py 3 Dec 2003 21:58:05 -0000 1.55.2.1
***************
*** 279,283 ****
assert 0, "Implement in subclasses"
! def dropTable(self, tableName):
self.query("DROP TABLE %s" % tableName)
--- 279,283 ----
assert 0, "Implement in subclasses"
! def dropTable(self, tableName, cascade=False):
self.query("DROP TABLE %s" % tableName)
***************
*** 606,609 ****
--- 606,613 ----
return '%s SERIAL PRIMARY KEY' % soClass._idName
+ def dropTable(self, tableName, cascade=False):
+ self.query("DROP TABLE %s %s" % (tableName,
+ cascade and 'CASCADE' or ''))
+
def joinSQLType(self, join):
return 'INT NOT NULL'
***************
*** 860,864 ****
column.firebirdCreateSQL()))
! def dropTable(self, tableName):
self.query("DROP TABLE %s" % tableName)
self.query("DROP GENERATOR GEN_%s" % tableName)
--- 864,868 ----
column.firebirdCreateSQL()))
! def dropTable(self, tableName, cascade=False):
self.query("DROP TABLE %s" % tableName)
self.query("DROP GENERATOR GEN_%s" % tableName)
***************
*** 1092,1096 ****
self._meta["%s.id" % soClass._table] = "1"
! def dropTable(self, tableName):
try:
del self._meta["%s.id" % tableName]
--- 1096,1100 ----
self._meta["%s.id" % soClass._table] = "1"
! def dropTable(self, tableName, cascade=False):
try:
del self._meta["%s.id" % tableName]
Index: SQLObject.py
===================================================================
RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v
retrieving revision 1.64
retrieving revision 1.64.2.1
diff -C2 -d -r1.64 -r1.64.2.1
*** SQLObject.py 12 Nov 2003 17:06:00 -0000 1.64
--- SQLObject.py 3 Dec 2003 21:58:05 -0000 1.64.2.1
***************
*** 37,40 ****
--- 37,41 ----
class SQLObjectNotFound(LookupError): pass
+ class SQLObjectIntegrityError(Exception): pass
True, False = 1==1, 0==1
***************
*** 312,315 ****
--- 313,329 ----
return classRegistry[registry][name]
+ def findDependencies(name, registry=None):
+ depends = []
+ for n, klass in classRegistry[registry].items():
+ if findDependantColumns(name, klass):
+ depends.append(klass)
+ return depends
+
+ def findDependantColumns(name, klass):
+ depends = []
+ for col in klass._SO_columns:
+ if col.foreignKey == name and col.cascade is not None:
+ depends.append(col)
+ return depends
class CreateNewSQLObject:
***************
*** 933,936 ****
--- 947,954 ----
_SO_fetchAlternateID = classmethod(_SO_fetchAlternateID)
+ def _SO_depends(cls):
+ return findDependencies(cls.__name__, cls._registry)
+ _SO_depends = classmethod(_SO_depends)
+
def select(cls, clause=None, clauseTables=None,
orderBy=NoDefault, limit=None,
***************
*** 952,959 ****
# 3-03 @@: Should these have a connection argument?
! def dropTable(cls, ifExists=False, dropJoinTables=True):
if ifExists and not cls._connection.tableExists(cls._table):
return
! cls._connection.dropTable(cls._table)
if dropJoinTables:
cls.dropJoinTables(ifExists=ifExists)
--- 970,977 ----
# 3-03 @@: Should these have a connection argument?
! def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False):
if ifExists and not cls._connection.tableExists(cls._table):
return
! cls._connection.dropTable(cls._table, cascade)
if dropJoinTables:
cls.dropJoinTables(ifExists=ifExists)
***************
*** 1010,1013 ****
--- 1028,1054 ----
def destroySelf(self):
# Kills this object. Kills it dead!
+ depends = []
+ klass = self.__class__
+ depends = self._SO_depends()
+ for k in depends:
+ cols = findDependantColumns(klass.__name__, k)
+ query = []
+ restrict = False
+ for col in cols:
+ if col.cascade == False:
+ # Found a restriction
+ restrict = True
+ query.append("%s = %s" % (col.dbName, self.id))
+ query = ' OR '.join(query)
+ results = k.select(query)
+ if restrict and results.count():
+ # Restrictions only apply if there are
+ # matching records on the related table
+ raise SQLObjectIntegrityError, (
+ "Tried to delete %s::%s but "
+ "table %s has a restriction against it" %
+ (klass.__name__, self.id, k.__name__))
+ for row in results:
+ row.destroySelf()
self._SO_obsolete = True
self._connection._SO_delete(self)
|