class Meta: indexes = ([ GinIndex( OpClass("artist", "gin_trgm_ops"), name="core_archivedsong_artist_trgm", ), GinIndex( OpClass("title", "gin_trgm_ops"), name="core_archivedsong_title_trgm", ), ] if connection.vendor == "postgresql" else [])
def _get_expressions(self, schema_editor, query): expressions = [] for idx, (expression, operator) in enumerate(self.expressions): if isinstance(expression, str): expression = F(expression) try: expression = OpClass(expression, self.opclasses[idx]) except IndexError: pass expression = ExclusionConstraintExpression(expression, operator=operator) expression.set_wrapper_classes(schema_editor.connection) expressions.append(expression) return ExpressionList(*expressions).resolve_expression(query)
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_repr(self): constraint = ExclusionConstraint( name="exclude_overlapping", expressions=[ (F("datespan"), RangeOperators.OVERLAPS), (F("room"), RangeOperators.EQUAL), ], ) self.assertEqual( repr(constraint), "<ExclusionConstraint: index_type='GIST' expressions=[" "(F(datespan), '&&'), (F(room), '=')] name='exclude_overlapping'>", ) constraint = ExclusionConstraint( name="exclude_overlapping", expressions=[(F("datespan"), RangeOperators.ADJACENT_TO)], condition=Q(cancelled=False), index_type="SPGiST", ) self.assertEqual( repr(constraint), "<ExclusionConstraint: index_type='SPGiST' expressions=[" "(F(datespan), '-|-')] name='exclude_overlapping' " "condition=(AND: ('cancelled', False))>", ) constraint = ExclusionConstraint( name="exclude_overlapping", expressions=[(F("datespan"), RangeOperators.ADJACENT_TO)], deferrable=Deferrable.IMMEDIATE, ) self.assertEqual( repr(constraint), "<ExclusionConstraint: index_type='GIST' expressions=[" "(F(datespan), '-|-')] name='exclude_overlapping' " "deferrable=Deferrable.IMMEDIATE>", ) constraint = ExclusionConstraint( name="exclude_overlapping", expressions=[(F("datespan"), RangeOperators.ADJACENT_TO)], include=["cancelled", "room"], ) self.assertEqual( repr(constraint), "<ExclusionConstraint: index_type='GIST' expressions=[" "(F(datespan), '-|-')] name='exclude_overlapping' " "include=('cancelled', 'room')>", ) constraint = ExclusionConstraint( name="exclude_overlapping", expressions=[ (OpClass("datespan", name="range_ops"), RangeOperators.ADJACENT_TO), ], ) self.assertEqual( repr(constraint), "<ExclusionConstraint: index_type='GIST' expressions=[" "(OpClass(F(datespan), name=range_ops), '-|-')] " "name='exclude_overlapping'>", )
def test_range_adjacent_opclass(self): constraint_name = "ints_adjacent_opclass" self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) constraint = ExclusionConstraint( name=constraint_name, expressions=[ (OpClass("ints", name="range_ops"), RangeOperators.ADJACENT_TO), ], ) with connection.schema_editor() as editor: editor.add_constraint(RangesModel, constraint) constraints = self.get_constraints(RangesModel._meta.db_table) self.assertIn(constraint_name, constraints) with editor.connection.cursor() as cursor: cursor.execute(SchemaTests.get_opclass_query, [constraint_name]) self.assertEqual( cursor.fetchall(), [("range_ops", constraint_name)], ) RangesModel.objects.create(ints=(20, 50)) with self.assertRaises(IntegrityError), transaction.atomic(): RangesModel.objects.create(ints=(10, 20)) RangesModel.objects.create(ints=(10, 19)) RangesModel.objects.create(ints=(51, 60)) # Drop the constraint. with connection.schema_editor() as editor: editor.remove_constraint(RangesModel, constraint) self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
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 Meta: if connection.vendor == "postgresql": indexes = ([ GinIndex( OpClass("query", "gin_trgm_ops"), name="core_archivedquery_query_trgm", ) ] if connection.vendor == "postgresql" else [])
def test_range_overlaps_custom(self): class TsTzRange(Func): function = 'TSTZRANGE' output_field = DateTimeRangeField() constraint = ExclusionConstraint( name='exclude_overlapping_reservations_custom_opclass', expressions=[ ( OpClass(TsTzRange('start', 'end', RangeBoundary()), 'range_ops'), RangeOperators.OVERLAPS, ), (OpClass('room', 'gist_int4_ops'), RangeOperators.EQUAL), ], condition=Q(cancelled=False), ) self._test_range_overlaps(constraint)
def test_range_overlaps_custom(self): class TsTzRange(Func): function = "TSTZRANGE" output_field = DateTimeRangeField() constraint = ExclusionConstraint( name="exclude_overlapping_reservations_custom_opclass", expressions=[ ( OpClass(TsTzRange("start", "end", RangeBoundary()), "range_ops"), RangeOperators.OVERLAPS, ), (OpClass("room", "gist_int4_ops"), RangeOperators.EQUAL), ], condition=Q(cancelled=False), ) self._test_range_overlaps(constraint)
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_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_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_range_adjacent_opclass_deferrable(self): constraint_name = "ints_adjacent_opclass_deferrable" self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) constraint = ExclusionConstraint( name=constraint_name, expressions=[ (OpClass("ints", name="range_ops"), RangeOperators.ADJACENT_TO), ], deferrable=Deferrable.DEFERRED, ) with connection.schema_editor() as editor: editor.add_constraint(RangesModel, constraint) self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
def test_range_adjacent_opclass_condition(self): constraint_name = 'ints_adjacent_opclass_condition' self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) constraint = ExclusionConstraint( name=constraint_name, expressions=[ (OpClass('ints', name='range_ops'), RangeOperators.ADJACENT_TO), ], condition=Q(id__gte=100), ) with connection.schema_editor() as editor: editor.add_constraint(RangesModel, constraint) self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
def test_range_adjacent_spgist_opclass_include(self): constraint_name = "ints_adjacent_spgist_opclass_include" self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) constraint = ExclusionConstraint( name=constraint_name, expressions=[ (OpClass("ints", name="range_ops"), RangeOperators.ADJACENT_TO), ], index_type="spgist", include=["decimals"], ) with connection.schema_editor() as editor: editor.add_constraint(RangesModel, constraint) self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
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'])
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))