From 9f07f27124540e27b4c3140235da375b974c4175 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 17 Mar 2020 22:00:28 -0400 Subject: [PATCH] Fixed #31376 -- Optimized nulls ordering when possible on SQLite and MySQL. Both backends order NULLs first on ascending ordering and last on descending ordering which makes ORDER BY IS (NOT)? NULL wasteful when asc(nulls_first) and desc(nulls_last) are used since it prevents indice usage. --- django/db/backends/base/features.py | 3 +++ django/db/backends/mysql/features.py | 1 + django/db/backends/sqlite3/features.py | 1 + django/db/models/expressions.py | 8 ++++++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 5760b59098..c2b92ecdee 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -91,6 +91,9 @@ class BaseDatabaseFeatures: # Does the backend support NULLS FIRST and NULLS LAST in ORDER BY? supports_order_by_nulls_modifier = True + # Does the backend orders NULLS FIRST by default? + order_by_nulls_first = False + # The database's limit on the number of query parameters. max_query_params = None diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py index 1d0cd365db..e1d4ce726b 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -51,6 +51,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): # Neither MySQL nor MariaDB support partial indexes. supports_partial_indexes = False supports_order_by_nulls_modifier = False + order_by_nulls_first = True @cached_property def _mysql_storage_engine(self): diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index 6aebbc3262..84eca98644 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -45,3 +45,4 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_frame_range_fixed_distance = Database.sqlite_version_info >= (3, 28, 0) supports_aggregate_filter_clause = Database.sqlite_version_info >= (3, 30, 1) supports_order_by_nulls_modifier = Database.sqlite_version_info >= (3, 30, 0) + order_by_nulls_first = True diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index f773e99a0f..84960d77e1 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1120,9 +1120,13 @@ class OrderBy(BaseExpression): elif self.nulls_first: template = '%s NULLS FIRST' % template else: - if self.nulls_last: + if self.nulls_last and not ( + self.descending and connection.features.order_by_nulls_first + ): template = '%%(expression)s IS NULL, %s' % template - elif self.nulls_first: + elif self.nulls_first and not ( + not self.descending and connection.features.order_by_nulls_first + ): template = '%%(expression)s IS NOT NULL, %s' % template connection.ops.check_expression_support(self) expression_sql, params = compiler.compile(self.expression)