#32584 closed Uncategorized (wontfix)
OrderBy.as_sql() overwrites template, creating invalid syntax for certain database backends
Reported by: | Tim Nyborg | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 3.2 |
Severity: | Normal | Keywords: | order_by, nulls_first |
Cc: | David Beitey | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
A change in Django 3.1 has caused OrderBy.as_sql()
in django.db.models.expressions
to incorrectly extend SQL templates provided by db backends when nulls_first
or nulls_last
is set:
def as_sql(self, compiler, connection, template=None, **extra_context): template = template or self.template if connection.features.supports_order_by_nulls_modifier: if self.nulls_last: template = '%s NULLS LAST' % template elif self.nulls_first: template = '%s NULLS FIRST' % template else: 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 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) placeholders = { 'expression': expression_sql, 'ordering': 'DESC' if self.descending else 'ASC', **extra_context, } template = template or self.template params *= template.count('%(expression)s') return (template % placeholders).rstrip(), params
With changes from 3.1, feature flags were added along with alternative syntax (https://github.com/django/django/commit/7286eaf681d497167cd7dc8b70ceebfcf5cd21ad) but the two types of syntax included only work for the given db backends. On a backend that doesn't support either syntax (such as the MSSQL 3rd party driver), there is currently no way of supplying its own template with correct syntax because that template will always be modified when nulls_first
or nulls_last
is set (e.g. when descending=True/nulls_first=True or descending=False/nulls_last=True).
Can an additional database feature flag be added to Django to cover this use case? For example supports_order_by_nulls
and when that is True
, the existing code can run to modify the given template; if False
, then the template should not be modified.
Ref: https://github.com/microsoft/mssql-django/issues/19 & https://github.com/microsoft/mssql-django/issues/31
Change History (8)
comment:1 by , 4 years ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
comment:2 by , 4 years ago
comment:3 by , 4 years ago
Cc: | added |
---|---|
Description: | modified (diff) |
Has patch: | unset |
Resolution: | invalid |
Status: | closed → new |
Summary: | OrderBy.as_sql() overwrites template → OrderBy.as_sql() overwrites template, creating invalid syntax for certain database backends |
Version: | 3.1 → 3.2 |
comment:4 by , 4 years ago
Prior to this commit: (https://github.com/django/django/commit/7286eaf681d497167cd7dc8b70ceebfcf5cd21ad), a supplied template would not be overridden. I think that's the correct approach - logic to add nulls last
or order by <expression> is null
should only be applied to self.template
, if template
is null.
comment:5 by , 4 years ago
David, I don't think that an additional feature flag is needed. You can always define as_mssql()
to override the current behavior on MSSQL. If MSSQL doesn't support nulls_first
and nulls_last
I would raise an exception, e.g.
def as_mssql(self, compiler, connection): if self.nulls_first or self.nulls_last: raise NotSupportedError('nulls_first/nulls_last are not supported on MSSQL.') return self.as_sql(compiler, connection, template=template)
comment:6 by , 4 years ago
You can also disable nulls_first
and nulls_last
if you handled them in your custom template, e.g.
def as_mssql(self, compiler, connection): template = ... copy = self.copy() copy.nulls_first = False copy.nulls_last = False return copy.as_sql(compiler, connection, template=template)
comment:7 by , 4 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
comment:8 by , 4 years ago
@Mariusz, thanks for the suggestions - much appreciated.
I've followed your second suggestion and created https://github.com/microsoft/mssql-django/pull/32. Relying on the implementation details of OrderBy.as_sql()
not to modify templates feels slightly hacky, but in contrast, re-implementing the whole as_mssql()
would have involved copy-pasting most of the function out of Django's core (e.g in order to remove the template modification code) -- and keeping that in lock-step with Django would be a greater challenge.
I had misread one of the db feature flags that was relied on