#28908: Array specific lookups do not work with array aggregates
-------------------------------------+-------------------------------------
Reporter: Jaap Roes | Owner: nobody
Type: New | Status: new
feature |
Component: Database | Version: master
layer (models, ORM) | Keywords: ArrayAgg ArrayField
Severity: Normal | lookup
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
It's not possible to use array lookups on an array aggregate
(`django.contrib.postgres.aggregates.ArrayAgg`).
Given these models:
{{{#!python
import uuid
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=100, unique=True)
class Author(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=100)
class Genre(models.Model):
code = models.DecimalField(max_digits=3, decimal_places=1,
primary_key=True)
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
publishers = models.ManyToManyField(Publisher)
authors = models.ManyToManyField(Author)
genres = models.ManyToManyField(Genre)
class Novel(models.Model):
title = models.CharField(max_length=100)
publishers = ArrayField(models.CharField(max_length=100))
authors = ArrayField(models.UUIDField())
genres = ArrayField(models.DecimalField(max_digits=3,
decimal_places=1))
}}}
These tests pass:
{{{#!python
from django.contrib.postgres.aggregates import ArrayAgg
from django.test import TestCase
from .models import Author, Book, Genre, Novel, Publisher
class BaseArrayTestCase:
def setUp(self):
self.author = Author.objects.create(name='Charles Dickens')
self.publisher = Publisher.objects.create(name='Chapman & Hall')
self.genre = Genre.objects.create(code='1.0', name='Novella')
class TestNovelArrayField(BaseArrayTestCase, TestCase):
def setUp(self):
super().setUp()
self.novel = Novel.objects.create(
authors=[self.author.id],
publishers=[self.publisher.name],
genres=[self.genre.code],
title='A Christmas Carol')
def test_overlap(self):
qs = Novel.objects.filter(authors__overlap=[self.author.id])
# SELECT * FROM novel WHERE novel.authors && '{b1824cde-011d-46ec-
80f9-8139a20bdacb}'::uuid[];
self.assertEqual(qs.get(), self.novel)
def test_contains(self):
qs = Novel.objects.filter(genres__contains=[self.genre.code])
# SELECT * FROM novel WHERE novel.genres @> '{1.0}'::numeric(3,
1)[];
self.assertEqual(qs.get(), self.novel)
def test_contained_by(self):
qs =
Novel.objects.filter(publishers__contained_by=[self.publisher.name])
# SELECT * FROM novel WHERE novel.publishers <@ '{Chapman &
Hall}'::varchar(100)[];
self.assertEqual(qs.get(), self.novel)
def test_len(self):
qs = Novel.objects.filter(authors__len=1)
# SELECT * FROM novel WHERE array_length(novel.authors, 1) = 1;
self.assertEqual(qs.get(), self.novel)
}}}
While these tests fail:
{{{#!python
class TestBookArrayAgg(BaseArrayTestCase, TestCase):
def setUp(self):
super().setUp()
self.book = Book.objects.create(title='A Christmas Carol')
self.book.authors.set([self.author])
self.book.publishers.set([self.publisher])
self.book.genres.set([self.genre])
def test_overlap(self):
qs = Book.objects.annotate(author_ids=ArrayAgg('authors__id'))
qs = qs.filter(author_ids__overlap=[self.author.id])
# Expected:
# SELECT book.*, ARRAY_AGG(book_authors.author_id) AS author_ids
# FROM book
# LEFT OUTER JOIN book_authors ON (book.id =
book_authors.book_id)
# GROUP BY book.id
# HAVING ARRAY_AGG(book_authors.author_id) && '{b1824cde-011d-
46ec-80f9-8139a20bdacb}'::uuid[];
# Actual:
# Unsupported lookup 'overlap' for UUIDField or join on the
field not permitted.
self.assertEqual(qs.get(), self.book)
def test_contains(self):
qs = Book.objects.annotate(genre_codes=ArrayAgg('genres__code'))
qs = qs.filter(genre_codes__contains=[self.genre.code])
# Expected:
# SELECT book.*, ARRAY_AGG(book_genres.genre_id) AS genre_codes
# FROM book
# LEFT OUTER JOIN book_genres ON (book.id = book_genres.book_id)
# GROUP BY book.id
# HAVING ARRAY_AGG(book_genres.genre_id) @> '{1.0}'::numeric(3,
1)[];
# Actual:
# SELECT
# ...
# HAVING ARRAY_AGG(book_genres.genre_id)::text LIKE '%["1.0"]%';
self.assertEqual(qs.get(), self.book)
def test_contained_by(self):
qs =
Book.objects.annotate(publisher_names=ArrayAgg('publishers__name'))
qs =
qs.filter(publisher_names__contained_by=[self.publisher.name])
# Expected:
# SELECT book.*, ARRAY_AGG(publisher.name) AS publisher_names
# FROM book
# LEFT OUTER JOIN book_publishers ON (book.id =
book_publishers.book_id)
# LEFT OUTER JOIN publisher ON (book_publishers.publisher_id =
publisher.id)
# GROUP BY book.id
# HAVING ARRAY_AGG(publisher.name) <@ '{Chapman &
Hall}'::varchar(100)[];
# Actual:
# Unsupported lookup 'contained_by' for CharField or join on the
field not permitted.
self.assertEqual(qs.get(), self.book)
def test_len(self):
qs = Book.objects.annotate(author_names=ArrayAgg('authors__name'))
qs = qs.filter(author_names__len=1)
# Expected:
# SELECT book.*, ARRAY_AGG(author.name) AS author_names
# FROM book
# LEFT OUTER JOIN book_authors ON (book.id =
book_authors.book_id)
# LEFT OUTER JOIN author ON (book_authors.author_id = author.id)
# GROUP BY book.id
# HAVING array_length(ARRAY_AGG(author.name), 1) = 1;
# Actual:
# Unsupported lookup 'len' for CharField or join on the field not
permitted.
self.assertEqual(qs.get(), self.book)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/28908>
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 post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/048.be874a0d3f53611d1eb60c404ec03320%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.