diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2011-04-20 20:42:07 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2011-04-20 20:42:07 +0000 |
| commit | 8f0f73c7b8b110489a1a127cc47e3cabb0eea646 (patch) | |
| tree | 7a1376e9031d6d56037f26be29e2e60310c1941f /django | |
| parent | 99c179442782ea83c52452296d5769950f23a75a (diff) | |
Fixed #2705: added a `select_for_update()` clause to querysets.
A number of people worked on this patch over the years -- Hawkeye, Colin Grady,
KBS, sakyamuni, anih, jdemoor, and Issak Kelly. Thanks to them all, and
apologies if I missed anyone.
Special thanks to Dan Fairs for picking it up again at the end and seeing this
through to commit.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16058 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/__init__.py | 11 | ||||
| -rw-r--r-- | django/db/backends/mysql/base.py | 2 | ||||
| -rw-r--r-- | django/db/backends/oracle/base.py | 2 | ||||
| -rw-r--r-- | django/db/backends/postgresql_psycopg2/base.py | 3 | ||||
| -rw-r--r-- | django/db/models/manager.py | 3 | ||||
| -rw-r--r-- | django/db/models/query.py | 13 | ||||
| -rw-r--r-- | django/db/models/sql/compiler.py | 15 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 5 |
8 files changed, 54 insertions, 0 deletions
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 4b845d5135..b64fb01b03 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -279,6 +279,8 @@ class BaseDatabaseFeatures(object): # integer primary keys. related_fields_match_type = False allow_sliced_subqueries = True + has_select_for_update = False + has_select_for_update_nowait = False # Does the default test database allow multiple connections? # Usually an indication that the test database is in-memory @@ -476,6 +478,15 @@ class BaseDatabaseOperations(object): """ return [] + def for_update_sql(self, nowait=False): + """ + Returns the FOR UPDATE SQL clause to lock rows for an update operation. + """ + if nowait: + return 'FOR UPDATE NOWAIT' + else: + return 'FOR UPDATE' + def fulltext_search_sql(self, field_name): """ Returns the SQL WHERE clause to use in order to perform a full-text diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 630b9805c3..16067c6807 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -124,6 +124,8 @@ class DatabaseFeatures(BaseDatabaseFeatures): allows_group_by_pk = True related_fields_match_type = True allow_sliced_subqueries = False + has_select_for_update = True + has_select_for_update_nowait = False supports_forward_references = False supports_long_model_names = False supports_microsecond_precision = False diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index dc70a1018c..bcb906762b 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -70,6 +70,8 @@ class DatabaseFeatures(BaseDatabaseFeatures): needs_datetime_string_cast = False interprets_empty_strings_as_nulls = True uses_savepoints = True + has_select_for_update = True + has_select_for_update_nowait = True can_return_id_from_insert = True allow_sliced_subqueries = False supports_subqueries_in_group_by = False diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index e0990a1089..67e28774fe 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -70,6 +70,9 @@ class DatabaseFeatures(BaseDatabaseFeatures): requires_rollback_on_dirty_transaction = True has_real_datatype = True can_defer_constraint_checks = True + has_select_for_update = True + has_select_for_update_nowait = True + class DatabaseWrapper(BaseDatabaseWrapper): vendor = 'postgresql' diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 9528d66812..4fa4c4a52d 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -164,6 +164,9 @@ class Manager(object): def order_by(self, *args, **kwargs): return self.get_query_set().order_by(*args, **kwargs) + def select_for_update(self, *args, **kwargs): + return self.get_query_set().select_for_update(*args, **kwargs) + def select_related(self, *args, **kwargs): return self.get_query_set().select_related(*args, **kwargs) diff --git a/django/db/models/query.py b/django/db/models/query.py index fbd391971f..8e33765fdf 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -435,6 +435,7 @@ class QuerySet(object): del_query._for_write = True # Disable non-supported fields. + del_query.query.select_for_update = False del_query.query.select_related = False del_query.query.clear_ordering() @@ -583,6 +584,18 @@ class QuerySet(object): else: return self._filter_or_exclude(None, **filter_obj) + def select_for_update(self, **kwargs): + """ + Returns a new QuerySet instance that will select objects with a + FOR UPDATE lock. + """ + # Default to false for nowait + nowait = kwargs.pop('nowait', False) + obj = self._clone() + obj.query.select_for_update = True + obj.query.select_for_update_nowait = nowait + return obj + def select_related(self, *fields, **kwargs): """ Returns a new QuerySet instance that will select related objects. diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index d425c8b32b..ace9cf4c88 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1,11 +1,13 @@ from django.core.exceptions import FieldError from django.db import connections +from django.db import transaction from django.db.backends.util import truncate_name from django.db.models.sql.constants import * from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.query import get_proxied_model, get_order_dir, \ select_related_descend, Query +from django.db.utils import DatabaseError class SQLCompiler(object): def __init__(self, query, connection, using): @@ -117,6 +119,14 @@ class SQLCompiler(object): result.append('LIMIT %d' % val) result.append('OFFSET %d' % self.query.low_mark) + if self.query.select_for_update and self.connection.features.has_select_for_update: + # 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 + 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)) + return ' '.join(result), tuple(params) def as_nested_sql(self): @@ -677,6 +687,11 @@ class SQLCompiler(object): resolve_columns = hasattr(self, 'resolve_columns') fields = None has_aggregate_select = bool(self.query.aggregate_select) + # Set transaction dirty if we're using SELECT FOR UPDATE to ensure + # a subsequent commit/rollback is executed, so any database locks + # are released. + if self.query.select_for_update and transaction.is_managed(self.using): + transaction.set_dirty(self.using) for rows in self.execute_sql(MULTI): for row in rows: if resolve_columns: diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 59f7bcbf14..f41272cf0f 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -125,6 +125,8 @@ class Query(object): self.order_by = [] self.low_mark, self.high_mark = 0, None # Used for offset/limit self.distinct = False + self.select_for_update = False + self.select_for_update_nowait = False self.select_related = False self.related_select_cols = [] @@ -254,6 +256,8 @@ class Query(object): obj.order_by = self.order_by[:] obj.low_mark, obj.high_mark = self.low_mark, self.high_mark obj.distinct = self.distinct + obj.select_for_update = self.select_for_update + obj.select_for_update_nowait = self.select_for_update_nowait obj.select_related = self.select_related obj.related_select_cols = [] obj.aggregates = copy.deepcopy(self.aggregates, memo=memo) @@ -360,6 +364,7 @@ class Query(object): query.clear_ordering(True) query.clear_limits() + query.select_for_update = False query.select_related = False query.related_select_cols = [] query.related_select_fields = [] |
