#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.

Reply via email to