Update of /cvsroot/sqlobject/SQLObject/SQLObject
In directory sc8-pr-cvs1:/tmp/cvs-serv16262/SQLObject
Modified Files:
Col.py DBConnection.py SQLObject.py
Log Message:
Merge cascadegeddon-branch. This implements foreign key constraints (postgresql-only) and 'DELETE CASCADE'/'DELETE RESTRICT' (should work on all db's as it's done manually). We don't support 'UPDATE CASCADE'/'UPDATE RESTRICT' yet. To get it working, just pass the 'cascade' keyword argument to ForeignKey. True=CASCADE, False=RESTRICT, None(default)=The old behavior.
Index: Col.py
===================================================================
RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/Col.py,v
retrieving revision 1.33
retrieving revision 1.34
diff -C2 -d -r1.33 -r1.34
*** Col.py 4 Dec 2003 16:36:16 -0000 1.33
--- Col.py 4 Dec 2003 16:44:04 -0000 1.34
***************
*** 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]
***************
*** 348,351 ****
--- 355,377 ----
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.56
retrieving revision 1.57
diff -C2 -d -r1.56 -r1.57
*** DBConnection.py 4 Dec 2003 16:36:16 -0000 1.56
--- DBConnection.py 4 Dec 2003 16:44:04 -0000 1.57
***************
*** 280,284 ****
assert 0, "Implement in subclasses"
! def dropTable(self, tableName):
self.query("DROP TABLE %s" % tableName)
--- 280,284 ----
assert 0, "Implement in subclasses"
! def dropTable(self, tableName, cascade=False):
self.query("DROP TABLE %s" % tableName)
***************
*** 614,617 ****
--- 614,621 ----
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'
***************
*** 982,986 ****
column.firebirdCreateSQL()))
! def dropTable(self, tableName):
self.query("DROP TABLE %s" % tableName)
self.query("DROP GENERATOR GEN_%s" % tableName)
--- 986,990 ----
column.firebirdCreateSQL()))
! def dropTable(self, tableName, cascade=False):
self.query("DROP TABLE %s" % tableName)
self.query("DROP GENERATOR GEN_%s" % tableName)
***************
*** 1218,1222 ****
self._meta["%s.id" % soClass._table] = "1"
! def dropTable(self, tableName):
try:
del self._meta["%s.id" % tableName]
--- 1222,1226 ----
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.65
diff -C2 -d -r1.64 -r1.65
*** SQLObject.py 12 Nov 2003 17:06:00 -0000 1.64
--- SQLObject.py 4 Dec 2003 16:44:04 -0000 1.65
***************
*** 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)
|