]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add SKIP LOCKED support for Postgresql, Oracle
authorJack Zhou <univerio@gmail.com>
Tue, 31 May 2016 14:01:46 +0000 (10:01 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Jun 2016 21:46:16 +0000 (17:46 -0400)
This adds `SELECT ... FOR UPDATE SKIP LOCKED`/
`SELECT ... FOR SHARE SKIP LOCKED` rendering.

Change-Id: Id1dc4f1cafc1de23f397a6f73d54ab2c58d5910d
Pull-request: https://bitbucket.org/zzzeek/sqlalchemy/pull-requests/86

doc/build/changelog/changelog_11.rst
doc/build/changelog/migration_11.rst
lib/sqlalchemy/dialects/oracle/base.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql/selectable.py
test/dialect/postgresql/test_compiler.py
test/dialect/test_oracle.py
test/orm/test_lockmode.py

index 898b8a0ba32e70d87ad6e93a0ce2b6afc3ef13bb..0f396ec394a262ef25e6923f55f654f3892551e9 100644 (file)
         objects present in the constraints collection.  Pull request courtesy
         Alex Grönholm.
 
+    .. change::
+        :tags: feature, postgresql, oracle
+        :pullreq: bitbucket:86
+
+        Added new parameter
+        :paramref:`.GenerativeSelect.with_for_update.skip_locked`, which
+        will render the ``SKIP LOCKED`` phrase for a ``FOR UPDATE`` or
+        ``FOR SHARE`` lock on the Postgresql and Oracle backends.  Pull
+        request courtesy Jack Zhou.
+
     .. change::
         :tags: change, orm
         :tickets: 3394
index 0d94f35e99f8c8de064ebb8a3d05818ed78f63db..bf37ec665e3e815be54baa1231bb62a8887bd3ef 100644 (file)
@@ -2072,6 +2072,14 @@ should be calling upon ``sqlalchemy.dialects.postgresql``.
 Engine URLs of the form ``postgres://`` will still continue to function,
 however.
 
+Support for SKIP LOCKED
+-----------------------
+
+The new parameter :paramref:`.GenerativeSelect.with_for_update.skip_locked`
+in both Core and ORM will generate the "SKIP LOCKED" suffix for a
+"SELECT...FOR UPDATE" or "SELECT.. FOR SHARE" query.
+
+
 Dialect Improvements and Changes - MySQL
 =============================================
 
@@ -2348,3 +2356,10 @@ on this behavior, set the flag back to True.
 
 Dialect Improvements and Changes - Oracle
 =============================================
+
+Support for SKIP LOCKED
+-----------------------
+
+The new parameter :paramref:`.GenerativeSelect.with_for_update.skip_locked`
+in both Core and ORM will generate the "SKIP LOCKED" suffix for a
+"SELECT...FOR UPDATE" or "SELECT.. FOR SHARE" query.
index 6992670c4f46e2c9ce72124f20dd722f13542ff3..493bf362fb1ba5a368974c4f2273d2f43cdf7fb2 100644 (file)
@@ -814,6 +814,8 @@ class OracleCompiler(compiler.SQLCompiler):
 
         if select._for_update_arg.nowait:
             tmp += " NOWAIT"
+        if select._for_update_arg.skip_locked:
+            tmp += " SKIP LOCKED"
 
         return tmp
 
index 136cb1b28ca126b52c60bb89c86c9eb416dd7141..1bc4409f288617b6469d284731ee24606fcaac86 100644 (file)
@@ -1185,6 +1185,8 @@ class PGCompiler(compiler.SQLCompiler):
 
         if select._for_update_arg.nowait:
             tmp += " NOWAIT"
+        if select._for_update_arg.skip_locked:
+            tmp += " SKIP LOCKED"
 
         return tmp
 
index 34daa707f15e44831924aeb1670eaf299b55f012..7fab3319763407f3b7c149ec67a3711341c3247c 100644 (file)
@@ -1397,7 +1397,8 @@ class Query(object):
         self._for_update_arg = LockmodeArg.parse_legacy_query(mode)
 
     @_generative()
-    def with_for_update(self, read=False, nowait=False, of=None):
+    def with_for_update(self, read=False, nowait=False, of=None,
+                        skip_locked=False):
         """return a new :class:`.Query` with the specified options for the
         ``FOR UPDATE`` clause.
 
@@ -1425,7 +1426,8 @@ class Query(object):
             full argument and behavioral description.
 
         """
-        self._for_update_arg = LockmodeArg(read=read, nowait=nowait, of=of)
+        self._for_update_arg = LockmodeArg(read=read, nowait=nowait, of=of,
+                                           skip_locked=skip_locked)
 
     @_generative()
     def params(self, *args, **kwargs):
index e299f067e8783e548983cc2ab7ae40d5fa8e11f4..bd1d04e57d9ce377c74ee3508672bf9966e59822 100644 (file)
@@ -1723,14 +1723,16 @@ class ForUpdateArg(ClauseElement):
         if self.of is not None:
             self.of = [clone(col, **kw) for col in self.of]
 
-    def __init__(self, nowait=False, read=False, of=None):
+    def __init__(self, nowait=False, read=False, of=None, skip_locked=False):
         """Represents arguments specified to :meth:`.Select.for_update`.
 
         .. versionadded:: 0.9.0
+
         """
 
         self.nowait = nowait
         self.read = read
+        self.skip_locked = skip_locked
         if of is not None:
             self.of = [_interpret_as_column_or_from(elem)
                        for elem in util.to_list(of)]
@@ -1873,7 +1875,8 @@ class GenerativeSelect(SelectBase):
         self._for_update_arg = ForUpdateArg.parse_legacy_select(value)
 
     @_generative
-    def with_for_update(self, nowait=False, read=False, of=None):
+    def with_for_update(self, nowait=False, read=False, of=None,
+                        skip_locked=False):
         """Specify a ``FOR UPDATE`` clause for this :class:`.GenerativeSelect`.
 
         E.g.::
@@ -1908,10 +1911,18 @@ class GenerativeSelect(SelectBase):
          and Oracle.  May render as a table or as a column depending on
          backend.
 
+        :param skip_locked: boolean, will render ``FOR UPDATE SKIP LOCKED``
+         on Oracle and Postgresql dialects or ``FOR SHARE SKIP LOCKED`` if
+         ``read=True`` is also specified.
+
+         .. versionadded:: 1.1.0
+
         .. versionadded:: 0.9.0
 
+
         """
-        self._for_update_arg = ForUpdateArg(nowait=nowait, read=read, of=of)
+        self._for_update_arg = ForUpdateArg(nowait=nowait, read=read, of=of,
+                                            skip_locked=skip_locked)
 
     @_generative
     def apply_labels(self):
index c20e48b01d52e17b9ad93abae64ac1f7980d577a..c061cfaf1c48eddc697a461c9081566d5ba99615 100644 (file)
@@ -605,6 +605,13 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             "SELECT mytable.myid, mytable.name, mytable.description "
             "FROM mytable WHERE mytable.myid = %(myid_1)s FOR UPDATE NOWAIT")
 
