Thread: [SQL-CVS] r1439 - in SQLObject/trunk/sqlobject: . inheritance inheritance/tests
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
From: <sub...@co...> - 2005-12-24 12:01:47
|
Author: phd Date: 2005-12-24 12:01:08 +0000 (Sat, 24 Dec 2005) New Revision: 1439 Modified: SQLObject/trunk/sqlobject/inheritance/__init__.py SQLObject/trunk/sqlobject/inheritance/iteration.py SQLObject/trunk/sqlobject/inheritance/tests/test_deep_inheritance.py SQLObject/trunk/sqlobject/main.py Log: A patch for inheritance by alexander smishlajev. Modified: SQLObject/trunk/sqlobject/inheritance/__init__.py =================================================================== --- SQLObject/trunk/sqlobject/inheritance/__init__.py 2005-12-21 18:46:46 UTC (rev 1438) +++ SQLObject/trunk/sqlobject/inheritance/__init__.py 2005-12-24 12:01:08 UTC (rev 1439) @@ -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: SQLObject/trunk/sqlobject/inheritance/iteration.py =================================================================== --- SQLObject/trunk/sqlobject/inheritance/iteration.py 2005-12-21 18:46:46 UTC (rev 1438) +++ SQLObject/trunk/sqlobject/inheritance/iteration.py 2005-12-24 12:01:08 UTC (rev 1439) @@ -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: SQLObject/trunk/sqlobject/inheritance/tests/test_deep_inheritance.py =================================================================== --- SQLObject/trunk/sqlobject/inheritance/tests/test_deep_inheritance.py 2005-12-21 18:46:46 UTC (rev 1438) +++ SQLObject/trunk/sqlobject/inheritance/tests/test_deep_inheritance.py 2005-12-24 12:01:08 UTC (rev 1439) @@ -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: SQLObject/trunk/sqlobject/main.py =================================================================== --- SQLObject/trunk/sqlobject/main.py 2005-12-21 18:46:46 UTC (rev 1438) +++ SQLObject/trunk/sqlobject/main.py 2005-12-24 12:01:08 UTC (rev 1439) @@ -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, |