From 3aa545281e0c0f9fac93753e3769df9e0334dbaa Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Sat, 6 Feb 2021 20:45:54 +0100 Subject: Fixed #30916 -- Added support for functional unique constraints. Thanks Ian Foote and Mariusz Felisiak for reviews. --- tests/postgres_tests/test_constraints.py | 33 ++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) (limited to 'tests/postgres_tests') diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py index 80c2bb77b6..7915bb226e 100644 --- a/tests/postgres_tests/test_constraints.py +++ b/tests/postgres_tests/test_constraints.py @@ -1,6 +1,7 @@ import datetime from unittest import mock +from django.contrib.postgres.indexes import OpClass from django.db import ( IntegrityError, NotSupportedError, connection, transaction, ) @@ -8,8 +9,8 @@ from django.db.models import ( CheckConstraint, Deferrable, F, Func, IntegerField, Q, UniqueConstraint, ) from django.db.models.fields.json import KeyTextTransform -from django.db.models.functions import Cast, Left -from django.test import skipUnlessDBFeature +from django.db.models.functions import Cast, Left, Lower +from django.test import modify_settings, skipUnlessDBFeature from django.utils import timezone from . import PostgreSQLTestCase @@ -26,6 +27,7 @@ except ImportError: pass +@modify_settings(INSTALLED_APPS={'append': 'django.contrib.postgres'}) class SchemaTests(PostgreSQLTestCase): get_opclass_query = ''' SELECT opcname, c.relname FROM pg_opclass AS oc @@ -166,6 +168,33 @@ class SchemaTests(PostgreSQLTestCase): [('varchar_pattern_ops', constraint.name)], ) + @skipUnlessDBFeature('supports_expression_indexes') + def test_opclass_func(self): + constraint = UniqueConstraint( + OpClass(Lower('scene'), name='text_pattern_ops'), + name='test_opclass_func', + ) + with connection.schema_editor() as editor: + editor.add_constraint(Scene, constraint) + constraints = self.get_constraints(Scene._meta.db_table) + self.assertIs(constraints[constraint.name]['unique'], True) + self.assertIn(constraint.name, constraints) + with editor.connection.cursor() as cursor: + cursor.execute(self.get_opclass_query, [constraint.name]) + self.assertEqual( + cursor.fetchall(), + [('text_pattern_ops', constraint.name)], + ) + Scene.objects.create(scene='Scene 10', setting='The dark forest of Ewing') + with self.assertRaises(IntegrityError), transaction.atomic(): + Scene.objects.create(scene='ScEnE 10', setting="Sir Bedemir's Castle") + Scene.objects.create(scene='Scene 5', setting="Sir Bedemir's Castle") + # Drop the constraint. + with connection.schema_editor() as editor: + editor.remove_constraint(Scene, constraint) + self.assertNotIn(constraint.name, self.get_constraints(Scene._meta.db_table)) + Scene.objects.create(scene='ScEnE 10', setting="Sir Bedemir's Castle") + class ExclusionConstraintTests(PostgreSQLTestCase): def get_constraints(self, table): -- cgit v1.3