#27985: Converting `Foo.objects.filter(bar=None)` to an `IsNull` too early.
-------------------------------------+-------------------------------------
Reporter: Jarek | Owner: nobody
Glowacki |
Type: Bug | Status: new
Component: Database | Version: master
layer (models, ORM) | Keywords: sql None NULL
Severity: Normal | transform
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
This is a little similar to #25946, but different angle.
Case Study:
We define a custom Field which converts emptystring to `None` before
sending to DB and then `None` back to emptystring when reading from DB:
WHY (optional reading):
For want of a CharField that enforces uniqueness on non-empty elements
only. `'' == ''` in the DB, but `NULL != NULL`. We don't want to just
allow passing None into it in the code though. Hence NulledCharField.
Example Implementation:
{{{
class NulledCharField(models.CharField):
description = _("CharField that stores NULL for emptystrings but
returns ''.")
def from_db_value(self, value, expression, connection, context):
if value is None:
return ''
else:
return value
def to_python(self, value):
if isinstance(value, models.CharField):
return value
if value is None:
return ''
return value
def get_prep_value(self, value):
if value is '':
return None
else:
return value
}}}
This works for the most part, but not with filtering!
I took a look at the SQL, and `Foo.objects.filter(bar='')` resolves to
`... WHERE "Foo"."bar" = NULL; args=(None,)`
Compare this to `Foo.objects.filter(bar=None)`, which resolves to `...
WHERE "Foo"."bar" IS NULL', ()`
The reason for this is that the `=None` -> `__isnull=True` conversion
happens before any transformation of the value.
Specifically, in django/db/models/sql/query.py,
`Query.build_filter()` calls on `self.prepare_lookup_value()` (line1139)
before it calls `self.build_lookup()` (line 1187).
The former does the isnull replacement, while the latter transforms the
value.
This also applies (more severely) to the converse: If my
`get_prep_value()` were to for some reason convert `None` to something
else, then this would get ignored as Django would just decide to use the
`IsNull` lookup off the bat.
Solution seems like we just need to rearrange the order here a little.
Happy to give it a go, but if anyone has context as to whether it's maybe
this way by design, do tell. The above cases would at the very least serve
as good failing tests if we think this kind of custom field support is
appropriate.
--
Ticket URL: <https://code.djangoproject.com/ticket/27985>
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/050.7cf45bf75235fd4b11ab113e933afd6b%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.