Prior to this change foreign key constraint references could be left pointing at tables dropped during operations simulating unsupported table alterations because of an unexpected failure to disable foreign key constraint checks. SQLite3 does not allow disabling such checks while in a transaction so they must be disabled beforehand. Thanks ezaquarii for the report and Carlton and Tim for the review. Backport of 315357ad25a6590e7f4564ec2e56a22132b09001 from master.
126 lines
5.4 KiB
Python
126 lines
5.4 KiB
Python
from unittest import skipUnless
|
|
|
|
from django.db import connection
|
|
from django.db.models.deletion import CASCADE
|
|
from django.db.models.fields.related import ForeignKey
|
|
from django.test import TestCase, TransactionTestCase
|
|
|
|
from .models import Article, ArticleTranslation, IndexTogetherSingleList
|
|
|
|
|
|
class SchemaIndexesTests(TestCase):
|
|
"""
|
|
Test index handling by the db.backends.schema infrastructure.
|
|
"""
|
|
|
|
def test_index_name_hash(self):
|
|
"""
|
|
Index names should be deterministic.
|
|
"""
|
|
editor = connection.schema_editor()
|
|
index_name = editor._create_index_name(
|
|
table_name=Article._meta.db_table,
|
|
column_names=("c1",),
|
|
suffix="123",
|
|
)
|
|
self.assertEqual(index_name, "indexes_article_c1_a52bd80b123")
|
|
|
|
def test_index_name(self):
|
|
"""
|
|
Index names on the built-in database backends::
|
|
* Are truncated as needed.
|
|
* Include all the column names.
|
|
* Include a deterministic hash.
|
|
"""
|
|
long_name = 'l%sng' % ('o' * 100)
|
|
editor = connection.schema_editor()
|
|
index_name = editor._create_index_name(
|
|
table_name=Article._meta.db_table,
|
|
column_names=('c1', 'c2', long_name),
|
|
suffix='ix',
|
|
)
|
|
expected = {
|
|
'mysql': 'indexes_article_c1_c2_looooooooooooooooooo_255179b2ix',
|
|
'oracle': 'indexes_a_c1_c2_loo_255179b2ix',
|
|
'postgresql': 'indexes_article_c1_c2_loooooooooooooooooo_255179b2ix',
|
|
'sqlite': 'indexes_article_c1_c2_l%sng_255179b2ix' % ('o' * 100),
|
|
}
|
|
if connection.vendor not in expected:
|
|
self.skipTest('This test is only supported on the built-in database backends.')
|
|
self.assertEqual(index_name, expected[connection.vendor])
|
|
|
|
def test_index_together(self):
|
|
editor = connection.schema_editor()
|
|
index_sql = [str(statement) for statement in editor._model_indexes_sql(Article)]
|
|
self.assertEqual(len(index_sql), 1)
|
|
# Ensure the index name is properly quoted
|
|
self.assertIn(
|
|
connection.ops.quote_name(
|
|
editor._create_index_name(Article._meta.db_table, ['headline', 'pub_date'], suffix='_idx')
|
|
),
|
|
index_sql[0]
|
|
)
|
|
|
|
def test_index_together_single_list(self):
|
|
# Test for using index_together with a single list (#22172)
|
|
index_sql = connection.schema_editor()._model_indexes_sql(IndexTogetherSingleList)
|
|
self.assertEqual(len(index_sql), 1)
|
|
|
|
@skipUnless(connection.vendor == 'postgresql', "This is a postgresql-specific issue")
|
|
def test_postgresql_text_indexes(self):
|
|
"""Test creation of PostgreSQL-specific text indexes (#12234)"""
|
|
from .models import IndexedArticle
|
|
index_sql = [str(statement) for statement in connection.schema_editor()._model_indexes_sql(IndexedArticle)]
|
|
self.assertEqual(len(index_sql), 5)
|
|
self.assertIn('("headline" varchar_pattern_ops)', index_sql[1])
|
|
self.assertIn('("body" text_pattern_ops)', index_sql[3])
|
|
# unique=True and db_index=True should only create the varchar-specific
|
|
# index (#19441).
|
|
self.assertIn('("slug" varchar_pattern_ops)', index_sql[4])
|
|
|
|
@skipUnless(connection.vendor == 'postgresql', "This is a postgresql-specific issue")
|
|
def test_postgresql_virtual_relation_indexes(self):
|
|
"""Test indexes are not created for related objects"""
|
|
index_sql = connection.schema_editor()._model_indexes_sql(Article)
|
|
self.assertEqual(len(index_sql), 1)
|
|
|
|
|
|
@skipUnless(connection.vendor == 'mysql', 'MySQL tests')
|
|
class SchemaIndexesMySQLTests(TransactionTestCase):
|
|
available_apps = ['indexes']
|
|
|
|
def test_no_index_for_foreignkey(self):
|
|
"""
|
|
MySQL on InnoDB already creates indexes automatically for foreign keys.
|
|
(#14180). An index should be created if db_constraint=False (#26171).
|
|
"""
|
|
storage = connection.introspection.get_storage_engine(
|
|
connection.cursor(), ArticleTranslation._meta.db_table
|
|
)
|
|
if storage != "InnoDB":
|
|
self.skip("This test only applies to the InnoDB storage engine")
|
|
index_sql = [str(statement) for statement in connection.schema_editor()._model_indexes_sql(ArticleTranslation)]
|
|
self.assertEqual(index_sql, [
|
|
'CREATE INDEX `indexes_articletranslation_article_no_constraint_id_d6c0806b` '
|
|
'ON `indexes_articletranslation` (`article_no_constraint_id`)'
|
|
])
|
|
|
|
# The index also shouldn't be created if the ForeignKey is added after
|
|
# the model was created.
|
|
field_created = False
|
|
try:
|
|
with connection.schema_editor() as editor:
|
|
new_field = ForeignKey(Article, CASCADE)
|
|
new_field.set_attributes_from_name('new_foreign_key')
|
|
editor.add_field(ArticleTranslation, new_field)
|
|
field_created = True
|
|
self.assertEqual([str(statement) for statement in editor.deferred_sql], [
|
|
'ALTER TABLE `indexes_articletranslation` '
|
|
'ADD CONSTRAINT `indexes_articletrans_new_foreign_key_id_d27a9146_fk_indexes_a` '
|
|
'FOREIGN KEY (`new_foreign_key_id`) REFERENCES `indexes_article` (`id`)'
|
|
])
|
|
finally:
|
|
if field_created:
|
|
with connection.schema_editor() as editor:
|
|
editor.remove_field(ArticleTranslation, new_field)
|