diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/dialects/mysql/base.py | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/oracle/base.py | 24 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/postgresql/base.py | 36 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 121 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 7 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 1 |
6 files changed, 156 insertions, 42 deletions
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index eec4d8a48..324ff2d36 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -1437,7 +1437,14 @@ class MySQLCompiler(compiler.SQLCompiler): self.process(join.onclause, **kwargs))) def for_update_clause(self, select): - if select.for_update == 'read': + # 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' else: return super(MySQLCompiler, self).for_update_clause(select) diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index e7263ba52..0bd009807 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -663,8 +663,28 @@ class OracleCompiler(compiler.SQLCompiler): def for_update_clause(self, select): if self.is_subquery(): return "" - elif select.for_update == "nowait": - return " FOR UPDATE NOWAIT" + + 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 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.mode == 'update_nowait': + return tmp + ' NOWAIT' + elif select.for_update.mode == 'update': + return tmp else: return super(OracleCompiler, self).for_update_clause(select) diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index e1dc4af71..089769975 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -230,7 +230,7 @@ RESERVED_WORDS = set( "default", "deferrable", "desc", "distinct", "do", "else", "end", "except", "false", "fetch", "for", "foreign", "from", "grant", "group", "having", "in", "initially", "intersect", "into", "leading", "limit", - "localtime", "localtimestamp", "new", "not", "null", "off", "offset", + "localtime", "localtimestamp", "new", "not", "null", "of", "off", "offset", "old", "on", "only", "or", "order", "placing", "primary", "references", "returning", "select", "session_user", "some", "symmetric", "table", "then", "to", "trailing", "true", "union", "unique", "user", "using", @@ -1014,12 +1014,34 @@ class PGCompiler(compiler.SQLCompiler): return "" def for_update_clause(self, select): - if select.for_update == 'nowait': - return " FOR UPDATE NOWAIT" - elif select.for_update == 'read': - return " FOR SHARE" - elif select.for_update == 'read_nowait': - return " FOR SHARE NOWAIT" + + 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 else: return super(PGCompiler, self).for_update_clause(select) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 7b6de0d01..bbaacec9e 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1124,33 +1124,42 @@ class Query(object): self._execution_options = self._execution_options.union(kwargs) @_generative() - def with_lockmode(self, mode): + def with_lockmode(self, mode, of=None): """Return a new Query object with the specified locking mode. :param mode: a string representing the desired locking mode. A - corresponding value is passed to the ``for_update`` parameter of - :meth:`~sqlalchemy.sql.expression.select` when the query is - executed. Valid values are: + corresponding :meth:`~sqlalchemy.orm.query.LockmodeArgs` object + is passed to the ``for_update`` parameter of + :meth:`~sqlalchemy.sql.expression.select` when the + query is executed. Valid values are: - ``'update'`` - passes ``for_update=True``, which translates to - ``FOR UPDATE`` (standard SQL, supported by most dialects) + ``None`` - translates to no lockmode - ``'update_nowait'`` - passes ``for_update='nowait'``, which - translates to ``FOR UPDATE NOWAIT`` (supported by Oracle, - PostgreSQL 8.1 upwards) + ``'update'`` - translates to ``FOR UPDATE`` + (standard SQL, supported by most dialects) - ``'read'`` - passes ``for_update='read'``, which translates to - ``LOCK IN SHARE MODE`` (for MySQL), and ``FOR SHARE`` (for - PostgreSQL) + ``'update_nowait'`` - translates to ``FOR UPDATE NOWAIT`` + (supported by Oracle, PostgreSQL 8.1 upwards) - ``'read_nowait'`` - passes ``for_update='read_nowait'``, which - translates to ``FOR SHARE NOWAIT`` (supported by PostgreSQL). + ``'read'`` - translates to ``LOCK IN SHARE MODE`` (for MySQL), + and ``FOR SHARE`` (for PostgreSQL) .. versionadded:: 0.7.7 ``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 """ - self._lockmode = mode + self._lockmode = LockmodeArgs(mode=mode, of=of) @_generative() def params(self, *args, **kwargs): @@ -2683,13 +2692,6 @@ class Query(object): update_op.exec_() return update_op.rowcount - _lockmode_lookup = { - 'read': 'read', - 'read_nowait': 'read_nowait', - 'update': True, - 'update_nowait': 'nowait', - None: False - } def _compile_context(self, labels=True): context = QueryContext(self) @@ -2699,12 +2701,13 @@ class Query(object): context.labels = labels - if self._lockmode: - try: - context.for_update = self._lockmode_lookup[self._lockmode] - except KeyError: - raise sa_exc.ArgumentError( - "Unknown lockmode %r" % self._lockmode) + if isinstance(self._lockmode, bool) and self._lockmode: + context.for_update = LockmodeArgs(mode='update') + elif isinstance(self._lockmode, LockmodeArgs): + if self._lockmode.mode not in LockmodeArgs.lockmodes: + raise sa_exc.ArgumentError('Unknown lockmode %r' % self._lockmode.mode) + context.for_update = self._lockmode + for entity in self._entities: entity.setup_context(self, context) @@ -3409,12 +3412,11 @@ class _ColumnEntity(_QueryEntity): return str(self.column) - class QueryContext(object): multi_row_eager_loaders = False adapter = None froms = () - for_update = False + for_update = None def __init__(self, query): @@ -3489,3 +3491,62 @@ class AliasOption(interfaces.MapperOption): else: alias = self.alias 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 59506edea..7725196ff 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1571,7 +1571,12 @@ class SQLCompiler(Compiled): return "" def for_update_clause(self, select): - if select.for_update: + # 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 "" diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 708103fc6..99b2fbefc 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -2786,4 +2786,3 @@ class AnnotatedFromClause(Annotated): Annotated.__init__(self, element, values) - |
