From: Mike Bayer Date: Tue, 20 Feb 2007 01:04:07 +0000 (+0000) Subject: - added "alias" argument to contains_eager(). use it to specify the string name X-Git-Tag: rel_0_3_5~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ae000fb5501f22fd2a3e50a513eb855142ccc8cd;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - added "alias" argument to contains_eager(). use it to specify the string name or Alias instance of an alias used in the query for the eagerly loaded child items. easier to use than "decorator" --- diff --git a/CHANGES b/CHANGES index 5a2ee05606..494cc8d3e7 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,9 @@ - improved support for complex queries embedded into "where" criterion for query.select() [ticket:449] - contains_eager('foo') automatically implies eagerload('foo') + - added "alias" argument to contains_eager(). use it to specify the string name + or Alias instance of an alias used in the query for the eagerly loaded child items. + easier to use than "decorator" - mapper options like eagerload(), lazyload(), deferred(), will work for "synonym()" relationships [ticket:485] - fixed bug where cascade operations incorrectly included deleted collection diff --git a/doc/build/content/adv_datamapping.txt b/doc/build/content/adv_datamapping.txt index e1d6d92aa9..cf49dc3410 100644 --- a/doc/build/content/adv_datamapping.txt +++ b/doc/build/content/adv_datamapping.txt @@ -770,7 +770,7 @@ When result-set mapping is used with a particular Mapper, SQLAlchemy has no acce # get results normally r = query.instances(statement.execute()) -It is often the case with large queries that some of the tables within the query need to be aliased in order to distinguish them from other occurences of the same table within the query. A query that attempts to add eagerly loaded child items will often have this condition. Therefore with a little more effort a decorator function can be used to produce translated rows (in the form of a dictionary which accepts Column instances), in the case that aliasing is used for the relationship tables. +It is often the case with large queries that some of the tables within the query need to be aliased in order to distinguish them from other occurences of the same table within the query. A query that attempts to add eagerly loaded child items will often have this condition. The `contains_eager()` function takes a keyword argument `alias` which can either be the string name of an alias, or an actual `Alias` construct used in constructing the query, which will target the eager loading towards the columns of that alias (new in version 0.3.5): {python} # use an alias of the addresses table @@ -779,17 +779,8 @@ It is often the case with large queries that some of the tables within the query # define a query on USERS with an outer join to adalias statement = users_table.outerjoin(adalias).select(use_labels=True) - # define row-translation function. this should return - # a dictionary-like object that will receive Column instances from the normally expected - # table (i.e. addreses_table), and produce results from the actual result set - def adtranslator(row): - d = {} - for c in addresses_table.columns: - d[c] = row[adalias.corresponding_column(addresses_table)] - return d - # construct a Query object which expects the "addresses" results - query = session.query(User).options(contains_eager('addresses', decorator=adtranslator)) + query = session.query(User).options(contains_eager('addresses', alias=adalias)) # get results normally {sql}r = query.instances(statement.execute()) diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 222383085d..ac9272a173 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -119,14 +119,21 @@ def noload(name): used with query.options().""" return strategies.EagerLazyOption(name, lazy=None) -def contains_eager(key, decorator=None): +def contains_eager(key, alias=None, decorator=None): """return a MapperOption that will indicate to the query that the given - attribute will be eagerly loaded without any row decoration, or using - a custom row decorator. - + attribute will be eagerly loaded. + used when feeding SQL result sets directly into - query.instances(). Also bundles an EagerLazyOption to turn on eager loading in case it isnt already.""" - return (strategies.EagerLazyOption(key, lazy=False), strategies.RowDecorateOption(key, decorator=decorator)) + query.instances(). Also bundles an EagerLazyOption to turn on eager loading + in case it isnt already. + + "alias" is the string name of an alias, *or* an sql.Alias object, which represents + the aliased columns in the query. this argument is optional. + + "decorator" is mutually exclusive of "alias" and is a row-processing function which + will be applied to the incoming row before sending to the eager load handler. use this + for more sophisticated row adjustments beyond a straight alias.""" + return (strategies.EagerLazyOption(key, lazy=False), strategies.RowDecorateOption(key, alias=alias, decorator=decorator)) def defer(name): """return a MapperOption that will convert the column property of the given diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 390b83867b..0ebe397adc 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -576,10 +576,20 @@ class EagerLazyOption(StrategizedOption): EagerLazyOption.logger = logging.class_logger(EagerLazyOption) class RowDecorateOption(PropertyOption): - def __init__(self, key, decorator=None): + def __init__(self, key, decorator=None, alias=None): super(RowDecorateOption, self).__init__(key) self.decorator = decorator + self.alias = alias def process_selection_property(self, context, property): + if self.alias is not None and self.decorator is None: + if isinstance(self.alias, basestring): + self.alias = property.target.alias(self.alias) + def decorate(row): + d = {} + for c in property.target.columns: + d[c] = row[self.alias.corresponding_column(c)] + return d + self.decorator = decorate context.attributes[(EagerLoader, property)] = self.decorator RowDecorateOption.logger = logging.class_logger(RowDecorateOption) diff --git a/test/orm/mapper.py b/test/orm/mapper.py index aa3ca04773..77c90863a2 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -1068,6 +1068,36 @@ class EagerTest(MapperSuperTest): self.assert_result(l, User, *user_address_result) self.assert_sql_count(testbase.db, go, 1) + def testcustomeagerwithstringalias(self): + mapper(User, users, properties={ + 'addresses':relation(Address, lazy=False) + }) + mapper(Address, addresses) + + adalias = addresses.alias('adalias') + selectquery = users.outerjoin(adalias).select(use_labels=True) + q = create_session().query(User) + + def go(): + l = q.options(contains_eager('addresses', alias="adalias")).instances(selectquery.execute()) + self.assert_result(l, User, *user_address_result) + self.assert_sql_count(testbase.db, go, 1) + + def testcustomeagerwithalias(self): + mapper(User, users, properties={ + 'addresses':relation(Address, lazy=False) + }) + mapper(Address, addresses) + + adalias = addresses.alias('adalias') + selectquery = users.outerjoin(adalias).select(use_labels=True) + q = create_session().query(User) + + def go(): + l = q.options(contains_eager('addresses', alias=adalias)).instances(selectquery.execute()) + self.assert_result(l, User, *user_address_result) + self.assert_sql_count(testbase.db, go, 1) + def testcustomeagerwithdecorator(self): mapper(User, users, properties={ 'addresses':relation(Address, lazy=False)