diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index 62ea5c4..0b2a77e 100644
a
|
b
|
class DatabaseCache(BaseDatabaseCache):
|
167 | 167 | num = cursor.fetchone()[0] |
168 | 168 | if num > self._max_entries: |
169 | 169 | cull_num = num / self._cull_frequency |
170 | | if connections[db].vendor == 'oracle': |
171 | | # Oracle doesn't support LIMIT + OFFSET |
172 | | cursor.execute("""SELECT cache_key FROM |
173 | | (SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, cache_key FROM %s) |
174 | | WHERE counter > %%s AND COUNTER <= %%s""" % table, [cull_num, cull_num + 1]) |
175 | | else: |
176 | | # This isn't standard SQL, it's likely to break |
177 | | # with some non officially supported databases |
178 | | cursor.execute("SELECT cache_key FROM %s " |
179 | | "ORDER BY cache_key " |
180 | | "LIMIT 1 OFFSET %%s" % table, [cull_num]) |
| 170 | |
| 171 | sql, params = connections[db].ops.raw_limit_offset_select( |
| 172 | fields='cache_key', |
| 173 | table=table, |
| 174 | order_by='cache_key', |
| 175 | limit=1, |
| 176 | offset=cull_num, |
| 177 | ) |
| 178 | cursor.execute(sql, params) |
| 179 | |
181 | 180 | cursor.execute("DELETE FROM %s " |
182 | 181 | "WHERE cache_key < %%s" % table, |
183 | 182 | [cursor.fetchone()[0]]) |
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 2762350..15926e2 100644
a
|
b
|
class BaseDatabaseOperations(object):
|
874 | 874 | conn = ' %s ' % connector |
875 | 875 | return conn.join(sub_expressions) |
876 | 876 | |
| 877 | def raw_limit_offset_select(self, fields, table, limit, offset=None, where=None, order_by=None): |
| 878 | """ |
| 879 | Returns a two element tuple with the raw SQL string to do a simple |
| 880 | select with limit + offset and a list of parameters to provide to the |
| 881 | cursor with the SQL. |
| 882 | |
| 883 | It is the caller's responsibilty to properly quote any entities in |
| 884 | `fields`, `table`, and `order_by`. |
| 885 | """ |
| 886 | params = [] |
| 887 | sql = "SELECT %s FROM %s" % (fields, table) |
| 888 | if where: |
| 889 | sql += " WHERE %s" % where |
| 890 | if order_by: |
| 891 | sql += " ORDER BY %s " % order_by |
| 892 | if limit: |
| 893 | sql += " LIMIT %s" |
| 894 | params.append(limit) |
| 895 | if offset: |
| 896 | sql += " OFFSET %s" |
| 897 | params.append(offset) |
| 898 | return sql, params |
| 899 | |
877 | 900 | class BaseDatabaseIntrospection(object): |
878 | 901 | """ |
879 | 902 | This class encapsulates all backend-specific introspection utilities |
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index 2f3a43d..dd0b0ff 100644
a
|
b
|
WHEN (new.%(col_name)s IS NULL)
|
389 | 389 | items_sql = "SELECT %s FROM DUAL" % ", ".join(["%s"] * len(fields)) |
390 | 390 | return " UNION ALL ".join([items_sql] * num_values) |
391 | 391 | |
| 392 | def raw_limit_offset_select(self, fields, table, limit, offset=None, where=None, order_by=None): |
| 393 | sql = "SELECT %s FROM " \ |
| 394 | "(SELECT ROW_NUMBER() OVER (ORDER BY cache_key) AS counter, %s" \ |
| 395 | " FROM %s)" % (fields, fields, table) |
| 396 | where_parts, params = [], [] |
| 397 | if where: |
| 398 | where_parts.append('(%s)' % where) |
| 399 | if offset is not None: |
| 400 | where_parts.append('counter > %s') |
| 401 | params.append(offset) |
| 402 | if limit is not None: |
| 403 | where_parts.append('counter <= %s') |
| 404 | params.append((offset + limit) if offset else limit) |
| 405 | if where_parts: |
| 406 | sql += ' WHERE %s' % ' AND '.join(where_parts) |
| 407 | return sql, params |
392 | 408 | |
393 | 409 | class _UninitializedOperatorsDescriptor(object): |
394 | 410 | |
diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py
index 36a54c0..676be2f 100644
a
|
b
|
from decimal import Decimal
|
6 | 6 | from operator import attrgetter |
7 | 7 | |
8 | 8 | from django.core.exceptions import FieldError |
| 9 | from django.db import connection |
9 | 10 | from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F, Q |
10 | 11 | from django.test import TestCase, Approximate, skipUnlessDBFeature |
11 | 12 | |
… |
… |
class AggregationTests(TestCase):
|
66 | 67 | Regression test for #11916: Extra params + aggregation creates |
67 | 68 | incorrect SQL. |
68 | 69 | """ |
69 | | #oracle doesn't support subqueries in group by clause |
70 | | shortest_book_sql = """ |
71 | | SELECT name |
72 | | FROM aggregation_regress_book b |
73 | | WHERE b.publisher_id = aggregation_regress_publisher.id |
74 | | ORDER BY b.pages |
75 | | LIMIT 1 |
76 | | """ |
| 70 | qn = connection.ops.quote_name |
| 71 | sql, params = connection.ops.raw_limit_offset_select( |
| 72 | fields=qn('name'), |
| 73 | table=qn('aggregation_regress_book'), |
| 74 | where='%s = %s.%s' % (qn('publisher_id'), qn('aggregation_regress_publisher'), qn('id')), |
| 75 | order_by=qn('pages'), |
| 76 | limit=1, |
| 77 | ) |
| 78 | shortest_book_sql = sql % params[0] |
77 | 79 | # tests that this query does not raise a DatabaseError due to the full |
78 | 80 | # subselect being (erroneously) added to the GROUP BY parameters |
79 | 81 | qs = Publisher.objects.extra(select={ |