summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py13
-rw-r--r--lib/sqlalchemy/dialects/oracle/base.py31
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py44
-rw-r--r--lib/sqlalchemy/orm/query.py59
-rw-r--r--lib/sqlalchemy/sql/compiler.py12
-rw-r--r--lib/sqlalchemy/sql/selectable.py141
-rw-r--r--test/sql/test_compiler.py6
7 files changed, 169 insertions, 137 deletions
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index 324ff2d36..971005a84 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -1437,17 +1437,10 @@ class MySQLCompiler(compiler.SQLCompiler):
self.process(join.onclause, **kwargs)))
def for_update_clause(self, select):
- # backwards compatibility
- if isinstance(select.for_update, bool):
- return ' FOR UPDATE'
- elif isinstance(select.for_update, str):
- if select.for_update == 'read':
- return ' LOCK IN SHARE MODE'
-
- if select.for_update.mode == 'read':
- return ' LOCK IN SHARE MODE'
+ if select._for_update_arg.read:
+ return " LOCK IN SHARE MODE"
else:
- return super(MySQLCompiler, self).for_update_clause(select)
+ return " FOR UPDATE"
def limit_clause(self, select):
# MySQL supports:
diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py
index 0bd009807..a3c31b7cc 100644
--- a/lib/sqlalchemy/dialects/oracle/base.py
+++ b/lib/sqlalchemy/dialects/oracle/base.py
@@ -632,7 +632,7 @@ class OracleCompiler(compiler.SQLCompiler):
# If needed, add the ora_rn, and wrap again with offset.
if select._offset is None:
- limitselect.for_update = select.for_update
+ limitselect._for_update_arg = select._for_update_arg
select = limitselect
else:
limitselect = limitselect.column(
@@ -651,7 +651,7 @@ class OracleCompiler(compiler.SQLCompiler):
offsetselect.append_whereclause(
sql.literal_column("ora_rn") > offset_value)
- offsetselect.for_update = select.for_update
+ offsetselect._for_update_arg = select._for_update_arg
select = offsetselect
kwargs['iswrapper'] = getattr(select, '_is_wrapper', False)
@@ -666,27 +666,16 @@ class OracleCompiler(compiler.SQLCompiler):
tmp = ' FOR UPDATE'
- # backwards compatibility
- if isinstance(select.for_update, bool):
- if select.for_update:
- return tmp
- elif isinstance(select.for_update, str):
- if select.for_update == 'nowait':
- return tmp + ' NOWAIT'
- else:
- return tmp
+ if select._for_update_arg.nowait:
+ tmp += " NOWAIT"
- if isinstance(select.for_update.of, list):
- tmp += ' OF ' + ', '.join(['.'.join(of) for of in select.for_update.of])
- elif isinstance(select.for_update.of, tuple):
- tmp += ' OF ' + '.'.join(select.for_update.of)
+ if select._for_update_arg.of:
+ tmp += ' OF ' + ', '.join(
+ self._process(elem) for elem in
+ select._for_update_arg.of
+ )
- if select.for_update.mode == 'update_nowait':
- return tmp + ' NOWAIT'
- elif select.for_update.mode == 'update':
- return tmp
- else:
- return super(OracleCompiler, self).for_update_clause(select)
+ return tmp
class OracleDDLCompiler(compiler.DDLCompiler):
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index 089769975..091fdeda2 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -1015,35 +1015,23 @@ class PGCompiler(compiler.SQLCompiler):
def for_update_clause(self, select):
- tmp = ' FOR UPDATE'
-
- # backwards compatibility
- if isinstance(select.for_update, bool):
- return tmp
- elif isinstance(select.for_update, str):
- if select.for_update == 'nowait':
- return tmp + ' NOWAIT'
- elif select.for_update == 'read':
- return ' FOR SHARE'
- elif select.for_update == 'read_nowait':
- return ' FOR SHARE NOWAIT'
-
- if select.for_update.mode == 'read':
- return ' FOR SHARE'
- elif select.for_update.mode == 'read_nowait':
- return ' FOR SHARE NOWAIT'
-
- if isinstance(select.for_update.of, list):
- tmp += ' OF ' + ', '.join([of[0] for of in select.for_update.of])
- elif isinstance(select.for_update.of, tuple):
- tmp += ' OF ' + select.for_update.of[0]
-
- if select.for_update.mode == 'update_nowait':
- return tmp + ' NOWAIT'
- elif select.for_update.mode == 'update':
- return tmp
+ if select._for_update_arg.read:
+ tmp = " FOR SHARE"
else:
- return super(PGCompiler, self).for_update_clause(select)
+ tmp = " FOR UPDATE"
+
+ if select._for_update_arg.nowait:
+ tmp += " NOWAIT"
+
+ if select._for_update_arg.of:
+ # TODO: assuming simplistic c.table here
+ tables = set(c.table for c in select._for_update_arg.of)
+ tmp += " OF " + ", ".join(
+ self.process(table, asfrom=True)
+ for table in tables
+ )
+
+ return tmp
def returning_clause(self, stmt, returning_cols):
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index bbaacec9e..f0d9a47d6 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -1127,6 +1127,8 @@ class Query(object):
def with_lockmode(self, mode, of=None):
"""Return a new Query object with the specified locking mode.
+ .. deprecated:: 0.9.0b2 superseded by :meth:`.Query.for_update`.
+
:param mode: a string representing the desired locking mode. A
corresponding :meth:`~sqlalchemy.orm.query.LockmodeArgs` object
is passed to the ``for_update`` parameter of
@@ -3493,60 +3495,3 @@ class AliasOption(interfaces.MapperOption):
query._from_obj_alias = sql_util.ColumnAdapter(alias)
-class LockmodeArgs(object):
-
- lockmodes = [None,
- 'read', 'read_nowait',
- 'update', 'update_nowait'
- ]
-
- mode = None
- of = None
-
- def __init__(self, mode=None, of=None):
- """ORM-level Lockmode
-
- :class:`.LockmodeArgs` defines the locking strategy for the
- dialects as given by ``FOR UPDATE [OF] [NOWAIT]``. The optional
- OF component is translated by the dialects into the supported
- tablename and columnname descriptors.
-
- :param mode: Defines the lockmode to use.
-
- ``None`` - translates to no lockmode
-
- ``'update'`` - translates to ``FOR UPDATE``
- (standard SQL, supported by most dialects)
-
- ``'update_nowait'`` - translates to ``FOR UPDATE NOWAIT``
- (supported by Oracle, PostgreSQL 8.1 upwards)
-
- ``'read'`` - translates to ``LOCK IN SHARE MODE`` (for MySQL),
- and ``FOR SHARE`` (for PostgreSQL)
-
- ``'read_nowait'`` - translates to ``FOR SHARE NOWAIT``
- (supported by PostgreSQL). ``FOR SHARE`` and
- ``FOR SHARE NOWAIT`` (PostgreSQL).
-
- :param of: either a column descriptor, or list of column
- descriptors, representing the optional OF part of the
- clause. This passes the descriptor to the
- corresponding :meth:`~sqlalchemy.orm.query.LockmodeArgs` object,
- and translates to ``FOR UPDATE OF table [NOWAIT]`` respectively
- ``FOR UPDATE OF table, table [NOWAIT]`` (PostgreSQL), or
- ``FOR UPDATE OF table.column [NOWAIT]`` respectively
- ``FOR UPDATE OF table.column, table.column [NOWAIT]`` (Oracle).
-
- .. versionadded:: 0.9.0b2
- """
-
- if isinstance(mode, bool) and mode:
- mode = 'update'
-
- self.mode = mode
-
- # extract table names and column names
- if isinstance(of, attributes.QueryableAttribute):
- self.of = (of.expression.table.name, of.expression.name)
- elif isinstance(of, (tuple, list)) and of != []:
- self.of = [(o.expression.table.name, o.expression.name) for o in of]
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 7725196ff..0fc99897e 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -1515,7 +1515,7 @@ class SQLCompiler(Compiled):
order_by_select=order_by_select, **kwargs)
if select._limit is not None or select._offset is not None:
text += self.limit_clause(select)
- if select.for_update:
+ if select._for_update_arg:
text += self.for_update_clause(select)
if self.ctes and \
@@ -1571,15 +1571,7 @@ class SQLCompiler(Compiled):
return ""
def for_update_clause(self, select):
- # backwards compatibility
- if isinstance(select.for_update, bool):
- return " FOR UPDATE" if select.for_update else ""
- elif isinstance(select.for_update, str):
- return " FOR UPDATE"
- elif select.for_update.mode is not None:
- return " FOR UPDATE"
- else:
- return ""
+ return " FOR UPDATE"
def returning_clause(self, stmt, returning_cols):
raise exc.CompileError(
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 99b2fbefc..e49c10001 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -14,7 +14,7 @@ from .elements import ClauseElement, TextClause, ClauseList, \
from .elements import _clone, \
_literal_as_text, _interpret_as_column_or_from, _expand_cloned,\
_select_iterables, _anonymous_label, _clause_element_as_expr,\
- _cloned_intersection, _cloned_difference, True_
+ _cloned_intersection, _cloned_difference, True_, _only_column_elements
from .base import Immutable, Executable, _generative, \
ColumnCollection, ColumnSet, _from_objects, Generative
from . import type_api
@@ -1151,6 +1151,68 @@ class TableClause(Immutable, FromClause):
return [self]
+class ForUpdateArg(object):
+
+ @classmethod
+ def parse_legacy_select(self, arg):
+ """Parse the for_update arugment of :func:`.select`.
+
+ :param mode: Defines the lockmode to use.
+
+ ``None`` - translates to no lockmode
+
+ ``'update'`` - translates to ``FOR UPDATE``
+ (standard SQL, supported by most dialects)
+
+ ``'nowait'`` - translates to ``FOR UPDATE NOWAIT``
+ (supported by Oracle, PostgreSQL 8.1 upwards)
+
+ ``'read'`` - translates to ``LOCK IN SHARE MODE`` (for MySQL),
+ and ``FOR SHARE`` (for PostgreSQL)
+
+ ``'read_nowait'`` - translates to ``FOR SHARE NOWAIT``
+ (supported by PostgreSQL). ``FOR SHARE`` and
+ ``FOR SHARE NOWAIT`` (PostgreSQL).
+
+ """
+ if arg is None:
+ return None
+
+ nowait = read = False
+ if arg == 'nowait':
+ nowait = True
+ elif arg == 'read':
+ read = True
+ elif arg == 'read_nowait':
+ read = nowait = True
+
+ return ForUpdateArg(read=read, nowait=nowait)
+
+ @property
+ def legacy_for_update_value(self):
+ if self.read and not self.nowait:
+ return "read"
+ elif self.read and self.nowait:
+ return "read_nowait"
+ elif self.nowait:
+ return "update_nowait"
+ else:
+ return "update"
+
+ def __init__(self, nowait=False, read=False, of=None):
+ """Represents arguments specified to :meth:`.Select.for_update`.
+
+ .. versionadded:: 0.9.0b2
+ """
+
+ self.nowait = nowait
+ self.read = read
+ if of is not None:
+ self.of = [_only_column_elements(of, "of")
+ for elem in util.to_list(of)]
+ else:
+ self.of = None
+
class SelectBase(Executable, FromClause):
"""Base class for :class:`.Select` and :class:`.CompoundSelect`."""
@@ -1158,6 +1220,7 @@ class SelectBase(Executable, FromClause):
_group_by_clause = ClauseList()
_limit = None
_offset = None
+ _for_update_arg = None
def __init__(self,
use_labels=False,
@@ -1169,7 +1232,10 @@ class SelectBase(Executable, FromClause):
bind=None,
autocommit=None):
self.use_labels = use_labels
- self.for_update = for_update
+
+ if for_update is not False:
+ self._for_update_arg = ForUpdateArg.parse_legacy_select(for_update)
+
if autocommit is not None:
util.warn_deprecated('autocommit on select() is '
'deprecated. Use .execution_options(a'
@@ -1188,6 +1254,46 @@ class SelectBase(Executable, FromClause):
if group_by is not None:
self._group_by_clause = ClauseList(*util.to_list(group_by))
+ @property
+ def for_update(self):
+ """Provide legacy dialect support for the ``for_update`` attribute
+ as a getter.
+
+ """
+ if self._for_update_arg is not None:
+ return self._for_update_arg.legacy_for_update_value
+ else:
+ return None
+
+ @_generative
+ def with_for_update(self, nowait=False, read=False, of=None):
+ """apply FOR UPDATE to this :class:`.SelectBase`.
+
+ E.g.::
+
+ stmt = select([table]).with_for_update(nowait=True)
+
+ Additional keyword arguments are provided for common database-specific
+ variants.
+
+ :param nowait: boolean; will render ``FOR UPDATE NOWAIT`` on Oracle and
+ Postgresql dialects.
+
+ :param read: boolean; will render ``LOCK IN SHARE MODE`` on MySQL,
+ ``FOR SHARE`` on Postgresql. On Postgresql, when combined with
+ ``nowait``, will render ``FOR SHARE NOWAIT``.
+
+ :param of: SQL expression or list of SQL expression elements which
+ will render into a ``FOR UPDATE OF`` clause; supported by PostgreSQL
+ and Oracle. May render as a table or as a column depending on
+ backend.
+
+
+ .. versionadded:: 0.9.0b2
+
+ """
+ self._for_update_arg = ForUpdateArg(nowait=nowait, read=read, of=of)
+
def as_scalar(self):
"""return a 'scalar' representation of this selectable, which can be
used as a column expression.
@@ -1724,6 +1830,8 @@ class HasPrefixes(object):
self._prefixes = self._prefixes + tuple(
[(_literal_as_text(p), dialect) for p in prefixes])
+
+
class Select(HasPrefixes, SelectBase):
"""Represents a ``SELECT`` statement.
@@ -1828,17 +1936,28 @@ class Select(HasPrefixes, SelectBase):
when ``True``, applies ``FOR UPDATE`` to the end of the
resulting statement.
- Certain database dialects also support
- alternate values for this parameter:
+ .. deprecated:: 0.9.0 - use :meth:`.SelectBase.with_for_update`
+ to specify for update arguments.
+
+ Additional values are accepted here, including:
+
+ ``None`` - translates to no lockmode
+
+ ``'update'`` - translates to ``FOR UPDATE``
+ (standard SQL, supported by most dialects)
+
+ ``'update_nowait'`` - translates to ``FOR UPDATE NOWAIT``
+ (supported by Oracle, PostgreSQL 8.1 upwards)
+
+ ``'read'`` - translates to ``LOCK IN SHARE MODE`` (for MySQL),
+ and ``FOR SHARE`` (for PostgreSQL)
- * With the MySQL dialect, the value ``"read"`` translates to
- ``LOCK IN SHARE MODE``.
- * With the Oracle and Postgresql dialects, the value ``"nowait"``
- translates to ``FOR UPDATE NOWAIT``.
- * With the Postgresql dialect, the values "read" and ``"read_nowait"``
- translate to ``FOR SHARE`` and ``FOR SHARE NOWAIT``, respectively.
+ ``'read_nowait'`` - translates to ``FOR SHARE NOWAIT``
+ (supported by PostgreSQL). ``FOR SHARE`` and
+ ``FOR SHARE NOWAIT`` (PostgreSQL).
- .. versionadded:: 0.7.7
+ The :meth:`.SelectBase.with_for_update` method should be preferred as
+ a means to specify FOR UPDATE more simply.
:param group_by:
a list of :class:`.ClauseElement` objects which will comprise the
diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py
index fbb88924d..26cd30026 100644
--- a/test/sql/test_compiler.py
+++ b/test/sql/test_compiler.py
@@ -1120,6 +1120,12 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
"FROM mytable WHERE mytable.myid = %(myid_1)s FOR SHARE NOWAIT",
dialect=postgresql.dialect())
+ self.assert_compile(
+ table1.select(table1.c.myid == 7).with_for_update(of=table1.c.myid),
+ "SELECT mytable.myid, mytable.name, mytable.description "
+ "FROM mytable WHERE mytable.myid = %(myid_1)s FOR UPDATE OF mytable",
+ dialect=postgresql.dialect())
+
def test_alias(self):
# test the alias for a table1. column names stay the same,
# table name "changes" to "foo".