Skip to content

Commit

Permalink
added LockmodeArgs
Browse files Browse the repository at this point in the history
  • Loading branch information
mlassnig committed Nov 28, 2013
1 parent 741da87 commit e9aaf8e
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 70 deletions.
9 changes: 8 additions & 1 deletion lib/sqlalchemy/dialects/mysql/base.py
Expand Up @@ -1422,7 +1422,14 @@ def visit_join(self, join, asfrom=False, **kwargs):
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)
Expand Down
22 changes: 16 additions & 6 deletions lib/sqlalchemy/dialects/oracle/base.py
Expand Up @@ -664,14 +664,24 @@ def for_update_clause(self, select):

tmp = ' FOR UPDATE'

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)
# 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 == 'nowait':
if select.for_update.mode == 'update_nowait':
return tmp + ' NOWAIT'
elif select.for_update:
elif select.for_update.mode == 'update':
return tmp
else:
return super(OracleCompiler, self).for_update_clause(select)
Expand Down
30 changes: 21 additions & 9 deletions lib/sqlalchemy/dialects/postgresql/base.py
Expand Up @@ -1015,20 +1015,32 @@ def get_select_precolumns(self, select):

def for_update_clause(self, select):

if select.for_update == 'read':
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 == 'read_nowait':
elif select.for_update.mode == 'read_nowait':
return ' FOR SHARE NOWAIT'

tmp = ' FOR UPDATE'
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 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 == 'nowait':
if select.for_update.mode == 'update_nowait':
return tmp + ' NOWAIT'
elif select.for_update:
elif select.for_update.mode == 'update':
return tmp
else:
return super(PGCompiler, self).for_update_clause(select)
Expand Down
134 changes: 84 additions & 50 deletions lib/sqlalchemy/orm/query.py
Expand Up @@ -70,7 +70,6 @@ class Query(object):
_criterion = None
_yield_per = None
_lockmode = None
_lockmode_of = None
_order_by = False
_group_by = False
_having = None
Expand Down Expand Up @@ -1129,49 +1128,38 @@ 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
:param of: either a column descriptor, or list of column
descriptors, representing the optional OF part of the
clause. This passes ``for_update_of=descriptor(s)'`` which
translates to ``FOR UPDATE OF table [NOWAIT]`` respectively
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.0
.. versionadded:: 0.9.0b2
"""

self._lockmode = mode

# do not drag the ORM layer into the dialect,
# we only need the table name and column name
if isinstance(of, attributes.QueryableAttribute):
self._lockmode_of = (of.expression.table.name,
of.expression.name)
elif isinstance(of, (tuple, list)):
self._lockmode_of = [(o.expression.table.name,
o.expression.name) for o in of]
elif of is not None:
raise TypeError('OF parameter is not a column(list)')
self._lockmode = LockmodeArgs(mode=mode, of=of)

@_generative()
def params(self, *args, **kwargs):
Expand Down Expand Up @@ -2704,13 +2692,6 @@ def update(self, values, synchronize_session='evaluate'):
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)
Expand All @@ -2720,14 +2701,12 @@ def _compile_context(self, labels=True):

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 self._lockmode_of is not None:
context.for_update_of = self._lockmode_of
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)
Expand Down Expand Up @@ -2813,7 +2792,6 @@ def _compound_eager_statement(self, context):
statement = sql.select(
[inner] + context.secondary_columns,
for_update=context.for_update,
for_update_of=context.for_update_of,
use_labels=context.labels)

from_clause = inner
Expand Down Expand Up @@ -2859,7 +2837,6 @@ def _simple_statement(self, context):
from_obj=context.froms,
use_labels=context.labels,
for_update=context.for_update,
for_update_of=context.for_update_of,
order_by=context.order_by,
**self._select_args
)
Expand Down Expand Up @@ -3435,13 +3412,11 @@ def __str__(self):
return str(self.column)



class QueryContext(object):
multi_row_eager_loaders = False
adapter = None
froms = ()
for_update = False
for_update_of = None
for_update = None

def __init__(self, query):

Expand Down Expand Up @@ -3516,3 +3491,62 @@ def process_query(self, query):
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]
7 changes: 6 additions & 1 deletion lib/sqlalchemy/sql/compiler.py
Expand Up @@ -1570,7 +1570,12 @@ def order_by_clause(self, select, **kw):
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 ""
Expand Down
3 changes: 0 additions & 3 deletions lib/sqlalchemy/sql/selectable.py
Expand Up @@ -1162,7 +1162,6 @@ class SelectBase(Executable, FromClause):
def __init__(self,
use_labels=False,
for_update=False,
for_update_of=None,
limit=None,
offset=None,
order_by=None,
Expand All @@ -1171,7 +1170,6 @@ def __init__(self,
autocommit=None):
self.use_labels = use_labels
self.for_update = for_update
self.for_update_of = for_update_of
if autocommit is not None:
util.warn_deprecated('autocommit on select() is '
'deprecated. Use .execution_options(a'
Expand Down Expand Up @@ -2787,4 +2785,3 @@ def __init__(self, element, values):
Annotated.__init__(self, element, values)



0 comments on commit e9aaf8e

Please sign in to comment.