summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2016-06-23 11:52:14 -0400
committerSimon Charette <charette.s@gmail.com>2016-08-08 12:01:43 -0400
commitb8e6e1b43b0bccec178dcc6708c5c8295abfb2e5 (patch)
treef3c146f02ecf6b0181d790cc67d83ad53cd629f9 /django
parent46509cf13dbf049f75077981c29ef2c60b5a96ab (diff)
Fixed #26500 -- Added SKIP LOCKED support to select_for_update().
Thanks Tim for the review.
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/features.py1
-rw-r--r--django/db/backends/base/operations.py4
-rw-r--r--django/db/backends/oracle/features.py1
-rw-r--r--django/db/backends/postgresql/features.py5
-rw-r--r--django/db/models/query.py5
-rw-r--r--django/db/models/sql/compiler.py11
-rw-r--r--django/db/models/sql/query.py2
7 files changed, 23 insertions, 6 deletions
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 484b272294..9bb0ed6848 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -36,6 +36,7 @@ class BaseDatabaseFeatures(object):
allow_sliced_subqueries = True
has_select_for_update = False
has_select_for_update_nowait = False
+ has_select_for_update_skip_locked = False
supports_select_related = True
diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py
index 4e5814e58f..1ed11b178e 100644
--- a/django/db/backends/base/operations.py
+++ b/django/db/backends/base/operations.py
@@ -177,12 +177,14 @@ class BaseDatabaseOperations(object):
"""
return []
- def for_update_sql(self, nowait=False):
+ def for_update_sql(self, nowait=False, skip_locked=False):
"""
Returns the FOR UPDATE SQL clause to lock rows for an update operation.
"""
if nowait:
return 'FOR UPDATE NOWAIT'
+ elif skip_locked:
+ return 'FOR UPDATE SKIP LOCKED'
else:
return 'FOR UPDATE'
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py
index 84e18cf9ba..956f7a051b 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -13,6 +13,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
uses_savepoints = True
has_select_for_update = True
has_select_for_update_nowait = True
+ has_select_for_update_skip_locked = True
can_return_id_from_insert = True
allow_sliced_subqueries = False
supports_subqueries_in_group_by = False
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index 218a7645de..a9e1c77480 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -1,5 +1,6 @@
from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.utils import InterfaceError
+from django.utils.functional import cached_property
class DatabaseFeatures(BaseDatabaseFeatures):
@@ -31,3 +32,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
greatest_least_ignores_nulls = True
can_clone_databases = True
supports_temporal_subtraction = True
+
+ @cached_property
+ def has_select_for_update_skip_locked(self):
+ return self.connection.pg_version >= 90500
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 07f1fc414f..0a0965fe47 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -835,15 +835,18 @@ class QuerySet(object):
else:
return self._filter_or_exclude(None, **filter_obj)
- def select_for_update(self, nowait=False):
+ def select_for_update(self, nowait=False, skip_locked=False):
"""
Returns a new QuerySet instance that will select objects with a
FOR UPDATE lock.
"""
+ if nowait and skip_locked:
+ raise ValueError('The nowait option cannot be used with skip_locked.')
obj = self._clone()
obj._for_write = True
obj.query.select_for_update = True
obj.query.select_for_update_nowait = nowait
+ obj.query.select_for_update_skip_locked = skip_locked
return obj
def select_related(self, *fields):
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index cedb140143..5eaac1fc74 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -445,13 +445,16 @@ class SQLCompiler(object):
"select_for_update cannot be used outside of a transaction."
)
- # If we've been asked for a NOWAIT query but the backend does
- # not support it, raise a DatabaseError otherwise we could get
- # an unexpected deadlock.
nowait = self.query.select_for_update_nowait
+ skip_locked = self.query.select_for_update_skip_locked
+ # If we've been asked for a NOWAIT/SKIP LOCKED query but the
+ # backend does not support it, raise a DatabaseError otherwise
+ # we could get an unexpected deadlock.
if nowait and not self.connection.features.has_select_for_update_nowait:
raise DatabaseError('NOWAIT is not supported on this database backend.')
- result.append(self.connection.ops.for_update_sql(nowait=nowait))
+ elif skip_locked and not self.connection.features.has_select_for_update_skip_locked:
+ raise DatabaseError('SKIP LOCKED is not supported on this database backend.')
+ result.append(self.connection.ops.for_update_sql(nowait=nowait, skip_locked=skip_locked))
return ' '.join(result), tuple(params)
finally:
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index d182242ce4..426087f8af 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -167,6 +167,7 @@ class Query(object):
self.distinct_fields = []
self.select_for_update = False
self.select_for_update_nowait = False
+ self.select_for_update_skip_locked = False
self.select_related = False
# Arbitrary limit for select_related to prevents infinite recursion.
@@ -286,6 +287,7 @@ class Query(object):
obj.distinct_fields = self.distinct_fields[:]
obj.select_for_update = self.select_for_update
obj.select_for_update_nowait = self.select_for_update_nowait
+ obj.select_for_update_skip_locked = self.select_for_update_skip_locked
obj.select_related = self.select_related
obj.values_select = self.values_select[:]
obj._annotations = self._annotations.copy() if self._annotations is not None else None