Opened 4 years ago

Last modified 8 months ago

#32673 closed Bug

ProgrammingError resulting from invalid SQL for nested models.Q instances — at Initial Version

Reported by: Charles Lirsac Owned by: nobody
Component: Database layer (models, ORM) Version: 3.0
Severity: Normal Keywords: postgresql oracle q
Cc: Simon Charette Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Given the following model:

class Price(models.Model):
    price = models.IntegerField(null=False)
    price_previous = models.IntegerField(null=False)
    on_sale = models.BooleanField(null=False)

The following query used to work in 2.2. but emits a ProgrammingError on 3.0+ against a Postgres database:

Price.objects.filter(
    models.Q(
        on_sale=models.ExpressionWrapper(
            models.Q(price__lt=models.F('price_previous')),
            output_field=models.BooleanField(),
        )
    )
)

Traceback:

Traceback (most recent call last):
  File "<console>", line 5, in <module>
  File ".../lib/python3.7/site-packages/django/db/models/query.py", line 256, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File ".../lib/python3.7/site-packages/django/db/models/query.py", line 280, in __iter__
    self._fetch_all()
  File ".../lib/python3.7/site-packages/django/db/models/query.py", line 1324, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File ".../lib/python3.7/site-packages/django/db/models/query.py", line 51, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File ".../lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1169, in execute_sql
    cursor.execute(sql, params)
  File ".../lib/python3.7/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File ".../lib/python3.7/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File ".../lib/python3.7/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File ".../lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File ".../lib/python3.7/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File ".../lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: syntax error at or near "<"
LINE 1: ..."prices_price"."on_sale" = "prices_price"."price" < "prices_...

The error comes from the way the query ended up compiled, in 3.2 we get:

SELECT "prices_price"."id", "prices_price"."price", "prices_price"."price_previous", "prices_price"."on_sale" 
FROM "prices_price" 
WHERE "prices_price"."on_sale" = "prices_price"."price" < "prices_price"."price_previous"

The right hand side of the clause is not wrapped in parentheses. While on 2.2 we get:

SELECT "prices_price"."id", "prices_price"."price", "prices_price"."price_previous", "prices_price"."on_sale" 
FROM "prices_price" 
WHERE "prices_price"."on_sale" = ("prices_price"."price" < ("prices_price"."price_previous"))

The right hand side is correctly wrapped.

Sorry if the title is not super accurate, I wasn't sure how to exactly to describe the exact kind of models.Q invocation at play here.

Versions:

  • Python 3.7
  • Tested agains postgres 9.6, 11 and 13
  • Tested against 3.0.14, 3.1.8 and 3.2. It works on 2.2.20

For reference. the following query (valid in 3.0+) is equivalent and generates the same broken SQL:

Price.objects.filter(
    models.Q(
        on_sale=models.Q(price__lt=models.F('price_previous'))
    )
)

I've logged more details and a complete reproduction at https://github.com/lirsacc/django-check-constraint-pg-regression (in the context of a CheckConstraint and migrations which is where we first saw this).

Change History (0)

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