2
0

tests.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from unittest import skipIf, skipUnless
  2. from django.db import connection
  3. from django.db.models import Index
  4. from django.db.models.deletion import CASCADE
  5. from django.db.models.fields.related import ForeignKey
  6. from django.test import TestCase, TransactionTestCase
  7. from .models import (
  8. Article, ArticleTranslation, IndexedArticle2, IndexTogetherSingleList,
  9. )
  10. class SchemaIndexesTests(TestCase):
  11. """
  12. Test index handling by the db.backends.schema infrastructure.
  13. """
  14. def test_index_name_hash(self):
  15. """
  16. Index names should be deterministic.
  17. """
  18. with connection.schema_editor() as editor:
  19. index_name = editor._create_index_name(
  20. table_name=Article._meta.db_table,
  21. column_names=("c1",),
  22. suffix="123",
  23. )
  24. self.assertEqual(index_name, "indexes_article_c1_a52bd80b123")
  25. def test_index_name(self):
  26. """
  27. Index names on the built-in database backends::
  28. * Are truncated as needed.
  29. * Include all the column names.
  30. * Include a deterministic hash.
  31. """
  32. long_name = 'l%sng' % ('o' * 100)
  33. with connection.schema_editor() as editor:
  34. index_name = editor._create_index_name(
  35. table_name=Article._meta.db_table,
  36. column_names=('c1', 'c2', long_name),
  37. suffix='ix',
  38. )
  39. expected = {
  40. 'mysql': 'indexes_article_c1_c2_looooooooooooooooooo_255179b2ix',
  41. 'oracle': 'indexes_a_c1_c2_loo_255179b2ix',
  42. 'postgresql': 'indexes_article_c1_c2_loooooooooooooooooo_255179b2ix',
  43. 'sqlite': 'indexes_article_c1_c2_l%sng_255179b2ix' % ('o' * 100),
  44. }
  45. if connection.vendor not in expected:
  46. self.skipTest('This test is only supported on the built-in database backends.')
  47. self.assertEqual(index_name, expected[connection.vendor])
  48. def test_index_together(self):
  49. editor = connection.schema_editor()
  50. index_sql = [str(statement) for statement in editor._model_indexes_sql(Article)]
  51. self.assertEqual(len(index_sql), 1)
  52. # Ensure the index name is properly quoted
  53. self.assertIn(
  54. connection.ops.quote_name(
  55. editor._create_index_name(Article._meta.db_table, ['headline', 'pub_date'], suffix='_idx')
  56. ),
  57. index_sql[0]
  58. )
  59. def test_index_together_single_list(self):
  60. # Test for using index_together with a single list (#22172)
  61. index_sql = connection.schema_editor()._model_indexes_sql(IndexTogetherSingleList)
  62. self.assertEqual(len(index_sql), 1)
  63. @skipIf(connection.vendor == 'postgresql', 'opclasses are PostgreSQL only')
  64. class SchemaIndexesNotPostgreSQLTests(TransactionTestCase):
  65. available_apps = ['indexes']
  66. def test_create_index_ignores_opclasses(self):
  67. index = Index(
  68. name='test_ops_class',
  69. fields=['headline'],
  70. opclasses=['varchar_pattern_ops'],
  71. )
  72. with connection.schema_editor() as editor:
  73. # This would error if opclasses weren't ingored.
  74. editor.add_index(IndexedArticle2, index)
  75. @skipUnless(connection.vendor == 'postgresql', 'PostgreSQL tests')
  76. class SchemaIndexesPostgreSQLTests(TransactionTestCase):
  77. available_apps = ['indexes']
  78. get_opclass_query = '''
  79. SELECT opcname, c.relname FROM pg_opclass AS oc
  80. JOIN pg_index as i on oc.oid = ANY(i.indclass)
  81. JOIN pg_class as c on c.oid = i.indexrelid
  82. WHERE c.relname = '%s'
  83. '''
  84. def test_text_indexes(self):
  85. """Test creation of PostgreSQL-specific text indexes (#12234)"""
  86. from .models import IndexedArticle
  87. index_sql = [str(statement) for statement in connection.schema_editor()._model_indexes_sql(IndexedArticle)]
  88. self.assertEqual(len(index_sql), 5)
  89. self.assertIn('("headline" varchar_pattern_ops)', index_sql[1])
  90. self.assertIn('("body" text_pattern_ops)', index_sql[3])
  91. # unique=True and db_index=True should only create the varchar-specific
  92. # index (#19441).
  93. self.assertIn('("slug" varchar_pattern_ops)', index_sql[4])
  94. def test_virtual_relation_indexes(self):
  95. """Test indexes are not created for related objects"""
  96. index_sql = connection.schema_editor()._model_indexes_sql(Article)
  97. self.assertEqual(len(index_sql), 1)
  98. def test_ops_class(self):
  99. index = Index(
  100. name='test_ops_class',
  101. fields=['headline'],
  102. opclasses=['varchar_pattern_ops'],
  103. )
  104. with connection.schema_editor() as editor:
  105. editor.add_index(IndexedArticle2, index)
  106. with editor.connection.cursor() as cursor:
  107. cursor.execute(self.get_opclass_query % 'test_ops_class')
  108. self.assertEqual(cursor.fetchall(), [('varchar_pattern_ops', 'test_ops_class')])
  109. def test_ops_class_multiple_columns(self):
  110. index = Index(
  111. name='test_ops_class_multiple',
  112. fields=['headline', 'body'],
  113. opclasses=['varchar_pattern_ops', 'text_pattern_ops'],
  114. )
  115. with connection.schema_editor() as editor:
  116. editor.add_index(IndexedArticle2, index)
  117. with editor.connection.cursor() as cursor:
  118. cursor.execute(self.get_opclass_query % 'test_ops_class_multiple')
  119. expected_ops_classes = (
  120. ('varchar_pattern_ops', 'test_ops_class_multiple'),
  121. ('text_pattern_ops', 'test_ops_class_multiple'),
  122. )
  123. self.assertCountEqual(cursor.fetchall(), expected_ops_classes)
  124. @skipUnless(connection.vendor == 'mysql', 'MySQL tests')
  125. class SchemaIndexesMySQLTests(TransactionTestCase):
  126. available_apps = ['indexes']
  127. def test_no_index_for_foreignkey(self):
  128. """
  129. MySQL on InnoDB already creates indexes automatically for foreign keys.
  130. (#14180). An index should be created if db_constraint=False (#26171).
  131. """
  132. storage = connection.introspection.get_storage_engine(
  133. connection.cursor(), ArticleTranslation._meta.db_table
  134. )
  135. if storage != "InnoDB":
  136. self.skip("This test only applies to the InnoDB storage engine")
  137. index_sql = [str(statement) for statement in connection.schema_editor()._model_indexes_sql(ArticleTranslation)]
  138. self.assertEqual(index_sql, [
  139. 'CREATE INDEX `indexes_articletranslation_article_no_constraint_id_d6c0806b` '
  140. 'ON `indexes_articletranslation` (`article_no_constraint_id`)'
  141. ])
  142. # The index also shouldn't be created if the ForeignKey is added after
  143. # the model was created.
  144. field_created = False
  145. try:
  146. with connection.schema_editor() as editor:
  147. new_field = ForeignKey(Article, CASCADE)
  148. new_field.set_attributes_from_name('new_foreign_key')
  149. editor.add_field(ArticleTranslation, new_field)
  150. field_created = True
  151. self.assertEqual([str(statement) for statement in editor.deferred_sql], [
  152. 'ALTER TABLE `indexes_articletranslation` '
  153. 'ADD CONSTRAINT `indexes_articletrans_new_foreign_key_id_d27a9146_fk_indexes_a` '
  154. 'FOREIGN KEY (`new_foreign_key_id`) REFERENCES `indexes_article` (`id`)'
  155. ])
  156. finally:
  157. if field_created:
  158. with connection.schema_editor() as editor:
  159. editor.remove_field(ArticleTranslation, new_field)