#31097: StringAgg And ArrayAgg with filtering in subquery generates invalid
string_agg() SQL function call
--------------------------------------------+------------------------
               Reporter:  Laurent Tramoy    |          Owner:  (none)
                   Type:  Bug               |         Status:  new
              Component:  contrib.postgres  |        Version:  2.2
               Severity:  Normal            |       Keywords:
           Triage Stage:  Unreviewed        |      Has patch:  0
    Needs documentation:  0                 |    Needs tests:  0
Patch needs improvement:  0                 |  Easy pickings:  0
                  UI/UX:  0                 |
--------------------------------------------+------------------------
 This issue is similar to issue #30315 but using the `filter` keyword
 instead of `ordering`, so I'll reuse the same structure. I tested it on
 Django 2.2.8

 Consider the following models (in the people app):

 {{{
 from django.db import models
 from django.contrib.postgres.fields import ArrayField


 class Person(models.Model):
     """Person model."""

     first_name = models.TextField()
     last_name = models.TextField()
     country = models.TextField(null=True, blank=True)


 class Book(models.Model):
     """Book model."""
     category = ArrayField(models.CharField(max_length=32), null=True,
 default=list)
     people = models.ManyToManyField(Person)
 }}}

 with the following objects:
 {{{
 p1 = Person.objects.create(first_name="John", last_name="Doe")
 p2 = Person.objects.create(first_name="Jane", last_name="Doe")

 b1 = Book.objects.create()
 b1.people.add(p1)

 b2 = Book.objects.create()
 b2.people.add(p1, p2)

 b3 = Book.objects.create()
 }}}

 This fails:
 {{{
 from django.contrib.postgres.aggregates import StringAgg
 from django.db.models import Subquery, OuterRef, Q

 from people.models import  Book

 subquery = (
     Book.objects.annotate(
         _annotated_value=StringAgg(
             "people__first_name", ",",
 filter=Q(people__first_name__startswith="Ja")
         ),
     )
     .filter(pk=OuterRef("pk"),)
     .exclude(_annotated_value="")
     .values("id")
 )
 Book.objects.filter(id__in=Subquery(subquery))
 }}}

 The generated SQL is

 {{{
 SELECT
     "people_book"."id",
     "people_book"."category"
 FROM
     "people_book"
 WHERE
     "people_book"."id" IN (
         SELECT
             U0."id"
         FROM
             "people_book" U0
         LEFT OUTER JOIN "people_book_people" U1 ON (U0."id" =
 U1."book_id")
     LEFT OUTER JOIN "people_person" U2 ON (U1."person_id" = U2."id")
 WHERE
     U0."id" = ("people_book"."id")
 GROUP BY
     U0."id"
 HAVING
     NOT (STRING_AGG(, ',') FILTER (WHERE U2."first_name") =))
 }}}

 as we can see, the `STRING_AGG argument is wrong.

 The same query without the `filter` works fine:

 {{{
 subquery = (
     Book.objects.annotate(
         _annotated_value=StringAgg(
             "people__first_name", ","
         ),
     )
     .filter(pk=OuterRef("pk"),)
     .exclude(_annotated_value="")
     .values("id")
 )
 Book.objects.filter(id__in=Subquery(subquery))
 }}}

 SQL query:

 {{{
 SELECT
     "people_book"."id",
     "people_book"."category"
 FROM
     "people_book"
 WHERE
     "people_book"."id" IN (
         SELECT
             U0."id"
         FROM
             "people_book" U0
         LEFT OUTER JOIN "people_book_people" U1 ON (U0. "id" =
 U1."book_id")
     LEFT OUTER JOIN "people_person" U2 ON (U1."person_id" = U2."id")
 WHERE
     U0."id" = ("people_book"."id")
 GROUP BY
     U0."id"
 HAVING
     NOT (STRING_AGG(U2."first_name", ',') =))
 }}}


 as well as the same query without using `Subquery`:

 {{{
 query = (
     Book.objects.annotate(
         _annotated_value=StringAgg(
             "people__first_name", ",",
 filter=Q(people__first_name__startswith="Ja")
         ),
     )
     .exclude(_annotated_value="")
 )
 }}}

 SQL query:

 {{{
 SELECT
     "people_book"."id",
     "people_book"."category",
     STRING_AGG("people_person"."first_name", ',') FILTER (WHERE
 "people_person"."first_name"::text LIKE Ja %) AS "_annotated_value"
 FROM
     "people_book"
     LEFT OUTER JOIN "people_book_people" ON ("people_book"."id" =
 "people_book_people"."book_id")
     LEFT OUTER JOIN "people_person" ON ("people_book_people"."person_id" =
 "people_person"."id")
 GROUP BY
     "people_book"."id"
 HAVING
     NOT (STRING_AGG("people_person"."first_name", ',') FILTER (WHERE
 ("people_person"."first_name"::text LIKE Ja %)) =)
 }}}

 Just to make sure I wasn't using an old version, I tried the query from
 #30315, which works fine.

 NB: I originally noticed that bug using ArrayAgg instead of StringAgg, but
 I encountered another bug using ArrayAgg (or at least what I think is a
 bug) while writing the example code, I'll report it later if needed

-- 
Ticket URL: <https://code.djangoproject.com/ticket/31097>
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/049.29e2aeee507f2d7db62c596755f0f635%40djangoproject.com.

Reply via email to