#32584: OrderBy.as_sql() overwrites template, creating invalid syntax for 
certain
database backends
-------------------------------------+-------------------------------------
     Reporter:  Tim Nyborg           |                    Owner:  nobody
         Type:  Uncategorized        |                   Status:  new
    Component:  Database layer       |                  Version:  3.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:  order_by,            |             Triage Stage:
  nulls_first                        |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by David Beitey):

 * status:  closed => new
 * cc: David Beitey (added)
 * version:  3.1 => 3.2
 * has_patch:  1 => 0
 * resolution:  invalid =>


Old description:

> A change in 3.1 has caused OrderBy.as_sql() in
> django.db.models.expressions to ignore 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
> }}}
>
> Note that template is always overwritten if nulls_first == True.  In 3.0,
> the function first tested for template == None.
>
> This causes trouble for the MSSQL 3rd party driver, which provides its
> own order by functionality:
> https://github.com/microsoft/mssql-django/issues/19
>
> The following change (simply testing for template) fixed the issue for me
> (forked off 3.1 as I'm on py 3.7)
> https://github.com/timnyborg/django/commit/fd41b39ade8d37138951223eba7f2e3fb66d0d1c

New description:

 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:

 
https://github.com/django/django/blob/ed0cc52dc3b0dfebba8a38c12b6157a007309900/django/db/models/expressions.py#L1212

 {{{
     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

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/32584#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/067.4cdf8e8668b52a152bcd1714ebc5848a%40djangoproject.com.

Reply via email to