Code

Ticket #18330: django-ticket18330.diff

File django-ticket18330.diff, 5.6 KB (added by manfre, 2 years ago)

Adds raw_limit_offset_select with better documentation and more flexibility for limit and offset.

Line 
1diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
2index 62ea5c4..0b2a77e 100644
3--- a/django/core/cache/backends/db.py
4+++ b/django/core/cache/backends/db.py
5@@ -167,17 +167,16 @@ class DatabaseCache(BaseDatabaseCache):
6             num = cursor.fetchone()[0]
7             if num > self._max_entries:
8                 cull_num = num / self._cull_frequency
9-                if connections[db].vendor == 'oracle':
10-                    # Oracle doesn't support LIMIT + OFFSET
11-                    cursor.execute("""SELECT cache_key FROM
12-(SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, cache_key FROM %s)
13-WHERE counter > %%s AND COUNTER <= %%s""" % table, [cull_num, cull_num + 1])
14-                else:
15-                    # This isn't standard SQL, it's likely to break
16-                    # with some non officially supported databases
17-                    cursor.execute("SELECT cache_key FROM %s "
18-                                   "ORDER BY cache_key "
19-                                   "LIMIT 1 OFFSET %%s" % table, [cull_num])
20+
21+                sql, params = connections[db].ops.raw_limit_offset_select(
22+                    fields='cache_key',
23+                    table=table,
24+                    order_by='cache_key',
25+                    limit=1,
26+                    offset=cull_num,
27+                )
28+                cursor.execute(sql, params)
29+
30                 cursor.execute("DELETE FROM %s "
31                                "WHERE cache_key < %%s" % table,
32                                [cursor.fetchone()[0]])
33diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
34index 2762350..15926e2 100644
35--- a/django/db/backends/__init__.py
36+++ b/django/db/backends/__init__.py
37@@ -874,6 +874,29 @@ class BaseDatabaseOperations(object):
38         conn = ' %s ' % connector
39         return conn.join(sub_expressions)
40 
41+    def raw_limit_offset_select(self, fields, table, limit, offset=None, where=None, order_by=None):
42+        """
43+        Returns a two element tuple with the raw SQL string to do a simple
44+        select with limit + offset and a list of parameters to provide to the
45+        cursor with the SQL.
46+
47+        It is the caller's responsibilty to properly quote any entities in
48+        `fields`, `table`, and `order_by`.
49+        """
50+        params = []
51+        sql = "SELECT %s FROM %s" % (fields, table)
52+        if where:
53+            sql += " WHERE %s" % where
54+        if order_by:
55+              sql += " ORDER BY %s " % order_by
56+        if limit:
57+            sql += " LIMIT %s"
58+            params.append(limit)
59+            if offset:
60+                sql += " OFFSET %s"
61+                params.append(offset)
62+        return sql, params
63+
64 class BaseDatabaseIntrospection(object):
65     """
66     This class encapsulates all backend-specific introspection utilities
67diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
68index 2f3a43d..dd0b0ff 100644
69--- a/django/db/backends/oracle/base.py
70+++ b/django/db/backends/oracle/base.py
71@@ -389,6 +389,22 @@ WHEN (new.%(col_name)s IS NULL)
72         items_sql = "SELECT %s FROM DUAL" % ", ".join(["%s"] * len(fields))
73         return " UNION ALL ".join([items_sql] * num_values)
74 
75+    def raw_limit_offset_select(self, fields, table, limit, offset=None, where=None, order_by=None):
76+        sql = "SELECT %s FROM " \
77+              "(SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, %s" \
78+              " FROM %s)" % (fields, fields, table)
79+        where_parts, params = [], []
80+        if where:
81+            where_parts.append('(%s)' % where)
82+        if offset is not None:
83+            where_parts.append('counter > %s')
84+            params.append(offset)
85+        if limit is not None:
86+            where_parts.append('counter <= %s')
87+            params.append((offset + limit) if offset else limit)
88+        if where_parts:
89+            sql += ' WHERE %s' % ' AND '.join(where_parts)
90+        return sql, params
91 
92 class _UninitializedOperatorsDescriptor(object):
93 
94diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py
95index 36a54c0..676be2f 100644
96--- a/tests/regressiontests/aggregation_regress/tests.py
97+++ b/tests/regressiontests/aggregation_regress/tests.py
98@@ -6,6 +6,7 @@ from decimal import Decimal
99 from operator import attrgetter
100 
101 from django.core.exceptions import FieldError
102+from django.db import connection
103 from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q
104 from django.test import TestCase, Approximate, skipUnlessDBFeature
105 
106@@ -66,14 +67,15 @@ class AggregationTests(TestCase):
107         Regression test for #11916: Extra params + aggregation creates
108         incorrect SQL.
109         """
110-        #oracle doesn't support subqueries in group by clause
111-        shortest_book_sql = """
112-        SELECT name
113-        FROM aggregation_regress_book b
114-        WHERE b.publisher_id = aggregation_regress_publisher.id
115-        ORDER BY b.pages
116-        LIMIT 1
117-        """
118+        qn = connection.ops.quote_name
119+        sql, params = connection.ops.raw_limit_offset_select(
120+            fields=qn('name'),
121+            table=qn('aggregation_regress_book'),
122+            where='%s = %s.%s' % (qn('publisher_id'), qn('aggregation_regress_publisher'), qn('id')),
123+            order_by=qn('pages'),
124+            limit=1,
125+        )
126+        shortest_book_sql = sql % params[0]
127         # tests that this query does not raise a DatabaseError due to the full
128         # subselect being (erroneously) added to the GROUP BY parameters
129         qs = Publisher.objects.extra(select={