tests.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. import datetime
  2. from unittest import skipUnless
  3. from django.conf import settings
  4. from django.db import connection
  5. from django.db.models import CASCADE, ForeignKey, Index, Q
  6. from django.db.models.functions import Lower
  7. from django.test import (
  8. TestCase,
  9. TransactionTestCase,
  10. skipIfDBFeature,
  11. skipUnlessDBFeature,
  12. )
  13. from django.test.utils import override_settings
  14. from django.utils import timezone
  15. from .models import Article, ArticleTranslation, IndexedArticle2
  16. class SchemaIndexesTests(TestCase):
  17. """
  18. Test index handling by the db.backends.schema infrastructure.
  19. """
  20. def test_index_name_hash(self):
  21. """
  22. Index names should be deterministic.
  23. """
  24. editor = connection.schema_editor()
  25. index_name = editor._create_index_name(
  26. table_name=Article._meta.db_table,
  27. column_names=("c1",),
  28. suffix="123",
  29. )
  30. self.assertEqual(index_name, "indexes_article_c1_a52bd80b123")
  31. def test_index_name(self):
  32. """
  33. Index names on the built-in database backends::
  34. * Are truncated as needed.
  35. * Include all the column names.
  36. * Include a deterministic hash.
  37. """
  38. long_name = "l%sng" % ("o" * 100)
  39. editor = connection.schema_editor()
  40. index_name = editor._create_index_name(
  41. table_name=Article._meta.db_table,
  42. column_names=("c1", "c2", long_name),
  43. suffix="ix",
  44. )
  45. expected = {
  46. "mysql": "indexes_article_c1_c2_looooooooooooooooooo_255179b2ix",
  47. "oracle": "indexes_a_c1_c2_loo_255179b2ix",
  48. "postgresql": "indexes_article_c1_c2_loooooooooooooooooo_255179b2ix",
  49. "sqlite": "indexes_article_c1_c2_l%sng_255179b2ix" % ("o" * 100),
  50. }
  51. if connection.vendor not in expected:
  52. self.skipTest(
  53. "This test is only supported on the built-in database backends."
  54. )
  55. self.assertEqual(index_name, expected[connection.vendor])
  56. def test_quoted_index_name(self):
  57. editor = connection.schema_editor()
  58. index_sql = [str(statement) for statement in editor._model_indexes_sql(Article)]
  59. self.assertEqual(len(index_sql), 1)
  60. # Ensure the index name is properly quoted.
  61. self.assertIn(
  62. connection.ops.quote_name(Article._meta.indexes[0].name),
  63. index_sql[0],
  64. )
  65. def test_columns_list_sql(self):
  66. index = Index(fields=["headline"], name="whitespace_idx")
  67. editor = connection.schema_editor()
  68. self.assertIn(
  69. "(%s)" % editor.quote_name("headline"),
  70. str(index.create_sql(Article, editor)),
  71. )
  72. @skipUnlessDBFeature("supports_index_column_ordering")
  73. def test_descending_columns_list_sql(self):
  74. index = Index(fields=["-headline"], name="whitespace_idx")
  75. editor = connection.schema_editor()
  76. self.assertIn(
  77. "(%s DESC)" % editor.quote_name("headline"),
  78. str(index.create_sql(Article, editor)),
  79. )
  80. class SchemaIndexesNotPostgreSQLTests(TransactionTestCase):
  81. available_apps = ["indexes"]
  82. def test_create_index_ignores_opclasses(self):
  83. index = Index(
  84. name="test_ops_class",
  85. fields=["headline"],
  86. opclasses=["varchar_pattern_ops"],
  87. )
  88. with connection.schema_editor() as editor:
  89. # This would error if opclasses weren't ignored.
  90. editor.add_index(IndexedArticle2, index)
  91. # The `condition` parameter is ignored by databases that don't support partial
  92. # indexes.
  93. @skipIfDBFeature("supports_partial_indexes")
  94. class PartialIndexConditionIgnoredTests(TransactionTestCase):
  95. available_apps = ["indexes"]
  96. def test_condition_ignored(self):
  97. index = Index(
  98. name="test_condition_ignored",
  99. fields=["published"],
  100. condition=Q(published=True),
  101. )
  102. with connection.schema_editor() as editor:
  103. # This would error if condition weren't ignored.
  104. editor.add_index(Article, index)
  105. self.assertNotIn(
  106. "WHERE %s" % editor.quote_name("published"),
  107. str(index.create_sql(Article, editor)),
  108. )
  109. @skipUnless(connection.vendor == "postgresql", "PostgreSQL tests")
  110. class SchemaIndexesPostgreSQLTests(TransactionTestCase):
  111. available_apps = ["indexes"]
  112. get_opclass_query = """
  113. SELECT opcname, c.relname FROM pg_opclass AS oc
  114. JOIN pg_index as i on oc.oid = ANY(i.indclass)
  115. JOIN pg_class as c on c.oid = i.indexrelid
  116. WHERE c.relname = '%s'
  117. """
  118. def test_text_indexes(self):
  119. """Test creation of PostgreSQL-specific text indexes (#12234)"""
  120. from .models import IndexedArticle
  121. index_sql = [
  122. str(statement)
  123. for statement in connection.schema_editor()._model_indexes_sql(
  124. IndexedArticle
  125. )
  126. ]
  127. self.assertEqual(len(index_sql), 5)
  128. self.assertIn('("headline" varchar_pattern_ops)', index_sql[1])
  129. self.assertIn('("body" text_pattern_ops)', index_sql[3])
  130. # unique=True and db_index=True should only create the varchar-specific
  131. # index (#19441).
  132. self.assertIn('("slug" varchar_pattern_ops)', index_sql[4])
  133. def test_virtual_relation_indexes(self):
  134. """Test indexes are not created for related objects"""
  135. index_sql = connection.schema_editor()._model_indexes_sql(Article)
  136. self.assertEqual(len(index_sql), 1)
  137. def test_ops_class(self):
  138. index = Index(
  139. name="test_ops_class",
  140. fields=["headline"],
  141. opclasses=["varchar_pattern_ops"],
  142. )
  143. with connection.schema_editor() as editor:
  144. editor.add_index(IndexedArticle2, index)
  145. with editor.connection.cursor() as cursor:
  146. cursor.execute(self.get_opclass_query % "test_ops_class")
  147. self.assertEqual(
  148. cursor.fetchall(), [("varchar_pattern_ops", "test_ops_class")]
  149. )
  150. def test_ops_class_multiple_columns(self):
  151. index = Index(
  152. name="test_ops_class_multiple",
  153. fields=["headline", "body"],
  154. opclasses=["varchar_pattern_ops", "text_pattern_ops"],
  155. )
  156. with connection.schema_editor() as editor:
  157. editor.add_index(IndexedArticle2, index)
  158. with editor.connection.cursor() as cursor:
  159. cursor.execute(self.get_opclass_query % "test_ops_class_multiple")
  160. expected_ops_classes = (
  161. ("varchar_pattern_ops", "test_ops_class_multiple"),
  162. ("text_pattern_ops", "test_ops_class_multiple"),
  163. )
  164. self.assertCountEqual(cursor.fetchall(), expected_ops_classes)
  165. def test_ops_class_partial(self):
  166. index = Index(
  167. name="test_ops_class_partial",
  168. fields=["body"],
  169. opclasses=["text_pattern_ops"],
  170. condition=Q(headline__contains="China"),
  171. )
  172. with connection.schema_editor() as editor:
  173. editor.add_index(IndexedArticle2, index)
  174. with editor.connection.cursor() as cursor:
  175. cursor.execute(self.get_opclass_query % "test_ops_class_partial")
  176. self.assertCountEqual(
  177. cursor.fetchall(), [("text_pattern_ops", "test_ops_class_partial")]
  178. )
  179. def test_ops_class_partial_tablespace(self):
  180. indexname = "test_ops_class_tblspace"
  181. index = Index(
  182. name=indexname,
  183. fields=["body"],
  184. opclasses=["text_pattern_ops"],
  185. condition=Q(headline__contains="China"),
  186. db_tablespace="pg_default",
  187. )
  188. with connection.schema_editor() as editor:
  189. editor.add_index(IndexedArticle2, index)
  190. self.assertIn(
  191. 'TABLESPACE "pg_default" ',
  192. str(index.create_sql(IndexedArticle2, editor)),
  193. )
  194. with editor.connection.cursor() as cursor:
  195. cursor.execute(self.get_opclass_query % indexname)
  196. self.assertCountEqual(cursor.fetchall(), [("text_pattern_ops", indexname)])
  197. def test_ops_class_descending(self):
  198. indexname = "test_ops_class_ordered"
  199. index = Index(
  200. name=indexname,
  201. fields=["-body"],
  202. opclasses=["text_pattern_ops"],
  203. )
  204. with connection.schema_editor() as editor:
  205. editor.add_index(IndexedArticle2, index)
  206. with editor.connection.cursor() as cursor:
  207. cursor.execute(self.get_opclass_query % indexname)
  208. self.assertCountEqual(cursor.fetchall(), [("text_pattern_ops", indexname)])
  209. def test_ops_class_descending_partial(self):
  210. indexname = "test_ops_class_ordered_partial"
  211. index = Index(
  212. name=indexname,
  213. fields=["-body"],
  214. opclasses=["text_pattern_ops"],
  215. condition=Q(headline__contains="China"),
  216. )
  217. with connection.schema_editor() as editor:
  218. editor.add_index(IndexedArticle2, index)
  219. with editor.connection.cursor() as cursor:
  220. cursor.execute(self.get_opclass_query % indexname)
  221. self.assertCountEqual(cursor.fetchall(), [("text_pattern_ops", indexname)])
  222. @skipUnlessDBFeature("supports_covering_indexes")
  223. def test_ops_class_include(self):
  224. index_name = "test_ops_class_include"
  225. index = Index(
  226. name=index_name,
  227. fields=["body"],
  228. opclasses=["text_pattern_ops"],
  229. include=["headline"],
  230. )
  231. with connection.schema_editor() as editor:
  232. editor.add_index(IndexedArticle2, index)
  233. with editor.connection.cursor() as cursor:
  234. cursor.execute(self.get_opclass_query % index_name)
  235. self.assertCountEqual(cursor.fetchall(), [("text_pattern_ops", index_name)])
  236. @skipUnlessDBFeature("supports_covering_indexes")
  237. def test_ops_class_include_tablespace(self):
  238. index_name = "test_ops_class_include_tblspace"
  239. index = Index(
  240. name=index_name,
  241. fields=["body"],
  242. opclasses=["text_pattern_ops"],
  243. include=["headline"],
  244. db_tablespace="pg_default",
  245. )
  246. with connection.schema_editor() as editor:
  247. editor.add_index(IndexedArticle2, index)
  248. self.assertIn(
  249. 'TABLESPACE "pg_default"',
  250. str(index.create_sql(IndexedArticle2, editor)),
  251. )
  252. with editor.connection.cursor() as cursor:
  253. cursor.execute(self.get_opclass_query % index_name)
  254. self.assertCountEqual(cursor.fetchall(), [("text_pattern_ops", index_name)])
  255. def test_ops_class_columns_lists_sql(self):
  256. index = Index(
  257. fields=["headline"],
  258. name="whitespace_idx",
  259. opclasses=["text_pattern_ops"],
  260. )
  261. with connection.schema_editor() as editor:
  262. self.assertIn(
  263. "(%s text_pattern_ops)" % editor.quote_name("headline"),
  264. str(index.create_sql(Article, editor)),
  265. )
  266. def test_ops_class_descending_columns_list_sql(self):
  267. index = Index(
  268. fields=["-headline"],
  269. name="whitespace_idx",
  270. opclasses=["text_pattern_ops"],
  271. )
  272. with connection.schema_editor() as editor:
  273. self.assertIn(
  274. "(%s text_pattern_ops DESC)" % editor.quote_name("headline"),
  275. str(index.create_sql(Article, editor)),
  276. )
  277. @skipUnless(connection.vendor == "mysql", "MySQL tests")
  278. class SchemaIndexesMySQLTests(TransactionTestCase):
  279. available_apps = ["indexes"]
  280. def test_no_index_for_foreignkey(self):
  281. """
  282. MySQL on InnoDB already creates indexes automatically for foreign keys.
  283. (#14180). An index should be created if db_constraint=False (#26171).
  284. """
  285. with connection.cursor() as cursor:
  286. storage = connection.introspection.get_storage_engine(
  287. cursor,
  288. ArticleTranslation._meta.db_table,
  289. )
  290. if storage != "InnoDB":
  291. self.skipTest("This test only applies to the InnoDB storage engine")
  292. index_sql = [
  293. str(statement)
  294. for statement in connection.schema_editor()._model_indexes_sql(
  295. ArticleTranslation
  296. )
  297. ]
  298. self.assertEqual(
  299. index_sql,
  300. [
  301. "CREATE INDEX "
  302. "`indexes_articletranslation_article_no_constraint_id_d6c0806b` "
  303. "ON `indexes_articletranslation` (`article_no_constraint_id`)"
  304. ],
  305. )
  306. # The index also shouldn't be created if the ForeignKey is added after
  307. # the model was created.
  308. field_created = False
  309. try:
  310. with connection.schema_editor() as editor:
  311. new_field = ForeignKey(Article, CASCADE)
  312. new_field.set_attributes_from_name("new_foreign_key")
  313. editor.add_field(ArticleTranslation, new_field)
  314. field_created = True
  315. # No deferred SQL. The FK constraint is included in the
  316. # statement to add the field.
  317. self.assertFalse(editor.deferred_sql)
  318. finally:
  319. if field_created:
  320. with connection.schema_editor() as editor:
  321. editor.remove_field(ArticleTranslation, new_field)
  322. @skipUnlessDBFeature("supports_partial_indexes")
  323. # SQLite doesn't support timezone-aware datetimes when USE_TZ is False.
  324. @override_settings(USE_TZ=True)
  325. class PartialIndexTests(TransactionTestCase):
  326. # Schema editor is used to create the index to test that it works.
  327. available_apps = ["indexes"]
  328. def test_partial_index(self):
  329. with connection.schema_editor() as editor:
  330. index = Index(
  331. name="recent_article_idx",
  332. fields=["pub_date"],
  333. condition=Q(
  334. pub_date__gt=datetime.datetime(
  335. year=2015,
  336. month=1,
  337. day=1,
  338. # PostgreSQL would otherwise complain about the lookup
  339. # being converted to a mutable function (by removing
  340. # the timezone in the cast) which is forbidden.
  341. tzinfo=timezone.get_current_timezone(),
  342. ),
  343. ),
  344. )
  345. self.assertIn(
  346. "WHERE %s" % editor.quote_name("pub_date"),
  347. str(index.create_sql(Article, schema_editor=editor)),
  348. )
  349. editor.add_index(index=index, model=Article)
  350. with connection.cursor() as cursor:
  351. self.assertIn(
  352. index.name,
  353. connection.introspection.get_constraints(
  354. cursor=cursor,
  355. table_name=Article._meta.db_table,
  356. ),
  357. )
  358. editor.remove_index(index=index, model=Article)
  359. def test_integer_restriction_partial(self):
  360. with connection.schema_editor() as editor:
  361. index = Index(
  362. name="recent_article_idx",
  363. fields=["id"],
  364. condition=Q(pk__gt=1),
  365. )
  366. self.assertIn(
  367. "WHERE %s" % editor.quote_name("id"),
  368. str(index.create_sql(Article, schema_editor=editor)),
  369. )
  370. editor.add_index(index=index, model=Article)
  371. with connection.cursor() as cursor:
  372. self.assertIn(
  373. index.name,
  374. connection.introspection.get_constraints(
  375. cursor=cursor,
  376. table_name=Article._meta.db_table,
  377. ),
  378. )
  379. editor.remove_index(index=index, model=Article)
  380. def test_boolean_restriction_partial(self):
  381. with connection.schema_editor() as editor:
  382. index = Index(
  383. name="published_index",
  384. fields=["published"],
  385. condition=Q(published=True),
  386. )
  387. self.assertIn(
  388. "WHERE %s" % editor.quote_name("published"),
  389. str(index.create_sql(Article, schema_editor=editor)),
  390. )
  391. editor.add_index(index=index, model=Article)
  392. with connection.cursor() as cursor:
  393. self.assertIn(
  394. index.name,
  395. connection.introspection.get_constraints(
  396. cursor=cursor,
  397. table_name=Article._meta.db_table,
  398. ),
  399. )
  400. editor.remove_index(index=index, model=Article)
  401. @skipUnlessDBFeature("supports_functions_in_partial_indexes")
  402. def test_multiple_conditions(self):
  403. with connection.schema_editor() as editor:
  404. index = Index(
  405. name="recent_article_idx",
  406. fields=["pub_date", "headline"],
  407. condition=(
  408. Q(
  409. pub_date__gt=datetime.datetime(
  410. year=2015,
  411. month=1,
  412. day=1,
  413. tzinfo=timezone.get_current_timezone(),
  414. )
  415. )
  416. & Q(headline__contains="China")
  417. ),
  418. )
  419. sql = str(index.create_sql(Article, schema_editor=editor))
  420. where = sql.find("WHERE")
  421. self.assertIn("WHERE (%s" % editor.quote_name("pub_date"), sql)
  422. # Because each backend has different syntax for the operators,
  423. # check ONLY the occurrence of headline in the SQL.
  424. self.assertGreater(sql.rfind("headline"), where)
  425. editor.add_index(index=index, model=Article)
  426. with connection.cursor() as cursor:
  427. self.assertIn(
  428. index.name,
  429. connection.introspection.get_constraints(
  430. cursor=cursor,
  431. table_name=Article._meta.db_table,
  432. ),
  433. )
  434. editor.remove_index(index=index, model=Article)
  435. def test_is_null_condition(self):
  436. with connection.schema_editor() as editor:
  437. index = Index(
  438. name="recent_article_idx",
  439. fields=["pub_date"],
  440. condition=Q(pub_date__isnull=False),
  441. )
  442. self.assertIn(
  443. "WHERE %s IS NOT NULL" % editor.quote_name("pub_date"),
  444. str(index.create_sql(Article, schema_editor=editor)),
  445. )
  446. editor.add_index(index=index, model=Article)
  447. with connection.cursor() as cursor:
  448. self.assertIn(
  449. index.name,
  450. connection.introspection.get_constraints(
  451. cursor=cursor,
  452. table_name=Article._meta.db_table,
  453. ),
  454. )
  455. editor.remove_index(index=index, model=Article)
  456. @skipUnlessDBFeature("supports_expression_indexes")
  457. def test_partial_func_index(self):
  458. index_name = "partial_func_idx"
  459. index = Index(
  460. Lower("headline").desc(),
  461. name=index_name,
  462. condition=Q(pub_date__isnull=False),
  463. )
  464. with connection.schema_editor() as editor:
  465. editor.add_index(index=index, model=Article)
  466. sql = index.create_sql(Article, schema_editor=editor)
  467. table = Article._meta.db_table
  468. self.assertIs(sql.references_column(table, "headline"), True)
  469. sql = str(sql)
  470. self.assertIn("LOWER(%s)" % editor.quote_name("headline"), sql)
  471. self.assertIn(
  472. "WHERE %s IS NOT NULL" % editor.quote_name("pub_date"),
  473. sql,
  474. )
  475. self.assertGreater(sql.find("WHERE"), sql.find("LOWER"))
  476. with connection.cursor() as cursor:
  477. constraints = connection.introspection.get_constraints(
  478. cursor=cursor,
  479. table_name=table,
  480. )
  481. self.assertIn(index_name, constraints)
  482. if connection.features.supports_index_column_ordering:
  483. self.assertEqual(constraints[index_name]["orders"], ["DESC"])
  484. with connection.schema_editor() as editor:
  485. editor.remove_index(Article, index)
  486. with connection.cursor() as cursor:
  487. self.assertNotIn(
  488. index_name,
  489. connection.introspection.get_constraints(
  490. cursor=cursor,
  491. table_name=table,
  492. ),
  493. )
  494. @skipUnlessDBFeature("supports_covering_indexes")
  495. class CoveringIndexTests(TransactionTestCase):
  496. available_apps = ["indexes"]
  497. def test_covering_index(self):
  498. index = Index(
  499. name="covering_headline_idx",
  500. fields=["headline"],
  501. include=["pub_date", "published"],
  502. )
  503. with connection.schema_editor() as editor:
  504. self.assertIn(
  505. "(%s) INCLUDE (%s, %s)"
  506. % (
  507. editor.quote_name("headline"),
  508. editor.quote_name("pub_date"),
  509. editor.quote_name("published"),
  510. ),
  511. str(index.create_sql(Article, editor)),
  512. )
  513. editor.add_index(Article, index)
  514. with connection.cursor() as cursor:
  515. constraints = connection.introspection.get_constraints(
  516. cursor=cursor,
  517. table_name=Article._meta.db_table,
  518. )
  519. self.assertIn(index.name, constraints)
  520. self.assertEqual(
  521. constraints[index.name]["columns"],
  522. ["headline", "pub_date", "published"],
  523. )
  524. editor.remove_index(Article, index)
  525. with connection.cursor() as cursor:
  526. self.assertNotIn(
  527. index.name,
  528. connection.introspection.get_constraints(
  529. cursor=cursor,
  530. table_name=Article._meta.db_table,
  531. ),
  532. )
  533. def test_covering_partial_index(self):
  534. index = Index(
  535. name="covering_partial_headline_idx",
  536. fields=["headline"],
  537. include=["pub_date"],
  538. condition=Q(pub_date__isnull=False),
  539. )
  540. with connection.schema_editor() as editor:
  541. extra_sql = ""
  542. if settings.DEFAULT_INDEX_TABLESPACE:
  543. extra_sql = "TABLESPACE %s " % editor.quote_name(
  544. settings.DEFAULT_INDEX_TABLESPACE
  545. )
  546. self.assertIn(
  547. "(%s) INCLUDE (%s) %sWHERE %s "
  548. % (
  549. editor.quote_name("headline"),
  550. editor.quote_name("pub_date"),
  551. extra_sql,
  552. editor.quote_name("pub_date"),
  553. ),
  554. str(index.create_sql(Article, editor)),
  555. )
  556. editor.add_index(Article, index)
  557. with connection.cursor() as cursor:
  558. constraints = connection.introspection.get_constraints(
  559. cursor=cursor,
  560. table_name=Article._meta.db_table,
  561. )
  562. self.assertIn(index.name, constraints)
  563. self.assertEqual(
  564. constraints[index.name]["columns"],
  565. ["headline", "pub_date"],
  566. )
  567. editor.remove_index(Article, index)
  568. with connection.cursor() as cursor:
  569. self.assertNotIn(
  570. index.name,
  571. connection.introspection.get_constraints(
  572. cursor=cursor,
  573. table_name=Article._meta.db_table,
  574. ),
  575. )
  576. @skipUnlessDBFeature("supports_expression_indexes")
  577. def test_covering_func_index(self):
  578. index_name = "covering_func_headline_idx"
  579. index = Index(Lower("headline"), name=index_name, include=["pub_date"])
  580. with connection.schema_editor() as editor:
  581. editor.add_index(index=index, model=Article)
  582. sql = index.create_sql(Article, schema_editor=editor)
  583. table = Article._meta.db_table
  584. self.assertIs(sql.references_column(table, "headline"), True)
  585. sql = str(sql)
  586. self.assertIn("LOWER(%s)" % editor.quote_name("headline"), sql)
  587. self.assertIn("INCLUDE (%s)" % editor.quote_name("pub_date"), sql)
  588. self.assertGreater(sql.find("INCLUDE"), sql.find("LOWER"))
  589. with connection.cursor() as cursor:
  590. constraints = connection.introspection.get_constraints(
  591. cursor=cursor,
  592. table_name=table,
  593. )
  594. self.assertIn(index_name, constraints)
  595. self.assertIn("pub_date", constraints[index_name]["columns"])
  596. with connection.schema_editor() as editor:
  597. editor.remove_index(Article, index)
  598. with connection.cursor() as cursor:
  599. self.assertNotIn(
  600. index_name,
  601. connection.introspection.get_constraints(
  602. cursor=cursor,
  603. table_name=table,
  604. ),
  605. )
  606. @skipIfDBFeature("supports_covering_indexes")
  607. class CoveringIndexIgnoredTests(TransactionTestCase):
  608. available_apps = ["indexes"]
  609. def test_covering_ignored(self):
  610. index = Index(
  611. name="test_covering_ignored",
  612. fields=["headline"],
  613. include=["pub_date"],
  614. )
  615. with connection.schema_editor() as editor:
  616. editor.add_index(Article, index)
  617. self.assertNotIn(
  618. "INCLUDE (%s)" % editor.quote_name("headline"),
  619. str(index.create_sql(Article, editor)),
  620. )