From 7355fa6b728357a3e7257fbe1175d68a0d7e2ec1 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sat, 23 Feb 2008 01:34:49 +0000 Subject: [PATCH] queryset-refactor: Implemented slicing to end of querysets. Refs #2150, #5012. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7147 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/__init__.py | 8 ++++++++ django/db/backends/mysql/base.py | 4 ++++ django/db/backends/mysql_old/base.py | 4 ++++ django/db/backends/postgresql/operations.py | 3 +++ django/db/backends/sqlite3/base.py | 3 +++ django/db/models/sql/query.py | 6 +++++- docs/db-api.txt | 8 ++++++++ tests/modeltests/basic/models.py | 8 +++----- 8 files changed, 38 insertions(+), 6 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index a4c5a25589..6ba06f083a 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -183,6 +183,14 @@ class BaseDatabaseOperations(object): """ return None + def no_limit_value(self): + """ + Returns the value to use for the LIMIT when we are wanting "LIMIT + infinity". Returns None if the limit clause can be omitted in this case. + """ + # FIXME: API may need to change once Oracle backend is repaired. + raise NotImplementedError() + def pk_default_value(self): """ Returns the value to use during an INSERT statement to specify that diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 697dcd3a67..c2b2c7ab1c 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -93,6 +93,10 @@ class DatabaseOperations(BaseDatabaseOperations): sql += "%s," % offset return sql + str(limit) + def no_limit_value(self): + # 2**64 - 1, as recommended by the MySQL documentation + return 18446744073709551615L + def quote_name(self, name): if name.startswith("`") and name.endswith("`"): return name # Quoting once is enough. diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py index c22094b968..374e9af1f5 100644 --- a/django/db/backends/mysql_old/base.py +++ b/django/db/backends/mysql_old/base.py @@ -98,6 +98,10 @@ class DatabaseOperations(BaseDatabaseOperations): sql += "%s," % offset return sql + str(limit) + def no_limit_value(self): + # 2**64 - 1, as recommended by the MySQL documentation + return 18446744073709551615L + def quote_name(self, name): if name.startswith("`") and name.endswith("`"): return name # Quoting once is enough. diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 20392b238e..d2c65a8753 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -31,6 +31,9 @@ class DatabaseOperations(BaseDatabaseOperations): cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)) return cursor.fetchone()[0] + def no_limit_value(self): + return None + def quote_name(self, name): if name.startswith('"') and name.endswith('"'): return name # Quoting once is enough. diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index b4b445cd16..b8bf5c8f0b 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -63,6 +63,9 @@ class DatabaseOperations(BaseDatabaseOperations): return name # Quoting once is enough. return '"%s"' % name + def no_limit_value(self): + return -1 + def sql_flush(self, style, tables, sequences): # NB: The generated SQL below is specific to SQLite # Note: The DELETE FROM... SQL generated below works for SQLite databases diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 20dcb4b71d..f251730c48 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -262,11 +262,15 @@ class Query(object): if ordering: result.append('ORDER BY %s' % ', '.join(ordering)) + # FIXME: Pull this out to make life easier for Oracle et al. if with_limits: if self.high_mark: result.append('LIMIT %d' % (self.high_mark - self.low_mark)) if self.low_mark: - assert self.high_mark, "'offset' is not allowed without 'limit'" + if not self.high_mark: + val = self.connection.ops.no_limit_value() + if val: + result.append('LIMIT %d' % val) result.append('OFFSET %d' % self.low_mark) params.extend(self.extra_params) diff --git a/docs/db-api.txt b/docs/db-api.txt index 887a06a67e..d96541d58b 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -422,6 +422,14 @@ This returns the sixth through tenth objects (``OFFSET 5 LIMIT 5``):: Entry.objects.all()[5:10] +You can also slice from the item ''N'' to the end of the queryset. For +example, to return everything from the fixth item onwards:: + + Entry.objects.all()[5:] + +How this last example is implemented in SQL varies depending upon the database +used, but it is supported in all cases. + Generally, slicing a ``QuerySet`` returns a new ``QuerySet`` -- it doesn't evaluate the query. An exception is if you use the "step" parameter of Python slice syntax. For example, this would actually execute the query in order to diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 557331a36e..51de8a50f8 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -292,11 +292,9 @@ datetime.datetime(2005, 7, 28, 0, 0) >>> Article.objects.all()[2:][2:3] [] -# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work: ->>> Article.objects.all()[2:] -Traceback (most recent call last): - ... -AssertionError: 'offset' is not allowed without 'limit' +# Using an offset without a limit is also possible. +>>> Article.objects.all()[5:] +[, , ] # Also, once you have sliced you can't filter, re-order or combine >>> Article.objects.all()[0:5].filter(id=1)