diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-04-01 19:18:36 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-04-01 19:37:43 -0400 |
| commit | fe1922764151454460dfabfd574d3ead12edf543 (patch) | |
| tree | 922676252957e7ad8e478159823545d9a11e1077 /lib/sqlalchemy/orm | |
| parent | 59816435dae75db9712f80c34734813a0246205a (diff) | |
| download | sqlalchemy-fe1922764151454460dfabfd574d3ead12edf543.tar.gz | |
- :class:`.Query` doesn't support joins, subselects, or special
FROM clauses when using the :meth:`.Query.update` or
:meth:`.Query.delete` methods; instead of silently ignoring these
fields if methods like :meth:`.Query.join` or
:meth:`.Query.select_from` has been called, an error is raised.
In 0.9.10 this only emits a warning.
fixes #3349
- don't needlessly call _compile_context() and build up a
whole statement that we never need. Construct QueryContext
as it's part of the event contract, but don't actually call upon
mapper attributes; use more direct systems of determining the
update or delete table.
- don't realy need _no_select_modifiers anymore
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 56 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 16 |
2 files changed, 46 insertions, 26 deletions
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index c5df63dfd..ff5dda7b3 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -16,10 +16,11 @@ in unitofwork.py. import operator from itertools import groupby, chain -from .. import sql, util, exc as sa_exc, schema +from .. import sql, util, exc as sa_exc from . import attributes, sync, exc as orm_exc, evaluator from .base import state_str, _attr_as_key, _entity_descriptor from ..sql import expression +from ..sql.base import _from_objects from . import loading @@ -1031,6 +1032,26 @@ class BulkUD(object): def __init__(self, query): self.query = query.enable_eagerloads(False) self.mapper = self.query._bind_mapper() + self._validate_query_state() + + def _validate_query_state(self): + for attr, methname, notset in ( + ('_limit', 'limit()', None), + ('_offset', 'offset()', None), + ('_order_by', 'order_by()', False), + ('_group_by', 'group_by()', False), + ('_distinct', 'distinct()', False), + ( + '_from_obj', + 'join(), outerjoin(), select_from(), or from_self()', + ()) + ): + if getattr(self.query, attr) is not notset: + raise sa_exc.InvalidRequestError( + "Can't call Query.update() or Query.delete() " + "when %s has been called" % + (methname, ) + ) @property def session(self): @@ -1055,18 +1076,34 @@ class BulkUD(object): self._do_post_synchronize() self._do_post() - def _do_pre(self): + @util.dependencies("sqlalchemy.orm.query") + def _do_pre(self, querylib): query = self.query - self.context = context = query._compile_context() - if len(context.statement.froms) != 1 or \ - not isinstance(context.statement.froms[0], schema.Table): + self.context = querylib.QueryContext(query) + + if isinstance(query._entities[0], querylib._ColumnEntity): + # check for special case of query(table) + tables = set() + for ent in query._entities: + if not isinstance(ent, querylib._ColumnEntity): + tables.clear() + break + else: + tables.update(_from_objects(ent.column)) + + if len(tables) != 1: + raise sa_exc.InvalidRequestError( + "This operation requires only one Table or " + "entity be specified as the target." + ) + else: + self.primary_table = tables.pop() + else: self.primary_table = query._only_entity_zero( "This operation requires only one Table or " "entity be specified as the target." ).mapper.local_table - else: - self.primary_table = context.statement.froms[0] session = query.session @@ -1121,7 +1158,8 @@ class BulkFetch(BulkUD): def _do_pre_synchronize(self): query = self.query session = query.session - select_stmt = self.context.statement.with_only_columns( + context = query._compile_context() + select_stmt = context.statement.with_only_columns( self.primary_table.primary_key) self.matched_rows = session.execute( select_stmt, @@ -1134,7 +1172,6 @@ class BulkUpdate(BulkUD): def __init__(self, query, values): super(BulkUpdate, self).__init__(query) - self.query._no_select_modifiers("update") self.values = values @classmethod @@ -1195,7 +1232,6 @@ class BulkDelete(BulkUD): def __init__(self, query): super(BulkDelete, self).__init__(query) - self.query._no_select_modifiers("delete") @classmethod def factory(cls, query, synchronize_session): diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 36180e8d5..9aa2e3d99 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -399,22 +399,6 @@ class Query(object): % (meth, meth) ) - def _no_select_modifiers(self, meth): - if not self._enable_assertions: - return - for attr, methname, notset in ( - ('_limit', 'limit()', None), - ('_offset', 'offset()', None), - ('_order_by', 'order_by()', False), - ('_group_by', 'group_by()', False), - ('_distinct', 'distinct()', False), - ): - if getattr(self, attr) is not notset: - raise sa_exc.InvalidRequestError( - "Can't call Query.%s() when %s has been called" % - (meth, methname) - ) - def _get_options(self, populate_existing=None, version_check=None, only_load_props=None, |