+        self.assert_compile(
+            table1.select(table1.c.myid == 7).
+            with_for_update(skip_locked=True),
+            "SELECT mytable.myid, mytable.name, mytable.description "
+            "FROM mytable WHERE mytable.myid = %(myid_1)s "
+            "FOR UPDATE SKIP LOCKED")
+
         self.assert_compile(
             table1.select(table1.c.myid == 7).with_for_update(read=True),
             "SELECT mytable.myid, mytable.name, mytable.description "
@@ -616,6 +623,13 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             "SELECT mytable.myid, mytable.name, mytable.description "
             "FROM mytable WHERE mytable.myid = %(myid_1)s FOR SHARE NOWAIT")
 
+        self.assert_compile(
+            table1.select(table1.c.myid == 7).
+            with_for_update(read=True, skip_locked=True),
+            "SELECT mytable.myid, mytable.name, mytable.description "
+            "FROM mytable WHERE mytable.myid = %(myid_1)s "
+            "FOR SHARE SKIP LOCKED")
+
         self.assert_compile(
             table1.select(table1.c.myid == 7).
             with_for_update(of=table1.c.myid),
@@ -645,6 +659,14 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             "FROM mytable WHERE mytable.myid = %(myid_1)s "
             "FOR SHARE OF mytable NOWAIT")
 
+        self.assert_compile(
+            table1.select(table1.c.myid == 7).
+            with_for_update(read=True, skip_locked=True,
+                            of=[table1.c.myid, table1.c.name]),
+            "SELECT mytable.myid, mytable.name, mytable.description "
+            "FROM mytable WHERE mytable.myid = %(myid_1)s "
+            "FOR SHARE OF mytable SKIP LOCKED")
+
         ta = table1.alias()
         self.assert_compile(
             ta.select(ta.c.myid == 7).
index 1f3e630406869c7e7715c3e2cdbd597cade1740e..8167412312d19c60b72a436530354d698e7f6cc2 100644 (file)
@@ -334,6 +334,13 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             "FROM mytable WHERE mytable.myid = :myid_1 FOR UPDATE OF "
             "mytable.myid, mytable.name NOWAIT")
 
+        self.assert_compile(
+            table1.select(table1.c.myid == 7).
+                with_for_update(skip_locked=True, of=[table1.c.myid, table1.c.name]),
+            "SELECT mytable.myid, mytable.name, mytable.description "
+            "FROM mytable WHERE mytable.myid = :myid_1 FOR UPDATE OF "
+            "mytable.myid, mytable.name SKIP LOCKED")
+
         ta = table1.alias()
         self.assert_compile(
             ta.select(ta.c.myid == 7).
index fc473a3297320e3b24f19778a972c10f11b88d5d..949fe0d81e7dd076ce9d563793af9ac37ce20200 100644 (file)
@@ -181,6 +181,15 @@ class CompileTest(_fixtures.FixtureTest, AssertsCompiledSQL):
             dialect="postgresql"
         )
 
+    def test_postgres_update_skip_locked(self):
+        User = self.classes.User
+        sess = Session()
+        self.assert_compile(sess.query(User.id).
+                with_for_update(skip_locked=True),
+            "SELECT users.id AS users_id FROM users FOR UPDATE SKIP LOCKED",
+            dialect="postgresql"
+        )
+
 
     def test_oracle_update(self):
         User = self.classes.User
@@ -190,6 +199,15 @@ class CompileTest(_fixtures.FixtureTest, AssertsCompiledSQL):
             dialect="oracle"
         )
 
+    def test_oracle_update_skip_locked(self):
+        User = self.classes.User
+        sess = Session()
+        self.assert_compile(sess.query(User.id)
+                .with_for_update(skip_locked=True),
+            "SELECT users.id AS users_id FROM users FOR UPDATE SKIP LOCKED",
+            dialect="oracle"
+        )
+
     def test_mysql_read(self):
         User = self.classes.User
         sess = Session()