From 695d4dd7908ca32e118716b474c23b43727579d2 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 7 Apr 2017 14:08:07 +0200 Subject: [PATCH] Fixed #23147 -- Disabled a limit/offset on a query with select_for_update on Oracle. Thanks Shai Berger and Tim Graham for the reviews. --- django/db/backends/oracle/compiler.py | 6 ++++++ django/db/models/sql/compiler.py | 7 ++++++- tests/select_for_update/tests.py | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/django/db/backends/oracle/compiler.py b/django/db/backends/oracle/compiler.py index 74c81be7080..52c2876fa2d 100644 --- a/django/db/backends/oracle/compiler.py +++ b/django/db/backends/oracle/compiler.py @@ -1,3 +1,4 @@ +from django.db import NotSupportedError from django.db.models.sql import compiler @@ -17,6 +18,11 @@ class SQLCompiler(compiler.SQLCompiler): do_offset = with_limits and (self.query.high_mark is not None or self.query.low_mark) if not do_offset: sql, params = super().as_sql(with_limits=False, with_col_aliases=with_col_aliases) + elif not self.connection.features.supports_select_for_update_with_limit and self.query.select_for_update: + raise NotSupportedError( + 'LIMIT/OFFSET not supported with select_for_update on this ' + 'database backend.' + ) else: sql, params = super().as_sql(with_limits=False, with_col_aliases=True) # Wrap the base query in an outer SELECT * with boundaries on diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index b274ba24aee..1fe19db4214 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -10,7 +10,7 @@ from django.db.models.sql.constants import ( ) from django.db.models.sql.query import Query, get_order_dir from django.db.transaction import TransactionManagementError -from django.db.utils import DatabaseError +from django.db.utils import DatabaseError, NotSupportedError FORCE = object() @@ -460,6 +460,11 @@ class SQLCompiler: if self.connection.get_autocommit(): raise TransactionManagementError('select_for_update cannot be used outside of a transaction.') + if with_limits and not self.connection.features.supports_select_for_update_with_limit: + raise NotSupportedError( + 'LIMIT/OFFSET not supported with select_for_update' + ' on this database backend.' + ) 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 diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index ac1d5b037e4..d2a32840c72 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -5,7 +5,8 @@ from unittest import mock from multiple_database.routers import TestRouter from django.db import ( - DatabaseError, connection, connections, router, transaction, + DatabaseError, NotSupportedError, connection, connections, router, + transaction, ) from django.test import ( TransactionTestCase, override_settings, skipIfDBFeature, @@ -179,6 +180,20 @@ class SelectForUpdateTests(TransactionTestCase): with self.assertRaises(transaction.TransactionManagementError): list(people) + @skipUnlessDBFeature('supports_select_for_update_with_limit') + def test_select_for_update_with_limit(self): + other = Person.objects.create(name='Grappeli') + with transaction.atomic(): + qs = list(Person.objects.all().order_by('pk').select_for_update()[1:2]) + self.assertEqual(qs[0], other) + + @skipIfDBFeature('supports_select_for_update_with_limit') + def test_unsupported_select_for_update_with_limit(self): + msg = 'LIMIT/OFFSET not supported with select_for_update on this database backend.' + with self.assertRaisesMessage(NotSupportedError, msg): + with transaction.atomic(): + list(Person.objects.all().order_by('pk').select_for_update()[1:2]) + def run_select_for_update(self, status, **kwargs): """ Utility method that runs a SELECT FOR UPDATE against all