Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#20091 closed Bug (fixed)

outer join regression over nullable foreign keys

Reported by: Ian Kelly Owned by: nobody
Component: Database layer (models, ORM) Version: 1.5
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no


We recently underwent an upgrade from Django 1.0.4 all the way to 1.5 and discovered a QuerySet regression at some intermediate point under Oracle. Given the models:

class Parent(models.Model):
    key = models.CharField(max_length=100)

class Child(models.Model):
    parent = models.ForeignKey(Parent, null=True)

Due to the way Oracle interprets empty strings as null, the following queries should both use an outer join and an IS NULL test:


The first query correctly uses both an outer join and an IS NULL test. The code responsible for transforming this lookup into an isnull is at
The second query correctly uses an IS NULL test but with an inner join. The code responsible for transforming this lookup into an isnull is at

I'm thinking that the latter code ought to be refactored into the former somehow.

Change History (9)

comment:1 Changed 5 years ago by Ian Kelly

Summary: outer join regressing over nullable foreign keysouter join regression over nullable foreign keys

comment:2 Changed 5 years ago by Anssi Kääriäinen

I see two possible fixes for this. The first is to add something like if lookup == 'exact' and connection.interprets_empty_strins_as_nulls and value = "": value = None just before the exact=None -> isnull conversion in build_filter (or add_filter in 1.5). This is somewhat dirty as the default connection will need to be used for checking the interprets_empty_strings_as_nulls condition (see is_nullable() in Query for another similar condition).

The other option is to do Oracle specific join promotion in compiler stage. This would involve going through all the where/having conditions checking for exact lookups against "". Then promote all the aliases found. This will be ugly to do correctly, but it would lead to correct results under multidb...

I think the first option is enough. If you are using multidb where your other database is Oracle, then you just need to do parent__key=None instead of u"" in your code.

comment:3 Changed 5 years ago by Anssi Kääriäinen

This seems to be regression from 1.1 to 1.2, in 1.1 add_filter() contains a value = "" and connection interprets emptry strings as nulls check, in 1.2 that is gone.

As such I don't see this as a regression and thus this isn't prime candidate for backpatch. Of course, the patch is going to be pretty simple, and if a core committer is willing to backpatch this to 1.5 I am not going to complain...

I will try to commit a patch to master soon, it will not apply directly to 1.5 due to changes between master and 1.5.

comment:4 Changed 5 years ago by Anssi Kääriäinen

A commit fixing this is available from

I am not sure if this should be actually fixed, wouldn't it be possible to just use somefield=None in this case? The fix will not work under multidb and I believe changes done for multidb were the reason for this regression.

I will leave the commit and backpatch decision to Ian.

comment:5 Changed 5 years ago by Ian Kelly

I believe the fix is important because while somefield=None would work, the ORM itself uses the empty string. For example, this would produce an empty queryset in Oracle:

somechild = Child.objects.create(parent=someparent)

comment:6 Changed 5 years ago by Anssi Kääriäinen

OK, convinced.

I will try to remember to do the patch + backport to 1.5 today.

comment:7 Changed 5 years ago by Anssi Kääriäinen <akaariai@…>

Resolution: fixed
Status: newclosed

In e17fa9e877e84e93b699c2bd13ea48dbbb86e451:

Fixed #20091 -- Oracle null promotion for empty strings

comment:8 Changed 5 years ago by Anssi Kääriäinen <akaariai@…>

In 207117ae731096d8148c17c6ca16f06ebf18537c:

[1.5.x] Fixed #20091 -- Oracle null promotion for empty strings

Backpatch of e17fa9e877e84e93b699c2bd13ea48dbbb86e451

comment:9 Changed 5 years ago by Anssi Kääriäinen

The fix is ugly, but it is the best that can be done easily. The alternative of doing nothing leads to Django itself generating queries that do not work.

I think something like Query.conditional_actions could be added if a perfect fix is needed. These are something that can be added during query construction and will be executed at start of compiling the query. So, one could add a conditional_promote to conditional_actions for the alias, and then promote the join if the given connection needs it. Then, this conditional promotion needs to be done at the end of compiling, otherwise the query will be changed in compiler and this doesn't work.

So, this would be a lot of work and some complicated code is needed to make the promotion work correctly. The gain is that correct join promotion happens when using non-default database for nullable strings. Doesn't seem to be worth it...

Note: See TracTickets for help on using tickets.
Back to Top