summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-06-19 16:35:53 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2017-06-19 17:41:39 -0400
commit83c1e03c5c74c69facfc371840ffae890f05c338 (patch)
tree15342e8c971d42192e25f71e2174fb44f0427a20 /lib/sqlalchemy
parent735fcd5e776f12e6237f190520ca2eef2565282d (diff)
downloadsqlalchemy-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__.py16
-rw-r--r--lib/sqlalchemy/orm/strategies.py42
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py48
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