#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 django-updates+unsubscr...@googlegroups.com. To post to this group, send email to django-updates@googlegroups.com. 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.