Fixed #31702 -- Added support for PostgreSQL opclasses in UniqueConstraint.

This commit is contained in:
Hannes Ljungberg 2020-06-11 21:37:12 +02:00 committed by Mariusz Felisiak
parent 69e0d9c553
commit 7edc6e53a7
6 changed files with 210 additions and 14 deletions

View file

@ -4,12 +4,14 @@ from unittest import mock
from django.db import (
IntegrityError, NotSupportedError, connection, transaction,
)
from django.db.models import CheckConstraint, Deferrable, F, Func, Q
from django.db.models import (
CheckConstraint, Deferrable, F, Func, Q, UniqueConstraint,
)
from django.test import skipUnlessDBFeature
from django.utils import timezone
from . import PostgreSQLTestCase
from .models import HotelReservation, RangesModel, Room
from .models import HotelReservation, RangesModel, Room, Scene
try:
from django.contrib.postgres.constraints import ExclusionConstraint
@ -21,6 +23,13 @@ except ImportError:
class SchemaTests(PostgreSQLTestCase):
get_opclass_query = '''
SELECT opcname, c.relname FROM pg_opclass AS oc
JOIN pg_index as i on oc.oid = ANY(i.indclass)
JOIN pg_class as c on c.oid = i.indexrelid
WHERE c.relname = %s
'''
def get_constraints(self, table):
"""Get the constraints on the table using a new cursor."""
with connection.cursor() as cursor:
@ -84,6 +93,75 @@ class SchemaTests(PostgreSQLTestCase):
timestamps_inner=(datetime_1, datetime_2),
)
def test_opclass(self):
constraint = UniqueConstraint(
name='test_opclass',
fields=['scene'],
opclasses=['varchar_pattern_ops'],
)
with connection.schema_editor() as editor:
editor.add_constraint(Scene, constraint)
self.assertIn(constraint.name, self.get_constraints(Scene._meta.db_table))
with editor.connection.cursor() as cursor:
cursor.execute(self.get_opclass_query, [constraint.name])
self.assertEqual(
cursor.fetchall(),
[('varchar_pattern_ops', constraint.name)],
)
# 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))
def test_opclass_multiple_columns(self):
constraint = UniqueConstraint(
name='test_opclass_multiple',
fields=['scene', 'setting'],
opclasses=['varchar_pattern_ops', 'text_pattern_ops'],
)
with connection.schema_editor() as editor:
editor.add_constraint(Scene, constraint)
with editor.connection.cursor() as cursor:
cursor.execute(self.get_opclass_query, [constraint.name])
expected_opclasses = (
('varchar_pattern_ops', constraint.name),
('text_pattern_ops', constraint.name),
)
self.assertCountEqual(cursor.fetchall(), expected_opclasses)
def test_opclass_partial(self):
constraint = UniqueConstraint(
name='test_opclass_partial',
fields=['scene'],
opclasses=['varchar_pattern_ops'],
condition=Q(setting__contains="Sir Bedemir's Castle"),
)
with connection.schema_editor() as editor:
editor.add_constraint(Scene, constraint)
with editor.connection.cursor() as cursor:
cursor.execute(self.get_opclass_query, [constraint.name])
self.assertCountEqual(
cursor.fetchall(),
[('varchar_pattern_ops', constraint.name)],
)
@skipUnlessDBFeature('supports_covering_indexes')
def test_opclass_include(self):
constraint = UniqueConstraint(
name='test_opclass_include',
fields=['scene'],
opclasses=['varchar_pattern_ops'],
include=['setting'],
)
with connection.schema_editor() as editor:
editor.add_constraint(Scene, constraint)
with editor.connection.cursor() as cursor:
cursor.execute(self.get_opclass_query, [constraint.name])
self.assertCountEqual(
cursor.fetchall(),
[('varchar_pattern_ops', constraint.name)],
)
class ExclusionConstraintTests(PostgreSQLTestCase):
def get_constraints(self, table):