diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-06-19 16:35:53 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-06-19 17:41:39 -0400 |
| commit | 83c1e03c5c74c69facfc371840ffae890f05c338 (patch) | |
| tree | 15342e8c971d42192e25f71e2174fb44f0427a20 /lib/sqlalchemy | |
| parent | 735fcd5e776f12e6237f190520ca2eef2565282d (diff) | |
| download | sqlalchemy-83c1e03c5c74c69facfc371840ffae890f05c338.tar.gz | |
Add ad-hoc mapped expressions
Added a new feature :func:`.orm.with_expression` that allows an ad-hoc
SQL expression to be added to a specific entity in a query at result
time. This is an alternative to the SQL expression being delivered as
a separate element in the result tuple.
Change-Id: Id8c479f7489fb02e09427837c59d1eabb2a6c014
Fixes: #3058
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 16 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 42 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 48 |
3 files changed, 106 insertions, 0 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 7ecd5b67e..80c27e392 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -68,6 +68,7 @@ from .query import AliasOption, Query, Bundle from ..util.langhelpers import public_factory from .. import util as _sa_util from . import strategies as _strategies +from .. import sql as _sql def create_session(bind=None, **kwargs): @@ -177,6 +178,20 @@ def deferred(*columns, **kw): return ColumnProperty(deferred=True, *columns, **kw) +def deferred_expression(): + """Indicate an attribute that populates from a query-time SQL expression. + + .. versionadded:: 1.2 + + .. seealso:: + + :ref:`mapper_deferred_expression` + + """ + prop = ColumnProperty(_sql.null()) + prop.strategy_key = (("deferred_expression", True),) + return prop + mapper = public_factory(Mapper, ".orm.mapper") synonym = public_factory(SynonymProperty, ".orm.synonym") @@ -235,6 +250,7 @@ contains_eager = strategy_options.contains_eager._unbound_fn defer = strategy_options.defer._unbound_fn undefer = strategy_options.undefer._unbound_fn undefer_group = strategy_options.undefer_group._unbound_fn +with_expression = strategy_options.with_expression._unbound_fn load_only = strategy_options.load_only._unbound_fn lazyload = strategy_options.lazyload._unbound_fn lazyload_all = strategy_options.lazyload_all._unbound_all_fn diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 690984a6a..4e7636cf8 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -195,6 +195,48 @@ class ColumnLoader(LoaderStrategy): @log.class_logger +@properties.ColumnProperty.strategy_for(deferred_expression=True) +class ExpressionColumnLoader(ColumnLoader): + def __init__(self, parent, strategy_key): + super(ExpressionColumnLoader, self).__init__(parent, strategy_key) + + def setup_query( + self, context, entity, path, loadopt, + adapter, column_collection, memoized_populators, **kwargs): + + if loadopt and "expression" in loadopt.local_opts: + columns = [loadopt.local_opts["expression"]] + + for c in columns: + if adapter: + c = adapter.columns[c] + column_collection.append(c) + + fetch = columns[0] + if adapter: + fetch = adapter.columns[fetch] + memoized_populators[self.parent_property] = fetch + + def create_row_processor( + self, context, path, + loadopt, mapper, result, adapter, populators): + # look through list of columns represented here + # to see which, if any, is present in the row. + if loadopt and "expression" in loadopt.local_opts: + columns = [loadopt.local_opts["expression"]] + + for col in columns: + if adapter: + col = adapter.columns[col] + getter = result._getter(col, False) + if getter: + populators["quick"].append((self.key, getter)) + break + else: + populators["expire"].append((self.key, True)) + + +@log.class_logger @properties.ColumnProperty.strategy_for(deferred=True, instrument=True) @properties.ColumnProperty.strategy_for(do_nothing=True) class DeferredColumnLoader(LoaderStrategy): diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 4c7e34ebb..2cdf6ba95 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -1348,6 +1348,54 @@ def undefer_group(name): return _UnboundLoad().undefer_group(name) +from ..sql import expression as sql_expr +from .util import _orm_full_deannotate + + +@loader_option() +def with_expression(loadopt, key, expression): + r"""Apply an ad-hoc SQL expression to a "deferred expression" attribute. + + This option is used in conjunction with the :func:`.orm.deferred_expression` + mapper-level construct that indicates an attribute which should be the + target of an ad-hoc SQL expression. + + E.g.:: + + + sess.query(SomeClass).options( + with_expression(SomeClass.x_y_expr, SomeClass.x + SomeClass.y) + ) + + .. versionadded:: 1.2 + + :param key: Attribute to be undeferred. + + :param expr: SQL expression to be applied to the attribute. + + .. seealso:: + + :ref:`mapper_deferred_expression` + + """ + + expression = sql_expr._labeled( + _orm_full_deannotate(expression)) + + return loadopt.set_column_strategy( + (key, ), + {"deferred_expression": True}, + opts={"expression": expression} + ) + + +@with_expression._add_unbound_fn +def with_expression(key, expression): + return _UnboundLoad._from_keys( + _UnboundLoad.with_expression, (key, ), + False, {"expression": expression}) + + @loader_option() def selectin_polymorphic(loadopt, classes): """Indicate an eager load should take place for all attributes |
