test_ddl.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. # testing/suite/test_ddl.py
  2. # Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. # mypy: ignore-errors
  8. import random
  9. from . import testing
  10. from .. import config
  11. from .. import fixtures
  12. from .. import util
  13. from ..assertions import eq_
  14. from ..assertions import is_false
  15. from ..assertions import is_true
  16. from ..config import requirements
  17. from ..schema import Table
  18. from ... import CheckConstraint
  19. from ... import Column
  20. from ... import ForeignKeyConstraint
  21. from ... import Index
  22. from ... import inspect
  23. from ... import Integer
  24. from ... import schema
  25. from ... import String
  26. from ... import UniqueConstraint
  27. class TableDDLTest(fixtures.TestBase):
  28. __backend__ = True
  29. def _simple_fixture(self, schema=None):
  30. return Table(
  31. "test_table",
  32. self.metadata,
  33. Column("id", Integer, primary_key=True, autoincrement=False),
  34. Column("data", String(50)),
  35. schema=schema,
  36. )
  37. def _underscore_fixture(self):
  38. return Table(
  39. "_test_table",
  40. self.metadata,
  41. Column("id", Integer, primary_key=True, autoincrement=False),
  42. Column("_data", String(50)),
  43. )
  44. def _table_index_fixture(self, schema=None):
  45. table = self._simple_fixture(schema=schema)
  46. idx = Index("test_index", table.c.data)
  47. return table, idx
  48. def _simple_roundtrip(self, table):
  49. with config.db.begin() as conn:
  50. conn.execute(table.insert().values((1, "some data")))
  51. result = conn.execute(table.select())
  52. eq_(result.first(), (1, "some data"))
  53. @requirements.create_table
  54. @util.provide_metadata
  55. def test_create_table(self):
  56. table = self._simple_fixture()
  57. table.create(config.db, checkfirst=False)
  58. self._simple_roundtrip(table)
  59. @requirements.create_table
  60. @requirements.schemas
  61. @util.provide_metadata
  62. def test_create_table_schema(self):
  63. table = self._simple_fixture(schema=config.test_schema)
  64. table.create(config.db, checkfirst=False)
  65. self._simple_roundtrip(table)
  66. @requirements.drop_table
  67. @util.provide_metadata
  68. def test_drop_table(self):
  69. table = self._simple_fixture()
  70. table.create(config.db, checkfirst=False)
  71. table.drop(config.db, checkfirst=False)
  72. @requirements.create_table
  73. @util.provide_metadata
  74. def test_underscore_names(self):
  75. table = self._underscore_fixture()
  76. table.create(config.db, checkfirst=False)
  77. self._simple_roundtrip(table)
  78. @requirements.comment_reflection
  79. @util.provide_metadata
  80. def test_add_table_comment(self, connection):
  81. table = self._simple_fixture()
  82. table.create(connection, checkfirst=False)
  83. table.comment = "a comment"
  84. connection.execute(schema.SetTableComment(table))
  85. eq_(
  86. inspect(connection).get_table_comment("test_table"),
  87. {"text": "a comment"},
  88. )
  89. @requirements.comment_reflection
  90. @util.provide_metadata
  91. def test_drop_table_comment(self, connection):
  92. table = self._simple_fixture()
  93. table.create(connection, checkfirst=False)
  94. table.comment = "a comment"
  95. connection.execute(schema.SetTableComment(table))
  96. connection.execute(schema.DropTableComment(table))
  97. eq_(
  98. inspect(connection).get_table_comment("test_table"), {"text": None}
  99. )
  100. @requirements.table_ddl_if_exists
  101. @util.provide_metadata
  102. def test_create_table_if_not_exists(self, connection):
  103. table = self._simple_fixture()
  104. connection.execute(schema.CreateTable(table, if_not_exists=True))
  105. is_true(inspect(connection).has_table("test_table"))
  106. connection.execute(schema.CreateTable(table, if_not_exists=True))
  107. @requirements.index_ddl_if_exists
  108. @util.provide_metadata
  109. def test_create_index_if_not_exists(self, connection):
  110. table, idx = self._table_index_fixture()
  111. connection.execute(schema.CreateTable(table, if_not_exists=True))
  112. is_true(inspect(connection).has_table("test_table"))
  113. is_false(
  114. "test_index"
  115. in [
  116. ix["name"]
  117. for ix in inspect(connection).get_indexes("test_table")
  118. ]
  119. )
  120. connection.execute(schema.CreateIndex(idx, if_not_exists=True))
  121. is_true(
  122. "test_index"
  123. in [
  124. ix["name"]
  125. for ix in inspect(connection).get_indexes("test_table")
  126. ]
  127. )
  128. connection.execute(schema.CreateIndex(idx, if_not_exists=True))
  129. @requirements.table_ddl_if_exists
  130. @util.provide_metadata
  131. def test_drop_table_if_exists(self, connection):
  132. table = self._simple_fixture()
  133. table.create(connection)
  134. is_true(inspect(connection).has_table("test_table"))
  135. connection.execute(schema.DropTable(table, if_exists=True))
  136. is_false(inspect(connection).has_table("test_table"))
  137. connection.execute(schema.DropTable(table, if_exists=True))
  138. @requirements.index_ddl_if_exists
  139. @util.provide_metadata
  140. def test_drop_index_if_exists(self, connection):
  141. table, idx = self._table_index_fixture()
  142. table.create(connection)
  143. is_true(
  144. "test_index"
  145. in [
  146. ix["name"]
  147. for ix in inspect(connection).get_indexes("test_table")
  148. ]
  149. )
  150. connection.execute(schema.DropIndex(idx, if_exists=True))
  151. is_false(
  152. "test_index"
  153. in [
  154. ix["name"]
  155. for ix in inspect(connection).get_indexes("test_table")
  156. ]
  157. )
  158. connection.execute(schema.DropIndex(idx, if_exists=True))
  159. class FutureTableDDLTest(fixtures.FutureEngineMixin, TableDDLTest):
  160. pass
  161. class LongNameBlowoutTest(fixtures.TestBase):
  162. """test the creation of a variety of DDL structures and ensure
  163. label length limits pass on backends
  164. """
  165. __backend__ = True
  166. def fk(self, metadata, connection):
  167. convention = {
  168. "fk": "foreign_key_%(table_name)s_"
  169. "%(column_0_N_name)s_"
  170. "%(referred_table_name)s_"
  171. + (
  172. "_".join(
  173. "".join(random.choice("abcdef") for j in range(20))
  174. for i in range(10)
  175. )
  176. ),
  177. }
  178. metadata.naming_convention = convention
  179. Table(
  180. "a_things_with_stuff",
  181. metadata,
  182. Column("id_long_column_name", Integer, primary_key=True),
  183. test_needs_fk=True,
  184. )
  185. cons = ForeignKeyConstraint(
  186. ["aid"], ["a_things_with_stuff.id_long_column_name"]
  187. )
  188. Table(
  189. "b_related_things_of_value",
  190. metadata,
  191. Column(
  192. "aid",
  193. ),
  194. cons,
  195. test_needs_fk=True,
  196. )
  197. actual_name = cons.name
  198. metadata.create_all(connection)
  199. if testing.requires.foreign_key_constraint_name_reflection.enabled:
  200. insp = inspect(connection)
  201. fks = insp.get_foreign_keys("b_related_things_of_value")
  202. reflected_name = fks[0]["name"]
  203. return actual_name, reflected_name
  204. else:
  205. return actual_name, None
  206. def pk(self, metadata, connection):
  207. convention = {
  208. "pk": "primary_key_%(table_name)s_"
  209. "%(column_0_N_name)s"
  210. + (
  211. "_".join(
  212. "".join(random.choice("abcdef") for j in range(30))
  213. for i in range(10)
  214. )
  215. ),
  216. }
  217. metadata.naming_convention = convention
  218. a = Table(
  219. "a_things_with_stuff",
  220. metadata,
  221. Column("id_long_column_name", Integer, primary_key=True),
  222. Column("id_another_long_name", Integer, primary_key=True),
  223. )
  224. cons = a.primary_key
  225. actual_name = cons.name
  226. metadata.create_all(connection)
  227. insp = inspect(connection)
  228. pk = insp.get_pk_constraint("a_things_with_stuff")
  229. reflected_name = pk["name"]
  230. return actual_name, reflected_name
  231. def ix(self, metadata, connection):
  232. convention = {
  233. "ix": "index_%(table_name)s_"
  234. "%(column_0_N_name)s"
  235. + (
  236. "_".join(
  237. "".join(random.choice("abcdef") for j in range(30))
  238. for i in range(10)
  239. )
  240. ),
  241. }
  242. metadata.naming_convention = convention
  243. a = Table(
  244. "a_things_with_stuff",
  245. metadata,
  246. Column("id_long_column_name", Integer, primary_key=True),
  247. Column("id_another_long_name", Integer),
  248. )
  249. cons = Index(None, a.c.id_long_column_name, a.c.id_another_long_name)
  250. actual_name = cons.name
  251. metadata.create_all(connection)
  252. insp = inspect(connection)
  253. ix = insp.get_indexes("a_things_with_stuff")
  254. reflected_name = ix[0]["name"]
  255. return actual_name, reflected_name
  256. def uq(self, metadata, connection):
  257. convention = {
  258. "uq": "unique_constraint_%(table_name)s_"
  259. "%(column_0_N_name)s"
  260. + (
  261. "_".join(
  262. "".join(random.choice("abcdef") for j in range(30))
  263. for i in range(10)
  264. )
  265. ),
  266. }
  267. metadata.naming_convention = convention
  268. cons = UniqueConstraint("id_long_column_name", "id_another_long_name")
  269. Table(
  270. "a_things_with_stuff",
  271. metadata,
  272. Column("id_long_column_name", Integer, primary_key=True),
  273. Column("id_another_long_name", Integer),
  274. cons,
  275. )
  276. actual_name = cons.name
  277. metadata.create_all(connection)
  278. insp = inspect(connection)
  279. uq = insp.get_unique_constraints("a_things_with_stuff")
  280. reflected_name = uq[0]["name"]
  281. return actual_name, reflected_name
  282. def ck(self, metadata, connection):
  283. convention = {
  284. "ck": "check_constraint_%(table_name)s"
  285. + (
  286. "_".join(
  287. "".join(random.choice("abcdef") for j in range(30))
  288. for i in range(10)
  289. )
  290. ),
  291. }
  292. metadata.naming_convention = convention
  293. cons = CheckConstraint("some_long_column_name > 5")
  294. Table(
  295. "a_things_with_stuff",
  296. metadata,
  297. Column("id_long_column_name", Integer, primary_key=True),
  298. Column("some_long_column_name", Integer),
  299. cons,
  300. )
  301. actual_name = cons.name
  302. metadata.create_all(connection)
  303. insp = inspect(connection)
  304. ck = insp.get_check_constraints("a_things_with_stuff")
  305. reflected_name = ck[0]["name"]
  306. return actual_name, reflected_name
  307. @testing.combinations(
  308. ("fk",),
  309. ("pk",),
  310. ("ix",),
  311. ("ck", testing.requires.check_constraint_reflection.as_skips()),
  312. ("uq", testing.requires.unique_constraint_reflection.as_skips()),
  313. argnames="type_",
  314. )
  315. def test_long_convention_name(self, type_, metadata, connection):
  316. actual_name, reflected_name = getattr(self, type_)(
  317. metadata, connection
  318. )
  319. assert len(actual_name) > 255
  320. if reflected_name is not None:
  321. overlap = actual_name[0 : len(reflected_name)]
  322. if len(overlap) < len(actual_name):
  323. eq_(overlap[0:-5], reflected_name[0 : len(overlap) - 5])
  324. else:
  325. eq_(overlap, reflected_name)
  326. __all__ = ("TableDDLTest", "FutureTableDDLTest", "LongNameBlowoutTest")