From: Mike Bayer Date: Sun, 13 Jan 2008 19:04:55 +0000 (+0000) Subject: - _get_equivalents() converted into a lazy-initializing property; Query was calling it X-Git-Tag: rel_0_4_3~110 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=188c2ac8e500054f1bfe54f91b0914f14854d311;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - _get_equivalents() converted into a lazy-initializing property; Query was calling it for polymorphic loads which is really expensive - surrogate_mapper adapts the given order_by, so that order_by can be against the mapped table and is usable for sub-mappers as well. Query properly calls select_mapper.order_by. --- diff --git a/CHANGES b/CHANGES index e68d8f1195..a460f53d4c 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,13 @@ CHANGES of being deferred until later. This mimics the old 0.3 behavior. + - fixed bug in polymorphic inheritance which made it + difficult to set a working "order_by" on a polymorphic + mapper + + - fixed a rather expensive call in Query that was slowing + down polymorphic queries + - general - warnings are now issued as type exceptions.SAWarning. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 2d1eebf154..84a9bfeab3 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -363,7 +363,11 @@ class Mapper(object): if self.version_id_col is None: self.version_id_col = self.inherits.version_id_col - + + for mapper in self.iterate_to_root(): + if hasattr(mapper, '_genned_equivalent_columns'): + del mapper._genned_equivalent_columns + if self.order_by is False: self.order_by = self.inherits.order_by self.polymorphic_map = self.inherits.polymorphic_map @@ -432,13 +436,11 @@ class Mapper(object): if self.inherits is not None and not self.concrete and not self.primary_key_argument: self.primary_key = self.inherits.primary_key self._get_clause = self.inherits._get_clause - self._equivalent_columns = {} else: # create the "primary_key" for this mapper. this will flatten "equivalent" primary key columns # into one column, where "equivalent" means that one column references the other via foreign key, or # multiple columns that all reference a common parent column. it will also resolve the column # against the "mapped_table" of this mapper. - self._equivalent_columns = self._get_equivalent_columns() primary_key = expression.ColumnSet() @@ -491,7 +493,7 @@ class Mapper(object): _get_clause.clauses.append(primary_key == bind) self._get_clause = (_get_clause, _get_params) - def _get_equivalent_columns(self): + def __get_equivalent_columns(self): """Create a map of all *equivalent* columns, based on the determination of column pairs that are equated to one another either by an established foreign key relationship @@ -556,7 +558,14 @@ class Mapper(object): equivs(col, util.Set(), col) return result - + def _equivalent_columns(self): + if hasattr(self, '_genned_equivalent_columns'): + return self._genned_equivalent_columns + else: + self._genned_equivalent_columns = self.__get_equivalent_columns() + return self._genned_equivalent_columns + _equivalent_columns = property(_equivalent_columns) + class _CompileOnAttr(PropComparator): """placeholder class attribute which fires mapper compilation on access""" @@ -714,14 +723,21 @@ class Mapper(object): """ if self.select_table is not self.mapped_table: - props = {} + self.__surrogate_mapper = Mapper(self.class_, self.select_table, non_primary=True, _polymorphic_map=self.polymorphic_map, polymorphic_on=_corresponding_column_or_error(self.select_table, self.polymorphic_on), primary_key=self.primary_key_argument) + adapter = sqlutil.ClauseAdapter(self.select_table, equivalents=self.__surrogate_mapper._equivalent_columns) + + if self.order_by: + order_by = [expression._literal_as_text(o) for o in util.to_list(self.order_by) or []] + order_by = adapter.copy_and_process(order_by) + self.__surrogate_mapper.order_by=order_by + if self._init_properties is not None: for key, prop in self._init_properties.iteritems(): if expression.is_column(prop): - props[key] = _corresponding_column_or_error(self.select_table, prop) + self.__surrogate_mapper.add_property(key, _corresponding_column_or_error(self.select_table, prop)) elif (isinstance(prop, list) and expression.is_column(prop[0])): - props[key] = [_corresponding_column_or_error(self.select_table, c) for c in prop] - self.__surrogate_mapper = Mapper(self.class_, self.select_table, non_primary=True, properties=props, _polymorphic_map=self.polymorphic_map, polymorphic_on=_corresponding_column_or_error(self.select_table, self.polymorphic_on), primary_key=self.primary_key_argument) + self.__surrogate_mapper.add_property(key, [_corresponding_column_or_error(self.select_table, c) for c in prop]) + def _compile_class(self): """If this mapper is to be a primary mapper (i.e. the diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index fd3fb7a77f..19af7b4737 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -617,7 +617,7 @@ class PropertyLoader(StrategizedProperty): # as we will be using the polymorphic selectables (i.e. select_table argument to Mapper) to figure this out, # first create maps of all the "equivalent" columns, since polymorphic selectables will often munge # several "equivalent" columns (such as parent/child fk cols) into just one column. - target_equivalents = self.mapper._get_equivalent_columns() + target_equivalents = self.mapper._equivalent_columns if self.secondaryjoin: self.polymorphic_secondaryjoin = ClauseAdapter(self.mapper.select_table).traverse(self.secondaryjoin, clone=True) @@ -704,7 +704,7 @@ class PropertyLoader(StrategizedProperty): try: return self._parent_join_cache[(parent, primary, secondary, polymorphic_parent)] except KeyError: - parent_equivalents = parent._get_equivalent_columns() + parent_equivalents = parent._equivalent_columns secondaryjoin = self.polymorphic_secondaryjoin if polymorphic_parent: # adapt the "parent" side of our join condition to the "polymorphic" select of the parent diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index c79b56ed5c..f651f04345 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -966,14 +966,14 @@ class Query(object): # adapt for poylmorphic mapper # TODO: generalize the polymorphic mapper adaption to that of the select_from() adaption if not adapt_criterion and whereclause is not None and (self.mapper is not self.select_mapper): - whereclause = sql_util.ClauseAdapter(from_obj, equivalents=self.select_mapper._get_equivalent_columns()).traverse(whereclause) + whereclause = sql_util.ClauseAdapter(from_obj, equivalents=self.select_mapper._equivalent_columns).traverse(whereclause) # TODO: mappers added via add_entity(), adapt their queries also, # if those mappers are polymorphic order_by = self._order_by if order_by is False: - order_by = self.mapper.order_by + order_by = self.select_mapper.order_by if order_by is False: order_by = [] if self.table.default_order_by() is not None: @@ -1069,7 +1069,7 @@ class Query(object): context.primary_columns.append(c) statement = sql.select(context.primary_columns + context.secondary_columns, whereclause, from_obj=from_obj, use_labels=True, for_update=for_update, order_by=util.to_list(order_by), **self._select_args()) - + if context.eager_joins: if adapt_criterion: context.eager_joins = sql_util.ClauseAdapter(from_obj).traverse(context.eager_joins) diff --git a/test/orm/inheritance/query.py b/test/orm/inheritance/query.py index bace4e6cf1..698df33fa7 100644 --- a/test/orm/inheritance/query.py +++ b/test/orm/inheritance/query.py @@ -81,7 +81,9 @@ class PolymorphicQueryTest(ORMTest): mapper(Company, companies, properties={ 'employees':relation(Person) }) - mapper(Person, people, select_table=person_join, polymorphic_on=people.c.type, polymorphic_identity='person', order_by=person_join.c.person_id, + + # testing a order_by here as well; the surrogate mapper has to adapt it + mapper(Person, people, select_table=person_join, polymorphic_on=people.c.type, polymorphic_identity='person', order_by=people.c.person_id, properties={ 'paperwork':relation(Paperwork) }) @@ -91,6 +93,8 @@ class PolymorphicQueryTest(ORMTest): mapper(Paperwork, paperwork) def insert_data(self): + global all_employees, c1_employees, c2_employees, e1, e2, b1, m1, e3 + c1 = Company(name="MegaCorp, Inc.") c2 = Company(name="Elbonia, Inc.") e1 = Engineer(name="dilbert", engineer_name="dilbert", primary_language="java", status="regular engineer", paperwork=[ @@ -118,11 +122,27 @@ class PolymorphicQueryTest(ORMTest): sess.flush() sess.clear() - global all_employees, c1_employees, c2_employees all_employees = [e1, e2, b1, m1, e3] c1_employees = [e1, e2, b1, m1] c2_employees = [e3] + def test_filter_on_subclass(self): + print Manager.person_id == Engineer.person_id + print Manager.c.person_id == Engineer.c.person_id + + sess = create_session() + self.assertEquals(sess.query(Engineer).all()[0], Engineer(name="dilbert")) + + self.assertEquals(sess.query(Engineer).first(), Engineer(name="dilbert")) + + self.assertEquals(sess.query(Engineer).filter(Engineer.person_id==e1.person_id).first(), Engineer(name="dilbert")) + + self.assertEquals(sess.query(Manager).filter(Manager.person_id==m1.person_id).one(), Manager(name="dogbert")) + + self.assertEquals(sess.query(Manager).filter(Manager.person_id==b1.person_id).one(), Boss(name="pointy haired boss")) + + self.assertEquals(sess.query(Boss).filter(Boss.person_id==b1.person_id).one(), Boss(name="pointy haired boss")) + def test_load_all(self): sess = create_session()