#25946: Negated clauses' "isnull" added term does not take field transforms into
account
----------------------------------------------+----------------------------
Reporter: ris | Owner: nobody
Type: Bug | Status: new
Component: Database layer (models, ORM) | Version: master
Severity: Normal | Keywords: transform
| negated null
Triage Stage: Unreviewed | Has patch: 0
Easy pickings: 0 | UI/UX: 0
----------------------------------------------+----------------------------
In
https://github.com/django/django/blob/32ef48aa562e6aaee9983f5d0f1c60f02fd555fb/django/db/models/sql/query.py#L1209:
{{{
if (lookup_type != 'isnull' and (
self.is_nullable(targets[0]) or
self.alias_map[join_list[-1]].join_type == LOUTER)):
# The condition added here will be SQL like this:
# NOT (col IS NOT NULL), where the first NOT is added in
# upper layers of code. The reason for addition is that if
col
# is null, then col != someval will result in SQL
"unknown"
# which isn't the same as in Python. The Python None
handling
# is wanted, and it can be gotten by
# (col IS NULL OR col != someval)
# <=>
# NOT (col IS NOT NULL AND col = someval).
lookup_class = targets[0].get_lookup('isnull')
clause.add(lookup_class(targets[0].get_col(alias,
sources[0]), False), AND)
}}}
The problem with this is that it always performs the `isnull` on the base
field, ignoring any transforms that may apply.
Consider a simple custom transform on a field, `base_field`, which
transforms it into `some_function ( base_field )`. Performing a negated
`exact` lookup on this will generate SQL along the lines of:
{{{
NOT (some_function ( "some_table"."base_field" ) = 1 AND
"some_table"."base_field" IS NOT NULL)
}}}
This will not produce the correct results in cases where `some_function`
doesn't pass through nulls as nulls or generates nulls of its own (though
it's true that many functions null behaviour is to mirror the source
expression null-ness exactly, which is probably why this hasn't been
noticed before).
The suggested "correct" SQL is probably
{{{
NOT (some_function ( "some_table"."base_field" ) = 1 AND some_function (
"some_table"."base_field" ) IS NOT NULL)
}}}
Though I note that this is itself not problem free if we consider cases
where `some_function` may have side effects or may not be `STABLE` or
`IMMUTABLE` (as postgres would put it), i.e. won't necessarily return the
same result when called twice given the same input. I would consider use
of `COALESCE()` instead to remedy this but this may have portability
concerns or may be more opaque to the query planner leading to slower
queries.
Noticed on 1.8, master's equivalent method doesn't appear to have changed
much since then.
--
Ticket URL: <https://code.djangoproject.com/ticket/25946>
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 post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/046.8465d2eaff3a65437e53688c4ee3317e%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.