Code

#20250 closed Bug (fixed)

AttributeError when filtering annotated queryset with negated Q (via fk)

Reported by: jan.koutny@… Owned by: Tim Graham <timograham@…>
Component: Database layer (models, ORM) Version: 1.4
Severity: Normal Keywords: annotate AttributeError
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: yes Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

If these conditions are met:

  • annotated queryset
  • Q is filtering attribute related via foreign key
  • Q is negation

Then AttributeError: 'NoneType' object has no attribute 'startswith' exception is throwed. Stack looks the same as in 12687 (fixed), see below.

Note that *all* conditions above are necessary, code works if one omitted. Reproducible with Django 1.4.5, python 2.6.8.

Traceback (most recent call last):
  File "bug.py", line 16, in <module>
    print qst.query
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/query.py", line 167, in __str__
    sql, params = self.sql_with_params()
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/query.py", line 175, in sql_with_params
    return self.get_compiler(DEFAULT_DB_ALIAS).as_sql()
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 82, in as_sql
    where, w_params = self.query.where.as_sql(qn=qn, connection=self.connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 91, in as_sql
    sql, params = child.as_sql(qn=qn, connection=connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 91, in as_sql
    sql, params = child.as_sql(qn=qn, connection=connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 91, in as_sql
    sql, params = child.as_sql(qn=qn, connection=connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 91, in as_sql
    sql, params = child.as_sql(qn=qn, connection=connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 91, in as_sql
    sql, params = child.as_sql(qn=qn, connection=connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 94, in as_sql
    sql, params = self.make_atom(child, qn, connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 154, in make_atom
    field_sql = self.sql_for_columns(lvalue, qn, connection)
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/where.py", line 227, in sql_for_columns
    lhs = '%s.%s' % (qn(table_alias), qn(name))
  File "/usr/lib/python2.6/site-packages/django/db/models/sql/compiler.py", line 49, in quote_name_unless_alias
    r = self.connection.ops.quote_name(name)
  File "/usr/lib/python2.6/site-packages/django/db/backends/sqlite3/base.py", line 145, in quote_name
    if name.startswith('"') and name.endswith('"'):
AttributeError: 'NoneType' object has no attribute 'startswith'

Code snippet to reproduce the issue, bug.py:

from bug.models import *
from django.db.models import Q,Count

qobj=~Q(rtom3__x=0) #TOXIC 
#qobj=~Q(x=0) #OK
#qobj=Q(rtom3__x=0) #OK

qst =  M1.objects.annotate(Count('m2')) #TOXIC
#qst =  M1.objects.all() #OK

qst = qst.filter(qobj)

#Gracefully returns [], no attempt to hit the database!!!!
#print qst

#Throws an exception!
print qst.query

and models.py:

from django.db import models

class M1(models.Model):
        rtom3 = models.ForeignKey("M3")
        x = models.IntegerField()

class M2(models.Model):
        rtom1 = models.ForeignKey("M1") 

class M3(models.Model):
        x = models.IntegerField()

Attachments (0)

Change History (5)

comment:1 Changed 12 months ago by akaariai

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to fixed
  • Status changed from new to closed

This seems to work on master. The results of the print qst.query are:

SELECT "tester_m1"."id", "tester_m1"."rtom3_id", "tester_m1"."x", COUNT("tester_m2"."id") AS "m2__count"
FROM "tester_m1" INNER JOIN "tester_m3" ON ( "tester_m1"."rtom3_id" = "tester_m3"."id" )
LEFT OUTER JOIN "tester_m2" ON ( "tester_m1"."id" = "tester_m2"."rtom1_id" )
WHERE NOT ("tester_m3"."x" = 0 ) GROUP BY "tester_m1"."id", "tester_m1"."rtom3_id", "tester_m1"."x"

I haven't verified that the generated query is correct, but at least no errors are thrown.

Backpatching this to 1.4 will not happen as this isn't a critical bug (not a dataloss/security/regression/crash bug). I am marking this as fixed.

comment:2 Changed 12 months ago by akaariai

  • Needs tests set
  • Resolution fixed deleted
  • Status changed from closed to new
  • Triage Stage changed from Unreviewed to Accepted

Reverting my fixed resolution, regression test should be added even if this already works in master.

comment:3 Changed 11 months ago by nott

  • Owner changed from nobody to nott
  • Status changed from new to assigned

comment:4 Changed 11 months ago by nott

  • Owner nott deleted
  • Status changed from assigned to new

Pull request with a regression test: https://github.com/django/django/pull/1165

comment:5 Changed 11 months ago by Tim Graham <timograham@…>

  • Owner set to Tim Graham <timograham@…>
  • Resolution set to fixed
  • Status changed from new to closed

In 7426e72302db9c8c78235afc5c335a437a7ad760:

Fixed #20250 - Added a regression test for negated Q + annotate

Thanks nott.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.