diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 4058b10a5e..14092e793e 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -88,6 +88,9 @@ class BaseDatabaseFeatures: # Does the backend order NULL values as largest or smallest? nulls_order_largest = False + # Does the backend support NULLS FIRST and NULLS LAST in ORDER BY? + supports_order_by_nulls_modifier = True + # 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 6a7f87ba27..9aaae2b5da 100644 --- a/django/db/backends/mysql/features.py +++ b/django/db/backends/mysql/features.py @@ -50,6 +50,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): db_functions_convert_bytes_to_str = True # Neither MySQL nor MariaDB support partial indexes. supports_partial_indexes = False + supports_order_by_nulls_modifier = False @cached_property def _mysql_storage_engine(self): diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index c82be5c30d..6aebbc3262 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -44,3 +44,4 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_over_clause = Database.sqlite_version_info >= (3, 25, 0) 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) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 44a52c1dc9..13e0ef172c 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1128,11 +1128,17 @@ class OrderBy(BaseExpression): return [self.expression] def as_sql(self, compiler, connection, template=None, **extra_context): - if not template: + template = template or self.template + if connection.features.supports_order_by_nulls_modifier: if self.nulls_last: - template = '%s NULLS LAST' % self.template + template = '%s NULLS LAST' % template elif self.nulls_first: - template = '%s NULLS FIRST' % self.template + template = '%s NULLS FIRST' % template + else: + if self.nulls_last: + template = '%%(expression)s IS NULL, %s' % template + elif self.nulls_first: + template = '%%(expression)s IS NOT NULL, %s' % template connection.ops.check_expression_support(self) expression_sql, params = compiler.compile(self.expression) placeholders = { @@ -1144,23 +1150,6 @@ class OrderBy(BaseExpression): params *= template.count('%(expression)s') return (template % placeholders).rstrip(), params - def as_sqlite(self, compiler, connection): - template = None - if connection.Database.sqlite_version_info < (3, 30, 0): - if self.nulls_last: - template = '%(expression)s IS NULL, %(expression)s %(ordering)s' - elif self.nulls_first: - template = '%(expression)s IS NOT NULL, %(expression)s %(ordering)s' - return self.as_sql(compiler, connection, template=template) - - def as_mysql(self, compiler, connection): - template = None - if self.nulls_last: - template = 'ISNULL(%(expression)s), %(expression)s %(ordering)s ' - elif self.nulls_first: - template = 'IF(ISNULL(%(expression)s),0,1), %(expression)s %(ordering)s ' - return self.as_sql(compiler, connection, template=template) - def as_oracle(self, compiler, connection): # Oracle doesn't allow ORDER BY EXISTS() unless it's wrapped in # a CASE WHEN.