summaryrefslogtreecommitdiff
path: root/tests/postgres_tests/test_indexes.py
diff options
context:
space:
mode:
authorHannes Ljungberg <hannes@5monkeys.se>2019-10-10 20:04:17 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-01-13 11:47:50 +0100
commit83fcfc9ec8610540948815e127101f1206562ead (patch)
treeaca51a61ef2d7397ee170fc74c564d242c441147 /tests/postgres_tests/test_indexes.py
parente3ece0144a988bc522c4bd551baecaf2139ce4ed (diff)
Fixed #26167 -- Added support for functional indexes.
Thanks Simon Charette, Mads Jensen, and Mariusz Felisiak for reviews. Co-authored-by: Markus Holtermann <info@markusholtermann.eu>
Diffstat (limited to 'tests/postgres_tests/test_indexes.py')
-rw-r--r--tests/postgres_tests/test_indexes.py162
1 files changed, 157 insertions, 5 deletions
diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py
index b9888f4843..49646feb97 100644
--- a/tests/postgres_tests/test_indexes.py
+++ b/tests/postgres_tests/test_indexes.py
@@ -1,17 +1,18 @@
from unittest import mock
from django.contrib.postgres.indexes import (
- BloomIndex, BrinIndex, BTreeIndex, GinIndex, GistIndex, HashIndex,
+ BloomIndex, BrinIndex, BTreeIndex, GinIndex, GistIndex, HashIndex, OpClass,
SpGistIndex,
)
from django.db import NotSupportedError, connection
-from django.db.models import CharField, Q
-from django.db.models.functions import Length
+from django.db.models import CharField, F, Index, Q
+from django.db.models.functions import Cast, Collate, Length, Lower
from django.test import skipUnlessDBFeature
-from django.test.utils import register_lookup
+from django.test.utils import modify_settings, register_lookup
from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
-from .models import CharFieldModel, IntegerArrayModel, Scene
+from .fields import SearchVector, SearchVectorField
+from .models import CharFieldModel, IntegerArrayModel, Scene, TextFieldModel
class IndexTestMixin:
@@ -28,6 +29,17 @@ class IndexTestMixin:
self.assertEqual(args, ())
self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_%s' % self.index_class.suffix})
+ def test_deconstruction_with_expressions_no_customization(self):
+ name = f'test_title_{self.index_class.suffix}'
+ index = self.index_class(Lower('title'), name=name)
+ path, args, kwargs = index.deconstruct()
+ self.assertEqual(
+ path,
+ f'django.contrib.postgres.indexes.{self.index_class.__name__}',
+ )
+ self.assertEqual(args, (Lower('title'),))
+ self.assertEqual(kwargs, {'name': name})
+
class BloomIndexTests(IndexTestMixin, PostgreSQLSimpleTestCase):
index_class = BloomIndex
@@ -181,7 +193,14 @@ class SpGistIndexTests(IndexTestMixin, PostgreSQLSimpleTestCase):
self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_spgist', 'fillfactor': 80})
+@modify_settings(INSTALLED_APPS={'append': 'django.contrib.postgres'})
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):
"""
@@ -260,6 +279,37 @@ class SchemaTests(PostgreSQLTestCase):
editor.remove_index(IntegerArrayModel, index)
self.assertNotIn(index_name, self.get_constraints(IntegerArrayModel._meta.db_table))
+ def test_trigram_op_class_gin_index(self):
+ index_name = 'trigram_op_class_gin'
+ index = GinIndex(OpClass(F('scene'), name='gin_trgm_ops'), name=index_name)
+ with connection.schema_editor() as editor:
+ editor.add_index(Scene, index)
+ with editor.connection.cursor() as cursor:
+ cursor.execute(self.get_opclass_query, [index_name])
+ self.assertCountEqual(cursor.fetchall(), [('gin_trgm_ops', index_name)])
+ constraints = self.get_constraints(Scene._meta.db_table)
+ self.assertIn(index_name, constraints)
+ self.assertIn(constraints[index_name]['type'], GinIndex.suffix)
+ with connection.schema_editor() as editor:
+ editor.remove_index(Scene, index)
+ self.assertNotIn(index_name, self.get_constraints(Scene._meta.db_table))
+
+ def test_cast_search_vector_gin_index(self):
+ index_name = 'cast_search_vector_gin'
+ index = GinIndex(Cast('field', SearchVectorField()), name=index_name)
+ with connection.schema_editor() as editor:
+ editor.add_index(TextFieldModel, index)
+ sql = index.create_sql(TextFieldModel, editor)
+ table = TextFieldModel._meta.db_table
+ constraints = self.get_constraints(table)
+ self.assertIn(index_name, constraints)
+ self.assertIn(constraints[index_name]['type'], GinIndex.suffix)
+ self.assertIs(sql.references_column(table, 'field'), True)
+ self.assertIn('::tsvector', str(sql))
+ with connection.schema_editor() as editor:
+ editor.remove_index(TextFieldModel, index)
+ self.assertNotIn(index_name, self.get_constraints(table))
+
def test_bloom_index(self):
index_name = 'char_field_model_field_bloom'
index = BloomIndex(fields=['field'], name=index_name)
@@ -400,6 +450,28 @@ class SchemaTests(PostgreSQLTestCase):
editor.add_index(Scene, index)
self.assertNotIn(index_name, self.get_constraints(Scene._meta.db_table))
+ def test_tsvector_op_class_gist_index(self):
+ index_name = 'tsvector_op_class_gist'
+ index = GistIndex(
+ OpClass(
+ SearchVector('scene', 'setting', config='english'),
+ name='tsvector_ops',
+ ),
+ name=index_name,
+ )
+ with connection.schema_editor() as editor:
+ editor.add_index(Scene, index)
+ sql = index.create_sql(Scene, editor)
+ table = Scene._meta.db_table
+ constraints = self.get_constraints(table)
+ self.assertIn(index_name, constraints)
+ self.assertIn(constraints[index_name]['type'], GistIndex.suffix)
+ self.assertIs(sql.references_column(table, 'scene'), True)
+ self.assertIs(sql.references_column(table, 'setting'), True)
+ with connection.schema_editor() as editor:
+ editor.remove_index(Scene, index)
+ self.assertNotIn(index_name, self.get_constraints(table))
+
def test_hash_index(self):
# Ensure the table is there and doesn't have an index.
self.assertNotIn('field', self.get_constraints(CharFieldModel._meta.db_table))
@@ -455,3 +527,83 @@ class SchemaTests(PostgreSQLTestCase):
with connection.schema_editor() as editor:
editor.remove_index(CharFieldModel, index)
self.assertNotIn(index_name, self.get_constraints(CharFieldModel._meta.db_table))
+
+ def test_op_class(self):
+ index_name = 'test_op_class'
+ index = Index(
+ OpClass(Lower('field'), name='text_pattern_ops'),
+ name=index_name,
+ )
+ with connection.schema_editor() as editor:
+ editor.add_index(TextFieldModel, index)
+ with editor.connection.cursor() as cursor:
+ cursor.execute(self.get_opclass_query, [index_name])
+ self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', index_name)])
+
+ def test_op_class_descending_collation(self):
+ collation = connection.features.test_collations.get('non_default')
+ if not collation:
+ self.skipTest(
+ 'This backend does not support case-insensitive collations.'
+ )
+ index_name = 'test_op_class_descending_collation'
+ index = Index(
+ Collate(
+ OpClass(Lower('field'), name='text_pattern_ops').desc(nulls_last=True),
+ collation=collation,
+ ),
+ name=index_name,
+ )
+ with connection.schema_editor() as editor:
+ editor.add_index(TextFieldModel, index)
+ self.assertIn(
+ 'COLLATE %s' % editor.quote_name(collation),
+ str(index.create_sql(TextFieldModel, editor)),
+ )
+ with editor.connection.cursor() as cursor:
+ cursor.execute(self.get_opclass_query, [index_name])
+ self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', index_name)])
+ table = TextFieldModel._meta.db_table
+ constraints = self.get_constraints(table)
+ self.assertIn(index_name, constraints)
+ self.assertEqual(constraints[index_name]['orders'], ['DESC'])
+ with connection.schema_editor() as editor:
+ editor.remove_index(TextFieldModel, index)
+ self.assertNotIn(index_name, self.get_constraints(table))
+
+ def test_op_class_descending_partial(self):
+ index_name = 'test_op_class_descending_partial'
+ index = Index(
+ OpClass(Lower('field'), name='text_pattern_ops').desc(),
+ name=index_name,
+ condition=Q(field__contains='China'),
+ )
+ with connection.schema_editor() as editor:
+ editor.add_index(TextFieldModel, index)
+ with editor.connection.cursor() as cursor:
+ cursor.execute(self.get_opclass_query, [index_name])
+ self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', index_name)])
+ constraints = self.get_constraints(TextFieldModel._meta.db_table)
+ self.assertIn(index_name, constraints)
+ self.assertEqual(constraints[index_name]['orders'], ['DESC'])
+
+ def test_op_class_descending_partial_tablespace(self):
+ index_name = 'test_op_class_descending_partial_tablespace'
+ index = Index(
+ OpClass(Lower('field').desc(), name='text_pattern_ops'),
+ name=index_name,
+ condition=Q(field__contains='China'),
+ db_tablespace='pg_default',
+ )
+ with connection.schema_editor() as editor:
+ editor.add_index(TextFieldModel, index)
+ self.assertIn(
+ 'TABLESPACE "pg_default" ',
+ str(index.create_sql(TextFieldModel, editor))
+ )
+ with editor.connection.cursor() as cursor:
+ cursor.execute(self.get_opclass_query, [index_name])
+ self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', index_name)])
+ constraints = self.get_constraints(TextFieldModel._meta.db_table)
+ self.assertIn(index_name, constraints)
+ self.assertEqual(constraints[index_name]['orders'], ['DESC'])