Fixed #27062 -- Eased implementing select_for_update() on MSSQL.
This commit is contained in:
parent
ef021412d5
commit
bae64dd0f1
|
@ -229,6 +229,9 @@ class BaseDatabaseFeatures(object):
|
||||||
# be equal?
|
# be equal?
|
||||||
ignores_quoted_identifier_case = False
|
ignores_quoted_identifier_case = False
|
||||||
|
|
||||||
|
# Place FOR UPDATE right after FROM clause. Used on MSSQL.
|
||||||
|
for_update_after_from = False
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
|
|
|
@ -402,6 +402,25 @@ class SQLCompiler(object):
|
||||||
result.extend(from_)
|
result.extend(from_)
|
||||||
params.extend(f_params)
|
params.extend(f_params)
|
||||||
|
|
||||||
|
for_update_part = None
|
||||||
|
if self.query.select_for_update and self.connection.features.has_select_for_update:
|
||||||
|
if self.connection.get_autocommit():
|
||||||
|
raise TransactionManagementError("select_for_update cannot be used outside of a transaction.")
|
||||||
|
|
||||||
|
nowait = self.query.select_for_update_nowait
|
||||||
|
skip_locked = self.query.select_for_update_skip_locked
|
||||||
|
# If it's a NOWAIT/SKIP LOCKED query but the backend doesn't
|
||||||
|
# support it, raise a DatabaseError to prevent a possible
|
||||||
|
# deadlock.
|
||||||
|
if nowait and not self.connection.features.has_select_for_update_nowait:
|
||||||
|
raise DatabaseError('NOWAIT is not supported on this database backend.')
|
||||||
|
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.')
|
||||||
|
for_update_part = self.connection.ops.for_update_sql(nowait=nowait, skip_locked=skip_locked)
|
||||||
|
|
||||||
|
if for_update_part and self.connection.features.for_update_after_from:
|
||||||
|
result.append(for_update_part)
|
||||||
|
|
||||||
if where:
|
if where:
|
||||||
result.append('WHERE %s' % where)
|
result.append('WHERE %s' % where)
|
||||||
params.extend(w_params)
|
params.extend(w_params)
|
||||||
|
@ -439,22 +458,8 @@ class SQLCompiler(object):
|
||||||
result.append('LIMIT %d' % val)
|
result.append('LIMIT %d' % val)
|
||||||
result.append('OFFSET %d' % self.query.low_mark)
|
result.append('OFFSET %d' % self.query.low_mark)
|
||||||
|
|
||||||
if self.query.select_for_update and self.connection.features.has_select_for_update:
|
if for_update_part and not self.connection.features.for_update_after_from:
|
||||||
if self.connection.get_autocommit():
|
result.append(for_update_part)
|
||||||
raise TransactionManagementError(
|
|
||||||
"select_for_update cannot be used outside of a transaction."
|
|
||||||
)
|
|
||||||
|
|
||||||
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.')
|
|
||||||
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)
|
return ' '.join(result), tuple(params)
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -5,9 +5,11 @@ import time
|
||||||
|
|
||||||
from multiple_database.routers import TestRouter
|
from multiple_database.routers import TestRouter
|
||||||
|
|
||||||
from django.db import DatabaseError, connection, router, transaction
|
from django.db import (
|
||||||
|
DatabaseError, connection, connections, router, transaction,
|
||||||
|
)
|
||||||
from django.test import (
|
from django.test import (
|
||||||
TransactionTestCase, override_settings, skipIfDBFeature,
|
TransactionTestCase, mock, override_settings, skipIfDBFeature,
|
||||||
skipUnlessDBFeature,
|
skipUnlessDBFeature,
|
||||||
)
|
)
|
||||||
from django.test.utils import CaptureQueriesContext
|
from django.test.utils import CaptureQueriesContext
|
||||||
|
@ -150,6 +152,14 @@ class SelectForUpdateTests(TransactionTestCase):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
Person.objects.select_for_update(skip_locked=True).get()
|
Person.objects.select_for_update(skip_locked=True).get()
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('has_select_for_update')
|
||||||
|
def test_for_update_after_from(self):
|
||||||
|
features_class = connections['default'].features.__class__
|
||||||
|
attribute_to_patch = "%s.%s.for_update_after_from" % (features_class.__module__, features_class.__name__)
|
||||||
|
with mock.patch(attribute_to_patch, return_value=True):
|
||||||
|
with transaction.atomic():
|
||||||
|
self.assertIn('FOR UPDATE WHERE', str(Person.objects.filter(name='foo').select_for_update().query))
|
||||||
|
|
||||||
@skipUnlessDBFeature('has_select_for_update')
|
@skipUnlessDBFeature('has_select_for_update')
|
||||||
def test_for_update_requires_transaction(self):
|
def test_for_update_requires_transaction(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue