#36338: Grouping on JSONField subkey throws tuple index out of range error
-------------------------------------+-------------------------------------
     Reporter:  Marc DEBUREAUX       |                    Owner:  (none)
         Type:  Bug                  |                   Status:  new
    Component:  Database layer       |                  Version:  5.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:  jsonfield            |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by Marc DEBUREAUX:

Old description:

> Have this simple model:
>
> {{{
> class Link(models.Model):
>   item = models.ForeignKey("Item", on_delete=models.CASCADE)
>   extra = models.JSONField(blank=True, null=True)
> }}}
>
> And have this simple query:
>
> {{{
> Link.objects.filter(item_id=1).values("extra__key").annotate(count=Count("id"))
> }}}
>
> This throws the following stacktrace error since 5.2 release:
>

> {{{
> ---------------------------------------------------------------------------
> IndexError                                Traceback (most recent call
> last)
> File ~/python3.12/site-packages/IPython/core/formatters.py:770, in
> PlainTextFormatter.__call__(self, obj)
>     763 stream = StringIO()
>     764 printer = pretty.RepresentationPrinter(stream, self.verbose,
>     765     self.max_width, self.newline,
>     766     max_seq_length=self.max_seq_length,
>     767     singleton_pprinters=self.singleton_printers,
>     768     type_pprinters=self.type_printers,
>     769     deferred_pprinters=self.deferred_printers)
> --> 770 printer.pretty(obj)
>     771 printer.flush()
>     772 return stream.getvalue()
>
> File ~/python3.12/site-packages/IPython/lib/pretty.py:411, in
> RepresentationPrinter.pretty(self, obj)
>     400                         return meth(obj, self, cycle)
>     401                 if (
>     402                     cls is not object
>     403                     # check if cls defines __repr__
>    (...)    409                     and callable(_safe_getattr(cls,
> "__repr__", None))
>     410                 ):
> --> 411                     return _repr_pprint(obj, self, cycle)
>     413     return _default_pprint(obj, self, cycle)
>     414 finally:
>
> File ~/python3.12/site-packages/IPython/lib/pretty.py:786, in
> _repr_pprint(obj, p, cycle)
>     784 """A pprint that just redirects to the normal repr function."""
>     785 # Find newlines and replace them with p.break_()
> --> 786 output = repr(obj)
>     787 lines = output.splitlines()
>     788 with p.group():
>
> File ~/python3.12/site-packages/django/db/models/query.py:360, in
> QuerySet.__repr__(self)
>     359 def __repr__(self):
> --> 360     data = list(self[: REPR_OUTPUT_SIZE + 1])
>     361     if len(data) > REPR_OUTPUT_SIZE:
>     362         data[-1] = "...(remaining elements truncated)..."
>
> File ~/python3.12/site-packages/django/db/models/query.py:384, in
> QuerySet.__iter__(self)
>     369 def __iter__(self):
>     370     """
>     371     The queryset iterator protocol uses three nested iterators in
> the
>     372     default case:
>    (...)    382            - Responsible for turning the rows into model
> objects.
>     383     """
> --> 384     self._fetch_all()
>     385     return iter(self._result_cache)
>
> File ~/python3.12/site-packages/django/db/models/query.py:1935, in
> QuerySet._fetch_all(self)
>    1933 def _fetch_all(self):
>    1934     if self._result_cache is None:
> -> 1935         self._result_cache = list(self._iterable_class(self))
>    1936     if self._prefetch_related_lookups and not
> self._prefetch_done:
>    1937         self._prefetch_related_objects()
>
> File ~/python3.12/site-packages/django/db/models/query.py:216, in
> ValuesIterable.__iter__(self)
>     210     names = [
>     211         *query.extra_select,
>     212         *query.values_select,
>     213         *query.annotation_select,
>     214     ]
>     215 indexes = range(len(names))
> --> 216 for row in compiler.results_iter(
>     217     chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
>     218 ):
>     219     yield {names[i]: row[i] for i in indexes}
>
> File ~/python3.12/site-packages/django/db/models/sql/compiler.py:1571, in
> SQLCompiler.results_iter(self, results, tuple_expected, chunked_fetch,
> chunk_size)
>    1569 """Return an iterator over the results from executing this
> query."""
>    1570 if results is None:
> -> 1571     results = self.execute_sql(
>    1572         MULTI, chunked_fetch=chunked_fetch, chunk_size=chunk_size
>    1573     )
>    1574 fields = [s[0] for s in self.select[0 : self.col_count]]
>    1575 converters = self.get_converters(fields)
>
> File ~/python3.12/site-packages/django/db/models/sql/compiler.py:1609, in
> SQLCompiler.execute_sql(self, result_type, chunked_fetch, chunk_size)
>    1607 result_type = result_type or NO_RESULTS
>    1608 try:
> -> 1609     sql, params = self.as_sql()
>    1610     if not sql:
>    1611         raise EmptyResultSet
>
> File ~/python3.12/site-packages/django/db/models/sql/compiler.py:765, in
> SQLCompiler.as_sql(self, with_limits, with_col_aliases)
>     763 try:
>     764     combinator = self.query.combinator
> --> 765     extra_select, order_by, group_by = self.pre_sql_setup(
>     766         with_col_aliases=with_col_aliases or bool(combinator),
>     767     )
>     768     for_update_part = None
>     769     # Is a LIMIT/OFFSET clause needed?
>
> File ~/python3.12/site-packages/django/db/models/sql/compiler.py:85, in
> SQLCompiler.pre_sql_setup(self, with_col_aliases)
>      79 def pre_sql_setup(self, with_col_aliases=False):
>      80     """
>      81     Do any necessary class setup immediately prior to producing
> SQL. This
>      82     is for things that can't necessarily be done in __init__
> because we
>      83     might not have all the pieces in place at that time.
>      84     """
> ---> 85     self.setup_query(with_col_aliases=with_col_aliases)
>      86     order_by = self.get_order_by()
>      87     self.where, self.having, self.qualify =
> self.query.where.split_having_qualify(
>      88         must_group_by=self.query.group_by is not None
>      89     )
>
> File ~/python3.12/site-packages/django/db/models/sql/compiler.py:74, in
> SQLCompiler.setup_query(self, with_col_aliases)
>      72 if all(self.query.alias_refcount[a] == 0 for a in
> self.query.alias_map):
>      73     self.query.get_initial_alias()
> ---> 74 self.select, self.klass_info, self.annotation_col_map =
> self.get_select(
>      75     with_col_aliases=with_col_aliases,
>      76 )
>      77 self.col_count = len(self.select)
>
> File ~/python3.12/site-packages/django/db/models/sql/compiler.py:286, in
> SQLCompiler.get_select(self, with_col_aliases)
>     284 # Reference to a column.
>     285 elif isinstance(expression, int):
> --> 286     expression = cols[expression]
>     287 # ColPairs cannot be aliased.
>     288 if isinstance(expression, ColPairs):
> }}}

New description:

 Have this simple model:

 {{{
 class Link(models.Model):
   item = models.ForeignKey("Item", on_delete=models.CASCADE)
   extra = models.JSONField(blank=True, null=True)
 }}}

 And have this simple query:

 {{{
 Link.objects.all().values("extra__key",
 "item_id").annotate(count=Count("id"))
 }}}

 This throws the following stacktrace error since 5.2 release:


 {{{
 ---------------------------------------------------------------------------
 IndexError                                Traceback (most recent call
 last)
 File ~/python3.12/site-packages/IPython/core/formatters.py:770, in
 PlainTextFormatter.__call__(self, obj)
     763 stream = StringIO()
     764 printer = pretty.RepresentationPrinter(stream, self.verbose,
     765     self.max_width, self.newline,
     766     max_seq_length=self.max_seq_length,
     767     singleton_pprinters=self.singleton_printers,
     768     type_pprinters=self.type_printers,
     769     deferred_pprinters=self.deferred_printers)
 --> 770 printer.pretty(obj)
     771 printer.flush()
     772 return stream.getvalue()

 File ~/python3.12/site-packages/IPython/lib/pretty.py:411, in
 RepresentationPrinter.pretty(self, obj)
     400                         return meth(obj, self, cycle)
     401                 if (
     402                     cls is not object
     403                     # check if cls defines __repr__
    (...)    409                     and callable(_safe_getattr(cls,
 "__repr__", None))
     410                 ):
 --> 411                     return _repr_pprint(obj, self, cycle)
     413     return _default_pprint(obj, self, cycle)
     414 finally:

 File ~/python3.12/site-packages/IPython/lib/pretty.py:786, in
 _repr_pprint(obj, p, cycle)
     784 """A pprint that just redirects to the normal repr function."""
     785 # Find newlines and replace them with p.break_()
 --> 786 output = repr(obj)
     787 lines = output.splitlines()
     788 with p.group():

 File ~/python3.12/site-packages/django/db/models/query.py:360, in
 QuerySet.__repr__(self)
     359 def __repr__(self):
 --> 360     data = list(self[: REPR_OUTPUT_SIZE + 1])
     361     if len(data) > REPR_OUTPUT_SIZE:
     362         data[-1] = "...(remaining elements truncated)..."

 File ~/python3.12/site-packages/django/db/models/query.py:384, in
 QuerySet.__iter__(self)
     369 def __iter__(self):
     370     """
     371     The queryset iterator protocol uses three nested iterators in
 the
     372     default case:
    (...)    382            - Responsible for turning the rows into model
 objects.
     383     """
 --> 384     self._fetch_all()
     385     return iter(self._result_cache)

 File ~/python3.12/site-packages/django/db/models/query.py:1935, in
 QuerySet._fetch_all(self)
    1933 def _fetch_all(self):
    1934     if self._result_cache is None:
 -> 1935         self._result_cache = list(self._iterable_class(self))
    1936     if self._prefetch_related_lookups and not self._prefetch_done:
    1937         self._prefetch_related_objects()

 File ~/python3.12/site-packages/django/db/models/query.py:216, in
 ValuesIterable.__iter__(self)
     210     names = [
     211         *query.extra_select,
     212         *query.values_select,
     213         *query.annotation_select,
     214     ]
     215 indexes = range(len(names))
 --> 216 for row in compiler.results_iter(
     217     chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
     218 ):
     219     yield {names[i]: row[i] for i in indexes}

 File ~/python3.12/site-packages/django/db/models/sql/compiler.py:1571, in
 SQLCompiler.results_iter(self, results, tuple_expected, chunked_fetch,
 chunk_size)
    1569 """Return an iterator over the results from executing this
 query."""
    1570 if results is None:
 -> 1571     results = self.execute_sql(
    1572         MULTI, chunked_fetch=chunked_fetch, chunk_size=chunk_size
    1573     )
    1574 fields = [s[0] for s in self.select[0 : self.col_count]]
    1575 converters = self.get_converters(fields)

 File ~/python3.12/site-packages/django/db/models/sql/compiler.py:1609, in
 SQLCompiler.execute_sql(self, result_type, chunked_fetch, chunk_size)
    1607 result_type = result_type or NO_RESULTS
    1608 try:
 -> 1609     sql, params = self.as_sql()
    1610     if not sql:
    1611         raise EmptyResultSet

 File ~/python3.12/site-packages/django/db/models/sql/compiler.py:765, in
 SQLCompiler.as_sql(self, with_limits, with_col_aliases)
     763 try:
     764     combinator = self.query.combinator
 --> 765     extra_select, order_by, group_by = self.pre_sql_setup(
     766         with_col_aliases=with_col_aliases or bool(combinator),
     767     )
     768     for_update_part = None
     769     # Is a LIMIT/OFFSET clause needed?

 File ~/python3.12/site-packages/django/db/models/sql/compiler.py:85, in
 SQLCompiler.pre_sql_setup(self, with_col_aliases)
      79 def pre_sql_setup(self, with_col_aliases=False):
      80     """
      81     Do any necessary class setup immediately prior to producing
 SQL. This
      82     is for things that can't necessarily be done in __init__
 because we
      83     might not have all the pieces in place at that time.
      84     """
 ---> 85     self.setup_query(with_col_aliases=with_col_aliases)
      86     order_by = self.get_order_by()
      87     self.where, self.having, self.qualify =
 self.query.where.split_having_qualify(
      88         must_group_by=self.query.group_by is not None
      89     )

 File ~/python3.12/site-packages/django/db/models/sql/compiler.py:74, in
 SQLCompiler.setup_query(self, with_col_aliases)
      72 if all(self.query.alias_refcount[a] == 0 for a in
 self.query.alias_map):
      73     self.query.get_initial_alias()
 ---> 74 self.select, self.klass_info, self.annotation_col_map =
 self.get_select(
      75     with_col_aliases=with_col_aliases,
      76 )
      77 self.col_count = len(self.select)

 File ~/python3.12/site-packages/django/db/models/sql/compiler.py:286, in
 SQLCompiler.get_select(self, with_col_aliases)
     284 # Reference to a column.
     285 elif isinstance(expression, int):
 --> 286     expression = cols[expression]
     287 # ColPairs cannot be aliased.
     288 if isinstance(expression, ColPairs):
 }}}

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36338#comment:1>
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 visit 
https://groups.google.com/d/msgid/django-updates/0107019650220607-cfb02dae-cfff-433f-9b3c-a90449b78ae5-000000%40eu-central-1.amazonses.com.

Reply via email to