Author: phd
Date: 2005-12-24 12:02:40 +0000 (Sat, 24 Dec 2005)
New Revision: 1440
Modified:
home/phd/SQLObject/paramstyles/sqlobject/inheritance/__init__.py
home/phd/SQLObject/paramstyles/sqlobject/inheritance/iteration.py
home/phd/SQLObject/paramstyles/sqlobject/inheritance/tests/test_deep_inheritance.py
home/phd/SQLObject/paramstyles/sqlobject/main.py
Log:
A patch for inheritance by alexander smishlajev.
Modified: home/phd/SQLObject/paramstyles/sqlobject/inheritance/__init__.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/inheritance/__init__.py 2005-12-24 12:01:08 UTC (rev 1439)
+++ home/phd/SQLObject/paramstyles/sqlobject/inheritance/__init__.py 2005-12-24 12:02:40 UTC (rev 1440)
@@ -16,8 +16,7 @@
class InheritableSelectResults(SelectResults):
IterationClass = iteration.InheritableIteration
- def __init__(self, sourceClass, clause, clauseTables=None,
- **ops):
+ def __init__(self, sourceClass, clause, clauseTables=None, **ops):
if clause is None or isinstance(clause, str) and clause == 'all':
clause = sqlbuilder.SQLTrueClause
tablesDict = sqlbuilder.tablesUsedDict(clause)
@@ -165,6 +164,8 @@
currentClass = cls.sqlmeta.parentClass
while currentClass:
for column in currentClass.sqlmeta.columnDefinitions.values():
+ if column.name == 'childName':
+ continue
if type(column) == col.ForeignKey:
continue
setattr(cls.q, column.name,
@@ -216,10 +217,20 @@
if childUpdate: return val
#DSM: If this class has a child, return the child
if 'childName' in cls.sqlmeta.columns:
- childName = val.childName
- if childName is not None:
- return cls.sqlmeta.childClasses[childName].get(id,
- connection=connection, selectResults=childResults)
+ childName = val.childName
+ if childName is not None:
+ childClass = cls.sqlmeta.childClasses[childName]
+ # If the class has no columns (which sometimes makes sense
+ # and may be true for non-inheritable (leaf) classes only),
+ # shunt the query to avoid almost meaningless SQL
+ # like "SELECT NULL FROM child WHERE id=1".
+ # This is based on assumption that child object exists
+ # if parent object exists. (If it doesn't your database
+ # is broken and that is a job for database maintenance.)
+ if not (childResults or childClass.sqlmeta.columns):
+ childResults = (None,)
+ return childClass.get(id, connection=connection,
+ selectResults=childResults)
#DSM: Now, we know we are alone or the last child in a family...
#DSM: It's time to find our parents
inst = val
@@ -277,12 +288,12 @@
new_kw = {}
parent_kw = {}
for (name, value) in kw.items():
- if hasattr(parentClass, name):
+ if (name != 'childName') and hasattr(parentClass, name):
parent_kw[name] = value
else:
new_kw[name] = value
kw = new_kw
- parent_kw["childName"] = self.sqlmeta.childName
+ parent_kw['childName'] = self.sqlmeta.childName
self._parent = parentClass(kw=parent_kw,
connection=self._connection)
@@ -298,6 +309,69 @@
return [obj.id], obj
_findAlternateID = classmethod(_findAlternateID)
+ def select(cls, clause=None, *args, **kwargs):
+ parentClass = cls.sqlmeta.parentClass
+ childUpdate = kwargs.pop('childUpdate', None)
+ # childUpdate may have one of three values:
+ # True:
+ # select was issued by parent class to create child objects.
+ # Execute select without modifications.
+ # None (default):
+ # select is run by application. If this class is inheritance
+ # child, delegate query to the parent class to utilize
+ # InheritableIteration optimizations. Selected records
+ # are restricted to this (child) class by adding childName
+ # filter to the where clause.
+ # False:
+ # select is delegated from inheritance child which is parent
+ # of another class. Delegate the query to parent if possible,
+ # but don't add childName restriction: selected records
+ # will be filtered by join to the table filtered by childName.
+ if (not childUpdate) and parentClass:
+ if childUpdate is None:
+ # this is the first parent in deep hierarchy
+ addClause = parentClass.q.childName == cls.sqlmeta.childName
+ # if the clause was one of TRUE varians, replace it
+ if (clause is None) or (clause is sqlbuilder.SQLTrueClause) \
+ or (isinstance(clause, basestring) and (clause == 'all')):
+ clause = addClause
+ else:
+ # patch WHERE condition:
+ # change ID field of this class to ID of parent class
+ # XXX the clause is patched in place; it would be better
+ # to build a new one if we have to replace field
+ clsID = cls.q.id
+ parentID = parentClass.q.id
+ def _get_patched(clause):
+ if isinstance(clause, sqlbuilder.SQLOp):
+ _patch_id_clause(clause)
+ return None
+ elif not isinstance(clause, sqlbuilder.Field):
+ return None
+ elif (clause.tableName == clsID.tableName) \
+ and (clause.fieldName == clsID.fieldName):
+ return parentID
+ else:
+ return None
+ def _patch_id_clause(clause):
+ if not isinstance(clause, sqlbuilder.SQLOp):
+ return
+ expr = _get_patched(clause.expr1)
+ if expr:
+ clause.expr1 = expr
+ expr = _get_patched(clause.expr2)
+ if expr:
+ clause.expr2 = expr
+ _patch_id_clause(clause)
+ # add childName filter
+ clause = sqlbuilder.AND(clause, addClause)
+ return parentClass.select(clause, childUpdate=False,
+ *args, **kwargs)
+ else:
+ return super(InheritableSQLObject, cls).select(
+ clause, *args, **kwargs)
+ select = classmethod(select)
+
def selectBy(cls, connection=None, **kw):
clause = []
for name, value in kw.items():
Modified: home/phd/SQLObject/paramstyles/sqlobject/inheritance/iteration.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/inheritance/iteration.py 2005-12-24 12:01:08 UTC (rev 1439)
+++ home/phd/SQLObject/paramstyles/sqlobject/inheritance/iteration.py 2005-12-24 12:02:40 UTC (rev 1440)
@@ -67,10 +67,25 @@
registry = self.select.sourceClass.sqlmeta.registry
for childName, ids in childIdsNames.items():
klass = findClass(childName, registry)
- select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids))
+ if len(ids) == 1:
+ select = klass.select(klass.q.id == ids[0],
+ childUpdate=True)
+ else:
+ select = klass.select(sqlbuilder.IN(klass.q.id, ids),
+ childUpdate=True)
query = dbconn.queryForSelect(select)
if dbconn.debug:
dbconn.printDebug(rawconn, query, 'Select children of the class %s' % childName)
self.dbconn._executeRetry(rawconn, cursor, query)
for result in cursor.fetchall():
- self._childrenResults[result[0]] = result[1:]
+ # Inheritance child classes may have no own columns
+ # (that makes sense when child class has a join
+ # that does not apply to parent class objects).
+ # In such cases result[1:] gives an empty tuple
+ # which is interpreted as "no results fetched" in .get().
+ # So .get() issues another query which is absolutely
+ # meaningless (like "SELECT NULL FROM child WHERE id=1").
+ # In order to avoid this, we replace empty results
+ # with non-empty tuple. Extra values in selectResults
+ # are Ok - they will be ignored by ._SO_selectInit().
+ self._childrenResults[result[0]] = result[1:] or (None,)
Modified: home/phd/SQLObject/paramstyles/sqlobject/inheritance/tests/test_deep_inheritance.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/inheritance/tests/test_deep_inheritance.py 2005-12-24 12:01:08 UTC (rev 1439)
+++ home/phd/SQLObject/paramstyles/sqlobject/inheritance/tests/test_deep_inheritance.py 2005-12-24 12:02:40 UTC (rev 1440)
@@ -19,21 +19,41 @@
subdudes = MultipleJoin("DIPerson", joinColumn="manager_id")
def test_deep_inheritance():
+
+ cache = getConnection().cache
+
setupClass(DIManager)
setupClass(DIEmployee)
setupClass(DIPerson)
manager = DIManager(firstName='Project', lastName='Manager',
position='Project Manager')
- employee = DIEmployee(firstName='Project', lastName='Leader',
- position='Project leader', manager=manager)
- person = DIPerson(firstName='Oneof', lastName='Authors', manager=manager)
+ manager_id = manager.id
+ employee_id = DIEmployee(firstName='Project', lastName='Leader',
+ position='Project leader', manager=manager).id
+ person_id = DIPerson(firstName='Oneof', lastName='Authors',
+ manager=manager).id
+ cache.clear()
managers = list(DIManager.select())
assert len(managers) == 1
+ cache.clear()
employees = list(DIEmployee.select())
assert len(employees) == 2
+ cache.clear()
persons = list(DIPerson.select())
assert len(persons) == 3
+ cache.clear()
+
+ person = DIPerson.get(employee_id)
+ assert isinstance(person, DIEmployee)
+
+ person = DIPerson.get(manager_id)
+ assert isinstance(person, DIEmployee)
+ assert isinstance(person, DIManager)
+ cache.clear()
+
+ person = DIEmployee.get(manager_id)
+ assert isinstance(person, DIManager)
Modified: home/phd/SQLObject/paramstyles/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/paramstyles/sqlobject/main.py 2005-12-24 12:01:08 UTC (rev 1439)
+++ home/phd/SQLObject/paramstyles/sqlobject/main.py 2005-12-24 12:02:40 UTC (rev 1440)
@@ -1513,6 +1513,9 @@
delete = classmethod(delete)
def __repr__(self):
+ if not hasattr(self, 'id'):
+ # Object initialization not finished. No attributes can be read.
+ return '<%s (not initialized)>' % self.__class__.__name__
return '<%s %r %s>' \
% (self.__class__.__name__,
self.id,
|