compare.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370
  1. # mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
  2. # mypy: no-warn-return-any, allow-any-generics
  3. from __future__ import annotations
  4. import contextlib
  5. import logging
  6. import re
  7. from typing import Any
  8. from typing import cast
  9. from typing import Dict
  10. from typing import Iterator
  11. from typing import Mapping
  12. from typing import Optional
  13. from typing import Set
  14. from typing import Tuple
  15. from typing import TYPE_CHECKING
  16. from typing import TypeVar
  17. from typing import Union
  18. from sqlalchemy import event
  19. from sqlalchemy import inspect
  20. from sqlalchemy import schema as sa_schema
  21. from sqlalchemy import text
  22. from sqlalchemy import types as sqltypes
  23. from sqlalchemy.sql import expression
  24. from sqlalchemy.sql.elements import conv
  25. from sqlalchemy.sql.schema import ForeignKeyConstraint
  26. from sqlalchemy.sql.schema import Index
  27. from sqlalchemy.sql.schema import UniqueConstraint
  28. from sqlalchemy.util import OrderedSet
  29. from .. import util
  30. from ..ddl._autogen import is_index_sig
  31. from ..ddl._autogen import is_uq_sig
  32. from ..operations import ops
  33. from ..util import sqla_compat
  34. if TYPE_CHECKING:
  35. from typing import Literal
  36. from sqlalchemy.engine.reflection import Inspector
  37. from sqlalchemy.sql.elements import quoted_name
  38. from sqlalchemy.sql.elements import TextClause
  39. from sqlalchemy.sql.schema import Column
  40. from sqlalchemy.sql.schema import Table
  41. from alembic.autogenerate.api import AutogenContext
  42. from alembic.ddl.impl import DefaultImpl
  43. from alembic.operations.ops import AlterColumnOp
  44. from alembic.operations.ops import MigrationScript
  45. from alembic.operations.ops import ModifyTableOps
  46. from alembic.operations.ops import UpgradeOps
  47. from ..ddl._autogen import _constraint_sig
  48. log = logging.getLogger(__name__)
  49. def _populate_migration_script(
  50. autogen_context: AutogenContext, migration_script: MigrationScript
  51. ) -> None:
  52. upgrade_ops = migration_script.upgrade_ops_list[-1]
  53. downgrade_ops = migration_script.downgrade_ops_list[-1]
  54. _produce_net_changes(autogen_context, upgrade_ops)
  55. upgrade_ops.reverse_into(downgrade_ops)
  56. comparators = util.Dispatcher(uselist=True)
  57. def _produce_net_changes(
  58. autogen_context: AutogenContext, upgrade_ops: UpgradeOps
  59. ) -> None:
  60. connection = autogen_context.connection
  61. assert connection is not None
  62. include_schemas = autogen_context.opts.get("include_schemas", False)
  63. inspector: Inspector = inspect(connection)
  64. default_schema = connection.dialect.default_schema_name
  65. schemas: Set[Optional[str]]
  66. if include_schemas:
  67. schemas = set(inspector.get_schema_names())
  68. # replace default schema name with None
  69. schemas.discard("information_schema")
  70. # replace the "default" schema with None
  71. schemas.discard(default_schema)
  72. schemas.add(None)
  73. else:
  74. schemas = {None}
  75. schemas = {
  76. s for s in schemas if autogen_context.run_name_filters(s, "schema", {})
  77. }
  78. assert autogen_context.dialect is not None
  79. comparators.dispatch("schema", autogen_context.dialect.name)(
  80. autogen_context, upgrade_ops, schemas
  81. )
  82. @comparators.dispatch_for("schema")
  83. def _autogen_for_tables(
  84. autogen_context: AutogenContext,
  85. upgrade_ops: UpgradeOps,
  86. schemas: Union[Set[None], Set[Optional[str]]],
  87. ) -> None:
  88. inspector = autogen_context.inspector
  89. conn_table_names: Set[Tuple[Optional[str], str]] = set()
  90. version_table_schema = (
  91. autogen_context.migration_context.version_table_schema
  92. )
  93. version_table = autogen_context.migration_context.version_table
  94. for schema_name in schemas:
  95. tables = set(inspector.get_table_names(schema=schema_name))
  96. if schema_name == version_table_schema:
  97. tables = tables.difference(
  98. [autogen_context.migration_context.version_table]
  99. )
  100. conn_table_names.update(
  101. (schema_name, tname)
  102. for tname in tables
  103. if autogen_context.run_name_filters(
  104. tname, "table", {"schema_name": schema_name}
  105. )
  106. )
  107. metadata_table_names = OrderedSet(
  108. [(table.schema, table.name) for table in autogen_context.sorted_tables]
  109. ).difference([(version_table_schema, version_table)])
  110. _compare_tables(
  111. conn_table_names,
  112. metadata_table_names,
  113. inspector,
  114. upgrade_ops,
  115. autogen_context,
  116. )
  117. def _compare_tables(
  118. conn_table_names: set,
  119. metadata_table_names: set,
  120. inspector: Inspector,
  121. upgrade_ops: UpgradeOps,
  122. autogen_context: AutogenContext,
  123. ) -> None:
  124. default_schema = inspector.bind.dialect.default_schema_name
  125. # tables coming from the connection will not have "schema"
  126. # set if it matches default_schema_name; so we need a list
  127. # of table names from local metadata that also have "None" if schema
  128. # == default_schema_name. Most setups will be like this anyway but
  129. # some are not (see #170)
  130. metadata_table_names_no_dflt_schema = OrderedSet(
  131. [
  132. (schema if schema != default_schema else None, tname)
  133. for schema, tname in metadata_table_names
  134. ]
  135. )
  136. # to adjust for the MetaData collection storing the tables either
  137. # as "schemaname.tablename" or just "tablename", create a new lookup
  138. # which will match the "non-default-schema" keys to the Table object.
  139. tname_to_table = {
  140. no_dflt_schema: autogen_context.table_key_to_table[
  141. sa_schema._get_table_key(tname, schema)
  142. ]
  143. for no_dflt_schema, (schema, tname) in zip(
  144. metadata_table_names_no_dflt_schema, metadata_table_names
  145. )
  146. }
  147. metadata_table_names = metadata_table_names_no_dflt_schema
  148. for s, tname in metadata_table_names.difference(conn_table_names):
  149. name = "%s.%s" % (s, tname) if s else tname
  150. metadata_table = tname_to_table[(s, tname)]
  151. if autogen_context.run_object_filters(
  152. metadata_table, tname, "table", False, None
  153. ):
  154. upgrade_ops.ops.append(
  155. ops.CreateTableOp.from_table(metadata_table)
  156. )
  157. log.info("Detected added table %r", name)
  158. modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
  159. comparators.dispatch("table")(
  160. autogen_context,
  161. modify_table_ops,
  162. s,
  163. tname,
  164. None,
  165. metadata_table,
  166. )
  167. if not modify_table_ops.is_empty():
  168. upgrade_ops.ops.append(modify_table_ops)
  169. removal_metadata = sa_schema.MetaData()
  170. for s, tname in conn_table_names.difference(metadata_table_names):
  171. name = sa_schema._get_table_key(tname, s)
  172. exists = name in removal_metadata.tables
  173. t = sa_schema.Table(tname, removal_metadata, schema=s)
  174. if not exists:
  175. event.listen(
  176. t,
  177. "column_reflect",
  178. # fmt: off
  179. autogen_context.migration_context.impl.
  180. _compat_autogen_column_reflect
  181. (inspector),
  182. # fmt: on
  183. )
  184. _InspectorConv(inspector).reflect_table(t, include_columns=None)
  185. if autogen_context.run_object_filters(t, tname, "table", True, None):
  186. modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
  187. comparators.dispatch("table")(
  188. autogen_context, modify_table_ops, s, tname, t, None
  189. )
  190. if not modify_table_ops.is_empty():
  191. upgrade_ops.ops.append(modify_table_ops)
  192. upgrade_ops.ops.append(ops.DropTableOp.from_table(t))
  193. log.info("Detected removed table %r", name)
  194. existing_tables = conn_table_names.intersection(metadata_table_names)
  195. existing_metadata = sa_schema.MetaData()
  196. conn_column_info = {}
  197. for s, tname in existing_tables:
  198. name = sa_schema._get_table_key(tname, s)
  199. exists = name in existing_metadata.tables
  200. t = sa_schema.Table(tname, existing_metadata, schema=s)
  201. if not exists:
  202. event.listen(
  203. t,
  204. "column_reflect",
  205. # fmt: off
  206. autogen_context.migration_context.impl.
  207. _compat_autogen_column_reflect(inspector),
  208. # fmt: on
  209. )
  210. _InspectorConv(inspector).reflect_table(t, include_columns=None)
  211. conn_column_info[(s, tname)] = t
  212. for s, tname in sorted(existing_tables, key=lambda x: (x[0] or "", x[1])):
  213. s = s or None
  214. name = "%s.%s" % (s, tname) if s else tname
  215. metadata_table = tname_to_table[(s, tname)]
  216. conn_table = existing_metadata.tables[name]
  217. if autogen_context.run_object_filters(
  218. metadata_table, tname, "table", False, conn_table
  219. ):
  220. modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
  221. with _compare_columns(
  222. s,
  223. tname,
  224. conn_table,
  225. metadata_table,
  226. modify_table_ops,
  227. autogen_context,
  228. inspector,
  229. ):
  230. comparators.dispatch("table")(
  231. autogen_context,
  232. modify_table_ops,
  233. s,
  234. tname,
  235. conn_table,
  236. metadata_table,
  237. )
  238. if not modify_table_ops.is_empty():
  239. upgrade_ops.ops.append(modify_table_ops)
  240. _IndexColumnSortingOps: Mapping[str, Any] = util.immutabledict(
  241. {
  242. "asc": expression.asc,
  243. "desc": expression.desc,
  244. "nulls_first": expression.nullsfirst,
  245. "nulls_last": expression.nullslast,
  246. "nullsfirst": expression.nullsfirst, # 1_3 name
  247. "nullslast": expression.nullslast, # 1_3 name
  248. }
  249. )
  250. def _make_index(
  251. impl: DefaultImpl, params: Dict[str, Any], conn_table: Table
  252. ) -> Optional[Index]:
  253. exprs: list[Union[Column[Any], TextClause]] = []
  254. sorting = params.get("column_sorting")
  255. for num, col_name in enumerate(params["column_names"]):
  256. item: Union[Column[Any], TextClause]
  257. if col_name is None:
  258. assert "expressions" in params
  259. name = params["expressions"][num]
  260. item = text(name)
  261. else:
  262. name = col_name
  263. item = conn_table.c[col_name]
  264. if sorting and name in sorting:
  265. for operator in sorting[name]:
  266. if operator in _IndexColumnSortingOps:
  267. item = _IndexColumnSortingOps[operator](item)
  268. exprs.append(item)
  269. ix = sa_schema.Index(
  270. params["name"],
  271. *exprs,
  272. unique=params["unique"],
  273. _table=conn_table,
  274. **impl.adjust_reflected_dialect_options(params, "index"),
  275. )
  276. if "duplicates_constraint" in params:
  277. ix.info["duplicates_constraint"] = params["duplicates_constraint"]
  278. return ix
  279. def _make_unique_constraint(
  280. impl: DefaultImpl, params: Dict[str, Any], conn_table: Table
  281. ) -> UniqueConstraint:
  282. uq = sa_schema.UniqueConstraint(
  283. *[conn_table.c[cname] for cname in params["column_names"]],
  284. name=params["name"],
  285. **impl.adjust_reflected_dialect_options(params, "unique_constraint"),
  286. )
  287. if "duplicates_index" in params:
  288. uq.info["duplicates_index"] = params["duplicates_index"]
  289. return uq
  290. def _make_foreign_key(
  291. params: Dict[str, Any], conn_table: Table
  292. ) -> ForeignKeyConstraint:
  293. tname = params["referred_table"]
  294. if params["referred_schema"]:
  295. tname = "%s.%s" % (params["referred_schema"], tname)
  296. options = params.get("options", {})
  297. const = sa_schema.ForeignKeyConstraint(
  298. [conn_table.c[cname] for cname in params["constrained_columns"]],
  299. ["%s.%s" % (tname, n) for n in params["referred_columns"]],
  300. onupdate=options.get("onupdate"),
  301. ondelete=options.get("ondelete"),
  302. deferrable=options.get("deferrable"),
  303. initially=options.get("initially"),
  304. name=params["name"],
  305. )
  306. # needed by 0.7
  307. conn_table.append_constraint(const)
  308. return const
  309. @contextlib.contextmanager
  310. def _compare_columns(
  311. schema: Optional[str],
  312. tname: Union[quoted_name, str],
  313. conn_table: Table,
  314. metadata_table: Table,
  315. modify_table_ops: ModifyTableOps,
  316. autogen_context: AutogenContext,
  317. inspector: Inspector,
  318. ) -> Iterator[None]:
  319. name = "%s.%s" % (schema, tname) if schema else tname
  320. metadata_col_names = OrderedSet(
  321. c.name for c in metadata_table.c if not c.system
  322. )
  323. metadata_cols_by_name = {
  324. c.name: c for c in metadata_table.c if not c.system
  325. }
  326. conn_col_names = {
  327. c.name: c
  328. for c in conn_table.c
  329. if autogen_context.run_name_filters(
  330. c.name, "column", {"table_name": tname, "schema_name": schema}
  331. )
  332. }
  333. for cname in metadata_col_names.difference(conn_col_names):
  334. if autogen_context.run_object_filters(
  335. metadata_cols_by_name[cname], cname, "column", False, None
  336. ):
  337. modify_table_ops.ops.append(
  338. ops.AddColumnOp.from_column_and_tablename(
  339. schema, tname, metadata_cols_by_name[cname]
  340. )
  341. )
  342. log.info("Detected added column '%s.%s'", name, cname)
  343. for colname in metadata_col_names.intersection(conn_col_names):
  344. metadata_col = metadata_cols_by_name[colname]
  345. conn_col = conn_table.c[colname]
  346. if not autogen_context.run_object_filters(
  347. metadata_col, colname, "column", False, conn_col
  348. ):
  349. continue
  350. alter_column_op = ops.AlterColumnOp(tname, colname, schema=schema)
  351. comparators.dispatch("column")(
  352. autogen_context,
  353. alter_column_op,
  354. schema,
  355. tname,
  356. colname,
  357. conn_col,
  358. metadata_col,
  359. )
  360. if alter_column_op.has_changes():
  361. modify_table_ops.ops.append(alter_column_op)
  362. yield
  363. for cname in set(conn_col_names).difference(metadata_col_names):
  364. if autogen_context.run_object_filters(
  365. conn_table.c[cname], cname, "column", True, None
  366. ):
  367. modify_table_ops.ops.append(
  368. ops.DropColumnOp.from_column_and_tablename(
  369. schema, tname, conn_table.c[cname]
  370. )
  371. )
  372. log.info("Detected removed column '%s.%s'", name, cname)
  373. _C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index])
  374. class _InspectorConv:
  375. __slots__ = ("inspector",)
  376. def __init__(self, inspector):
  377. self.inspector = inspector
  378. def _apply_reflectinfo_conv(self, consts):
  379. if not consts:
  380. return consts
  381. for const in consts:
  382. if const["name"] is not None and not isinstance(
  383. const["name"], conv
  384. ):
  385. const["name"] = conv(const["name"])
  386. return consts
  387. def _apply_constraint_conv(self, consts):
  388. if not consts:
  389. return consts
  390. for const in consts:
  391. if const.name is not None and not isinstance(const.name, conv):
  392. const.name = conv(const.name)
  393. return consts
  394. def get_indexes(self, *args, **kw):
  395. return self._apply_reflectinfo_conv(
  396. self.inspector.get_indexes(*args, **kw)
  397. )
  398. def get_unique_constraints(self, *args, **kw):
  399. return self._apply_reflectinfo_conv(
  400. self.inspector.get_unique_constraints(*args, **kw)
  401. )
  402. def get_foreign_keys(self, *args, **kw):
  403. return self._apply_reflectinfo_conv(
  404. self.inspector.get_foreign_keys(*args, **kw)
  405. )
  406. def reflect_table(self, table, *, include_columns):
  407. self.inspector.reflect_table(table, include_columns=include_columns)
  408. # I had a cool version of this using _ReflectInfo, however that doesn't
  409. # work in 1.4 and it's not public API in 2.x. Then this is just a two
  410. # liner. So there's no competition...
  411. self._apply_constraint_conv(table.constraints)
  412. self._apply_constraint_conv(table.indexes)
  413. @comparators.dispatch_for("table")
  414. def _compare_indexes_and_uniques(
  415. autogen_context: AutogenContext,
  416. modify_ops: ModifyTableOps,
  417. schema: Optional[str],
  418. tname: Union[quoted_name, str],
  419. conn_table: Optional[Table],
  420. metadata_table: Optional[Table],
  421. ) -> None:
  422. inspector = autogen_context.inspector
  423. is_create_table = conn_table is None
  424. is_drop_table = metadata_table is None
  425. impl = autogen_context.migration_context.impl
  426. # 1a. get raw indexes and unique constraints from metadata ...
  427. if metadata_table is not None:
  428. metadata_unique_constraints = {
  429. uq
  430. for uq in metadata_table.constraints
  431. if isinstance(uq, sa_schema.UniqueConstraint)
  432. }
  433. metadata_indexes = set(metadata_table.indexes)
  434. else:
  435. metadata_unique_constraints = set()
  436. metadata_indexes = set()
  437. conn_uniques = conn_indexes = frozenset() # type:ignore[var-annotated]
  438. supports_unique_constraints = False
  439. unique_constraints_duplicate_unique_indexes = False
  440. if conn_table is not None:
  441. # 1b. ... and from connection, if the table exists
  442. try:
  443. conn_uniques = _InspectorConv(inspector).get_unique_constraints(
  444. tname, schema=schema
  445. )
  446. supports_unique_constraints = True
  447. except NotImplementedError:
  448. pass
  449. except TypeError:
  450. # number of arguments is off for the base
  451. # method in SQLAlchemy due to the cache decorator
  452. # not being present
  453. pass
  454. else:
  455. conn_uniques = [ # type:ignore[assignment]
  456. uq
  457. for uq in conn_uniques
  458. if autogen_context.run_name_filters(
  459. uq["name"],
  460. "unique_constraint",
  461. {"table_name": tname, "schema_name": schema},
  462. )
  463. ]
  464. for uq in conn_uniques:
  465. if uq.get("duplicates_index"):
  466. unique_constraints_duplicate_unique_indexes = True
  467. try:
  468. conn_indexes = _InspectorConv(inspector).get_indexes(
  469. tname, schema=schema
  470. )
  471. except NotImplementedError:
  472. pass
  473. else:
  474. conn_indexes = [ # type:ignore[assignment]
  475. ix
  476. for ix in conn_indexes
  477. if autogen_context.run_name_filters(
  478. ix["name"],
  479. "index",
  480. {"table_name": tname, "schema_name": schema},
  481. )
  482. ]
  483. # 2. convert conn-level objects from raw inspector records
  484. # into schema objects
  485. if is_drop_table:
  486. # for DROP TABLE uniques are inline, don't need them
  487. conn_uniques = set() # type:ignore[assignment]
  488. else:
  489. conn_uniques = { # type:ignore[assignment]
  490. _make_unique_constraint(impl, uq_def, conn_table)
  491. for uq_def in conn_uniques
  492. }
  493. conn_indexes = { # type:ignore[assignment]
  494. index
  495. for index in (
  496. _make_index(impl, ix, conn_table) for ix in conn_indexes
  497. )
  498. if index is not None
  499. }
  500. # 2a. if the dialect dupes unique indexes as unique constraints
  501. # (mysql and oracle), correct for that
  502. if unique_constraints_duplicate_unique_indexes:
  503. _correct_for_uq_duplicates_uix(
  504. conn_uniques,
  505. conn_indexes,
  506. metadata_unique_constraints,
  507. metadata_indexes,
  508. autogen_context.dialect,
  509. impl,
  510. )
  511. # 3. give the dialect a chance to omit indexes and constraints that
  512. # we know are either added implicitly by the DB or that the DB
  513. # can't accurately report on
  514. impl.correct_for_autogen_constraints(
  515. conn_uniques, # type: ignore[arg-type]
  516. conn_indexes, # type: ignore[arg-type]
  517. metadata_unique_constraints,
  518. metadata_indexes,
  519. )
  520. # 4. organize the constraints into "signature" collections, the
  521. # _constraint_sig() objects provide a consistent facade over both
  522. # Index and UniqueConstraint so we can easily work with them
  523. # interchangeably
  524. metadata_unique_constraints_sig = {
  525. impl._create_metadata_constraint_sig(uq)
  526. for uq in metadata_unique_constraints
  527. }
  528. metadata_indexes_sig = {
  529. impl._create_metadata_constraint_sig(ix) for ix in metadata_indexes
  530. }
  531. conn_unique_constraints = {
  532. impl._create_reflected_constraint_sig(uq) for uq in conn_uniques
  533. }
  534. conn_indexes_sig = {
  535. impl._create_reflected_constraint_sig(ix) for ix in conn_indexes
  536. }
  537. # 5. index things by name, for those objects that have names
  538. metadata_names = {
  539. cast(str, c.md_name_to_sql_name(autogen_context)): c
  540. for c in metadata_unique_constraints_sig.union(metadata_indexes_sig)
  541. if c.is_named
  542. }
  543. conn_uniques_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig]
  544. conn_indexes_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig]
  545. conn_uniques_by_name = {c.name: c for c in conn_unique_constraints}
  546. conn_indexes_by_name = {c.name: c for c in conn_indexes_sig}
  547. conn_names = {
  548. c.name: c
  549. for c in conn_unique_constraints.union(conn_indexes_sig)
  550. if sqla_compat.constraint_name_string(c.name)
  551. }
  552. doubled_constraints = {
  553. name: (conn_uniques_by_name[name], conn_indexes_by_name[name])
  554. for name in set(conn_uniques_by_name).intersection(
  555. conn_indexes_by_name
  556. )
  557. }
  558. # 6. index things by "column signature", to help with unnamed unique
  559. # constraints.
  560. conn_uniques_by_sig = {uq.unnamed: uq for uq in conn_unique_constraints}
  561. metadata_uniques_by_sig = {
  562. uq.unnamed: uq for uq in metadata_unique_constraints_sig
  563. }
  564. unnamed_metadata_uniques = {
  565. uq.unnamed: uq
  566. for uq in metadata_unique_constraints_sig
  567. if not sqla_compat._constraint_is_named(
  568. uq.const, autogen_context.dialect
  569. )
  570. }
  571. # assumptions:
  572. # 1. a unique constraint or an index from the connection *always*
  573. # has a name.
  574. # 2. an index on the metadata side *always* has a name.
  575. # 3. a unique constraint on the metadata side *might* have a name.
  576. # 4. The backend may double up indexes as unique constraints and
  577. # vice versa (e.g. MySQL, Postgresql)
  578. def obj_added(obj: _constraint_sig):
  579. if is_index_sig(obj):
  580. if autogen_context.run_object_filters(
  581. obj.const, obj.name, "index", False, None
  582. ):
  583. modify_ops.ops.append(ops.CreateIndexOp.from_index(obj.const))
  584. log.info(
  585. "Detected added index %r on '%s'",
  586. obj.name,
  587. obj.column_names,
  588. )
  589. elif is_uq_sig(obj):
  590. if not supports_unique_constraints:
  591. # can't report unique indexes as added if we don't
  592. # detect them
  593. return
  594. if is_create_table or is_drop_table:
  595. # unique constraints are created inline with table defs
  596. return
  597. if autogen_context.run_object_filters(
  598. obj.const, obj.name, "unique_constraint", False, None
  599. ):
  600. modify_ops.ops.append(
  601. ops.AddConstraintOp.from_constraint(obj.const)
  602. )
  603. log.info(
  604. "Detected added unique constraint %r on '%s'",
  605. obj.name,
  606. obj.column_names,
  607. )
  608. else:
  609. assert False
  610. def obj_removed(obj: _constraint_sig):
  611. if is_index_sig(obj):
  612. if obj.is_unique and not supports_unique_constraints:
  613. # many databases double up unique constraints
  614. # as unique indexes. without that list we can't
  615. # be sure what we're doing here
  616. return
  617. if autogen_context.run_object_filters(
  618. obj.const, obj.name, "index", True, None
  619. ):
  620. modify_ops.ops.append(ops.DropIndexOp.from_index(obj.const))
  621. log.info("Detected removed index %r on %r", obj.name, tname)
  622. elif is_uq_sig(obj):
  623. if is_create_table or is_drop_table:
  624. # if the whole table is being dropped, we don't need to
  625. # consider unique constraint separately
  626. return
  627. if autogen_context.run_object_filters(
  628. obj.const, obj.name, "unique_constraint", True, None
  629. ):
  630. modify_ops.ops.append(
  631. ops.DropConstraintOp.from_constraint(obj.const)
  632. )
  633. log.info(
  634. "Detected removed unique constraint %r on %r",
  635. obj.name,
  636. tname,
  637. )
  638. else:
  639. assert False
  640. def obj_changed(
  641. old: _constraint_sig,
  642. new: _constraint_sig,
  643. msg: str,
  644. ):
  645. if is_index_sig(old):
  646. assert is_index_sig(new)
  647. if autogen_context.run_object_filters(
  648. new.const, new.name, "index", False, old.const
  649. ):
  650. log.info(
  651. "Detected changed index %r on %r: %s", old.name, tname, msg
  652. )
  653. modify_ops.ops.append(ops.DropIndexOp.from_index(old.const))
  654. modify_ops.ops.append(ops.CreateIndexOp.from_index(new.const))
  655. elif is_uq_sig(old):
  656. assert is_uq_sig(new)
  657. if autogen_context.run_object_filters(
  658. new.const, new.name, "unique_constraint", False, old.const
  659. ):
  660. log.info(
  661. "Detected changed unique constraint %r on %r: %s",
  662. old.name,
  663. tname,
  664. msg,
  665. )
  666. modify_ops.ops.append(
  667. ops.DropConstraintOp.from_constraint(old.const)
  668. )
  669. modify_ops.ops.append(
  670. ops.AddConstraintOp.from_constraint(new.const)
  671. )
  672. else:
  673. assert False
  674. for removed_name in sorted(set(conn_names).difference(metadata_names)):
  675. conn_obj = conn_names[removed_name]
  676. if (
  677. is_uq_sig(conn_obj)
  678. and conn_obj.unnamed in unnamed_metadata_uniques
  679. ):
  680. continue
  681. elif removed_name in doubled_constraints:
  682. conn_uq, conn_idx = doubled_constraints[removed_name]
  683. if (
  684. all(
  685. conn_idx.unnamed != meta_idx.unnamed
  686. for meta_idx in metadata_indexes_sig
  687. )
  688. and conn_uq.unnamed not in metadata_uniques_by_sig
  689. ):
  690. obj_removed(conn_uq)
  691. obj_removed(conn_idx)
  692. else:
  693. obj_removed(conn_obj)
  694. for existing_name in sorted(set(metadata_names).intersection(conn_names)):
  695. metadata_obj = metadata_names[existing_name]
  696. if existing_name in doubled_constraints:
  697. conn_uq, conn_idx = doubled_constraints[existing_name]
  698. if is_index_sig(metadata_obj):
  699. conn_obj = conn_idx
  700. else:
  701. conn_obj = conn_uq
  702. else:
  703. conn_obj = conn_names[existing_name]
  704. if type(conn_obj) != type(metadata_obj):
  705. obj_removed(conn_obj)
  706. obj_added(metadata_obj)
  707. else:
  708. comparison = metadata_obj.compare_to_reflected(conn_obj)
  709. if comparison.is_different:
  710. # constraint are different
  711. obj_changed(conn_obj, metadata_obj, comparison.message)
  712. elif comparison.is_skip:
  713. # constraint cannot be compared, skip them
  714. thing = (
  715. "index" if is_index_sig(conn_obj) else "unique constraint"
  716. )
  717. log.info(
  718. "Cannot compare %s %r, assuming equal and skipping. %s",
  719. thing,
  720. conn_obj.name,
  721. comparison.message,
  722. )
  723. else:
  724. # constraint are equal
  725. assert comparison.is_equal
  726. for added_name in sorted(set(metadata_names).difference(conn_names)):
  727. obj = metadata_names[added_name]
  728. obj_added(obj)
  729. for uq_sig in unnamed_metadata_uniques:
  730. if uq_sig not in conn_uniques_by_sig:
  731. obj_added(unnamed_metadata_uniques[uq_sig])
  732. def _correct_for_uq_duplicates_uix(
  733. conn_unique_constraints,
  734. conn_indexes,
  735. metadata_unique_constraints,
  736. metadata_indexes,
  737. dialect,
  738. impl,
  739. ):
  740. # dedupe unique indexes vs. constraints, since MySQL / Oracle
  741. # doesn't really have unique constraints as a separate construct.
  742. # but look in the metadata and try to maintain constructs
  743. # that already seem to be defined one way or the other
  744. # on that side. This logic was formerly local to MySQL dialect,
  745. # generalized to Oracle and others. See #276
  746. # resolve final rendered name for unique constraints defined in the
  747. # metadata. this includes truncation of long names. naming convention
  748. # names currently should already be set as cons.name, however leave this
  749. # to the sqla_compat to decide.
  750. metadata_cons_names = [
  751. (sqla_compat._get_constraint_final_name(cons, dialect), cons)
  752. for cons in metadata_unique_constraints
  753. ]
  754. metadata_uq_names = {
  755. name for name, cons in metadata_cons_names if name is not None
  756. }
  757. unnamed_metadata_uqs = {
  758. impl._create_metadata_constraint_sig(cons).unnamed
  759. for name, cons in metadata_cons_names
  760. if name is None
  761. }
  762. metadata_ix_names = {
  763. sqla_compat._get_constraint_final_name(cons, dialect)
  764. for cons in metadata_indexes
  765. if cons.unique
  766. }
  767. # for reflection side, names are in their final database form
  768. # already since they're from the database
  769. conn_ix_names = {cons.name: cons for cons in conn_indexes if cons.unique}
  770. uqs_dupe_indexes = {
  771. cons.name: cons
  772. for cons in conn_unique_constraints
  773. if cons.info["duplicates_index"]
  774. }
  775. for overlap in uqs_dupe_indexes:
  776. if overlap not in metadata_uq_names:
  777. if (
  778. impl._create_reflected_constraint_sig(
  779. uqs_dupe_indexes[overlap]
  780. ).unnamed
  781. not in unnamed_metadata_uqs
  782. ):
  783. conn_unique_constraints.discard(uqs_dupe_indexes[overlap])
  784. elif overlap not in metadata_ix_names:
  785. conn_indexes.discard(conn_ix_names[overlap])
  786. @comparators.dispatch_for("column")
  787. def _compare_nullable(
  788. autogen_context: AutogenContext,
  789. alter_column_op: AlterColumnOp,
  790. schema: Optional[str],
  791. tname: Union[quoted_name, str],
  792. cname: Union[quoted_name, str],
  793. conn_col: Column[Any],
  794. metadata_col: Column[Any],
  795. ) -> None:
  796. metadata_col_nullable = metadata_col.nullable
  797. conn_col_nullable = conn_col.nullable
  798. alter_column_op.existing_nullable = conn_col_nullable
  799. if conn_col_nullable is not metadata_col_nullable:
  800. if (
  801. sqla_compat._server_default_is_computed(
  802. metadata_col.server_default, conn_col.server_default
  803. )
  804. and sqla_compat._nullability_might_be_unset(metadata_col)
  805. or (
  806. sqla_compat._server_default_is_identity(
  807. metadata_col.server_default, conn_col.server_default
  808. )
  809. )
  810. ):
  811. log.info(
  812. "Ignoring nullable change on identity column '%s.%s'",
  813. tname,
  814. cname,
  815. )
  816. else:
  817. alter_column_op.modify_nullable = metadata_col_nullable
  818. log.info(
  819. "Detected %s on column '%s.%s'",
  820. "NULL" if metadata_col_nullable else "NOT NULL",
  821. tname,
  822. cname,
  823. )
  824. @comparators.dispatch_for("column")
  825. def _setup_autoincrement(
  826. autogen_context: AutogenContext,
  827. alter_column_op: AlterColumnOp,
  828. schema: Optional[str],
  829. tname: Union[quoted_name, str],
  830. cname: quoted_name,
  831. conn_col: Column[Any],
  832. metadata_col: Column[Any],
  833. ) -> None:
  834. if metadata_col.table._autoincrement_column is metadata_col:
  835. alter_column_op.kw["autoincrement"] = True
  836. elif metadata_col.autoincrement is True:
  837. alter_column_op.kw["autoincrement"] = True
  838. elif metadata_col.autoincrement is False:
  839. alter_column_op.kw["autoincrement"] = False
  840. @comparators.dispatch_for("column")
  841. def _compare_type(
  842. autogen_context: AutogenContext,
  843. alter_column_op: AlterColumnOp,
  844. schema: Optional[str],
  845. tname: Union[quoted_name, str],
  846. cname: Union[quoted_name, str],
  847. conn_col: Column[Any],
  848. metadata_col: Column[Any],
  849. ) -> None:
  850. conn_type = conn_col.type
  851. alter_column_op.existing_type = conn_type
  852. metadata_type = metadata_col.type
  853. if conn_type._type_affinity is sqltypes.NullType:
  854. log.info(
  855. "Couldn't determine database type " "for column '%s.%s'",
  856. tname,
  857. cname,
  858. )
  859. return
  860. if metadata_type._type_affinity is sqltypes.NullType:
  861. log.info(
  862. "Column '%s.%s' has no type within " "the model; can't compare",
  863. tname,
  864. cname,
  865. )
  866. return
  867. isdiff = autogen_context.migration_context._compare_type(
  868. conn_col, metadata_col
  869. )
  870. if isdiff:
  871. alter_column_op.modify_type = metadata_type
  872. log.info(
  873. "Detected type change from %r to %r on '%s.%s'",
  874. conn_type,
  875. metadata_type,
  876. tname,
  877. cname,
  878. )
  879. def _render_server_default_for_compare(
  880. metadata_default: Optional[Any], autogen_context: AutogenContext
  881. ) -> Optional[str]:
  882. if isinstance(metadata_default, sa_schema.DefaultClause):
  883. if isinstance(metadata_default.arg, str):
  884. metadata_default = metadata_default.arg
  885. else:
  886. metadata_default = str(
  887. metadata_default.arg.compile(
  888. dialect=autogen_context.dialect,
  889. compile_kwargs={"literal_binds": True},
  890. )
  891. )
  892. if isinstance(metadata_default, str):
  893. return metadata_default
  894. else:
  895. return None
  896. def _normalize_computed_default(sqltext: str) -> str:
  897. """we want to warn if a computed sql expression has changed. however
  898. we don't want false positives and the warning is not that critical.
  899. so filter out most forms of variability from the SQL text.
  900. """
  901. return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower()
  902. def _compare_computed_default(
  903. autogen_context: AutogenContext,
  904. alter_column_op: AlterColumnOp,
  905. schema: Optional[str],
  906. tname: str,
  907. cname: str,
  908. conn_col: Column[Any],
  909. metadata_col: Column[Any],
  910. ) -> None:
  911. rendered_metadata_default = str(
  912. cast(sa_schema.Computed, metadata_col.server_default).sqltext.compile(
  913. dialect=autogen_context.dialect,
  914. compile_kwargs={"literal_binds": True},
  915. )
  916. )
  917. # since we cannot change computed columns, we do only a crude comparison
  918. # here where we try to eliminate syntactical differences in order to
  919. # get a minimal comparison just to emit a warning.
  920. rendered_metadata_default = _normalize_computed_default(
  921. rendered_metadata_default
  922. )
  923. if isinstance(conn_col.server_default, sa_schema.Computed):
  924. rendered_conn_default = str(
  925. conn_col.server_default.sqltext.compile(
  926. dialect=autogen_context.dialect,
  927. compile_kwargs={"literal_binds": True},
  928. )
  929. )
  930. if rendered_conn_default is None:
  931. rendered_conn_default = ""
  932. else:
  933. rendered_conn_default = _normalize_computed_default(
  934. rendered_conn_default
  935. )
  936. else:
  937. rendered_conn_default = ""
  938. if rendered_metadata_default != rendered_conn_default:
  939. _warn_computed_not_supported(tname, cname)
  940. def _warn_computed_not_supported(tname: str, cname: str) -> None:
  941. util.warn("Computed default on %s.%s cannot be modified" % (tname, cname))
  942. def _compare_identity_default(
  943. autogen_context,
  944. alter_column_op,
  945. schema,
  946. tname,
  947. cname,
  948. conn_col,
  949. metadata_col,
  950. ):
  951. impl = autogen_context.migration_context.impl
  952. diff, ignored_attr, is_alter = impl._compare_identity_default(
  953. metadata_col.server_default, conn_col.server_default
  954. )
  955. return diff, is_alter
  956. @comparators.dispatch_for("column")
  957. def _compare_server_default(
  958. autogen_context: AutogenContext,
  959. alter_column_op: AlterColumnOp,
  960. schema: Optional[str],
  961. tname: Union[quoted_name, str],
  962. cname: Union[quoted_name, str],
  963. conn_col: Column[Any],
  964. metadata_col: Column[Any],
  965. ) -> Optional[bool]:
  966. metadata_default = metadata_col.server_default
  967. conn_col_default = conn_col.server_default
  968. if conn_col_default is None and metadata_default is None:
  969. return False
  970. if sqla_compat._server_default_is_computed(metadata_default):
  971. return _compare_computed_default( # type:ignore[func-returns-value]
  972. autogen_context,
  973. alter_column_op,
  974. schema,
  975. tname,
  976. cname,
  977. conn_col,
  978. metadata_col,
  979. )
  980. if sqla_compat._server_default_is_computed(conn_col_default):
  981. _warn_computed_not_supported(tname, cname)
  982. return False
  983. if sqla_compat._server_default_is_identity(
  984. metadata_default, conn_col_default
  985. ):
  986. alter_column_op.existing_server_default = conn_col_default
  987. diff, is_alter = _compare_identity_default(
  988. autogen_context,
  989. alter_column_op,
  990. schema,
  991. tname,
  992. cname,
  993. conn_col,
  994. metadata_col,
  995. )
  996. if is_alter:
  997. alter_column_op.modify_server_default = metadata_default
  998. if diff:
  999. log.info(
  1000. "Detected server default on column '%s.%s': "
  1001. "identity options attributes %s",
  1002. tname,
  1003. cname,
  1004. sorted(diff),
  1005. )
  1006. else:
  1007. rendered_metadata_default = _render_server_default_for_compare(
  1008. metadata_default, autogen_context
  1009. )
  1010. rendered_conn_default = (
  1011. cast(Any, conn_col_default).arg.text if conn_col_default else None
  1012. )
  1013. alter_column_op.existing_server_default = conn_col_default
  1014. is_diff = autogen_context.migration_context._compare_server_default(
  1015. conn_col,
  1016. metadata_col,
  1017. rendered_metadata_default,
  1018. rendered_conn_default,
  1019. )
  1020. if is_diff:
  1021. alter_column_op.modify_server_default = metadata_default
  1022. log.info("Detected server default on column '%s.%s'", tname, cname)
  1023. return None
  1024. @comparators.dispatch_for("column")
  1025. def _compare_column_comment(
  1026. autogen_context: AutogenContext,
  1027. alter_column_op: AlterColumnOp,
  1028. schema: Optional[str],
  1029. tname: Union[quoted_name, str],
  1030. cname: quoted_name,
  1031. conn_col: Column[Any],
  1032. metadata_col: Column[Any],
  1033. ) -> Optional[Literal[False]]:
  1034. assert autogen_context.dialect is not None
  1035. if not autogen_context.dialect.supports_comments:
  1036. return None
  1037. metadata_comment = metadata_col.comment
  1038. conn_col_comment = conn_col.comment
  1039. if conn_col_comment is None and metadata_comment is None:
  1040. return False
  1041. alter_column_op.existing_comment = conn_col_comment
  1042. if conn_col_comment != metadata_comment:
  1043. alter_column_op.modify_comment = metadata_comment
  1044. log.info("Detected column comment '%s.%s'", tname, cname)
  1045. return None
  1046. @comparators.dispatch_for("table")
  1047. def _compare_foreign_keys(
  1048. autogen_context: AutogenContext,
  1049. modify_table_ops: ModifyTableOps,
  1050. schema: Optional[str],
  1051. tname: Union[quoted_name, str],
  1052. conn_table: Table,
  1053. metadata_table: Table,
  1054. ) -> None:
  1055. # if we're doing CREATE TABLE, all FKs are created
  1056. # inline within the table def
  1057. if conn_table is None or metadata_table is None:
  1058. return
  1059. inspector = autogen_context.inspector
  1060. metadata_fks = {
  1061. fk
  1062. for fk in metadata_table.constraints
  1063. if isinstance(fk, sa_schema.ForeignKeyConstraint)
  1064. }
  1065. conn_fks_list = [
  1066. fk
  1067. for fk in _InspectorConv(inspector).get_foreign_keys(
  1068. tname, schema=schema
  1069. )
  1070. if autogen_context.run_name_filters(
  1071. fk["name"],
  1072. "foreign_key_constraint",
  1073. {"table_name": tname, "schema_name": schema},
  1074. )
  1075. ]
  1076. conn_fks = {
  1077. _make_foreign_key(const, conn_table) for const in conn_fks_list
  1078. }
  1079. impl = autogen_context.migration_context.impl
  1080. # give the dialect a chance to correct the FKs to match more
  1081. # closely
  1082. autogen_context.migration_context.impl.correct_for_autogen_foreignkeys(
  1083. conn_fks, metadata_fks
  1084. )
  1085. metadata_fks_sig = {
  1086. impl._create_metadata_constraint_sig(fk) for fk in metadata_fks
  1087. }
  1088. conn_fks_sig = {
  1089. impl._create_reflected_constraint_sig(fk) for fk in conn_fks
  1090. }
  1091. # check if reflected FKs include options, indicating the backend
  1092. # can reflect FK options
  1093. if conn_fks_list and "options" in conn_fks_list[0]:
  1094. conn_fks_by_sig = {c.unnamed: c for c in conn_fks_sig}
  1095. metadata_fks_by_sig = {c.unnamed: c for c in metadata_fks_sig}
  1096. else:
  1097. # otherwise compare by sig without options added
  1098. conn_fks_by_sig = {c.unnamed_no_options: c for c in conn_fks_sig}
  1099. metadata_fks_by_sig = {
  1100. c.unnamed_no_options: c for c in metadata_fks_sig
  1101. }
  1102. metadata_fks_by_name = {
  1103. c.name: c for c in metadata_fks_sig if c.name is not None
  1104. }
  1105. conn_fks_by_name = {c.name: c for c in conn_fks_sig if c.name is not None}
  1106. def _add_fk(obj, compare_to):
  1107. if autogen_context.run_object_filters(
  1108. obj.const, obj.name, "foreign_key_constraint", False, compare_to
  1109. ):
  1110. modify_table_ops.ops.append(
  1111. ops.CreateForeignKeyOp.from_constraint(const.const)
  1112. )
  1113. log.info(
  1114. "Detected added foreign key (%s)(%s) on table %s%s",
  1115. ", ".join(obj.source_columns),
  1116. ", ".join(obj.target_columns),
  1117. "%s." % obj.source_schema if obj.source_schema else "",
  1118. obj.source_table,
  1119. )
  1120. def _remove_fk(obj, compare_to):
  1121. if autogen_context.run_object_filters(
  1122. obj.const, obj.name, "foreign_key_constraint", True, compare_to
  1123. ):
  1124. modify_table_ops.ops.append(
  1125. ops.DropConstraintOp.from_constraint(obj.const)
  1126. )
  1127. log.info(
  1128. "Detected removed foreign key (%s)(%s) on table %s%s",
  1129. ", ".join(obj.source_columns),
  1130. ", ".join(obj.target_columns),
  1131. "%s." % obj.source_schema if obj.source_schema else "",
  1132. obj.source_table,
  1133. )
  1134. # so far it appears we don't need to do this by name at all.
  1135. # SQLite doesn't preserve constraint names anyway
  1136. for removed_sig in set(conn_fks_by_sig).difference(metadata_fks_by_sig):
  1137. const = conn_fks_by_sig[removed_sig]
  1138. if removed_sig not in metadata_fks_by_sig:
  1139. compare_to = (
  1140. metadata_fks_by_name[const.name].const
  1141. if const.name in metadata_fks_by_name
  1142. else None
  1143. )
  1144. _remove_fk(const, compare_to)
  1145. for added_sig in set(metadata_fks_by_sig).difference(conn_fks_by_sig):
  1146. const = metadata_fks_by_sig[added_sig]
  1147. if added_sig not in conn_fks_by_sig:
  1148. compare_to = (
  1149. conn_fks_by_name[const.name].const
  1150. if const.name in conn_fks_by_name
  1151. else None
  1152. )
  1153. _add_fk(const, compare_to)
  1154. @comparators.dispatch_for("table")
  1155. def _compare_table_comment(
  1156. autogen_context: AutogenContext,
  1157. modify_table_ops: ModifyTableOps,
  1158. schema: Optional[str],
  1159. tname: Union[quoted_name, str],
  1160. conn_table: Optional[Table],
  1161. metadata_table: Optional[Table],
  1162. ) -> None:
  1163. assert autogen_context.dialect is not None
  1164. if not autogen_context.dialect.supports_comments:
  1165. return
  1166. # if we're doing CREATE TABLE, comments will be created inline
  1167. # with the create_table op.
  1168. if conn_table is None or metadata_table is None:
  1169. return
  1170. if conn_table.comment is None and metadata_table.comment is None:
  1171. return
  1172. if metadata_table.comment is None and conn_table.comment is not None:
  1173. modify_table_ops.ops.append(
  1174. ops.DropTableCommentOp(
  1175. tname, existing_comment=conn_table.comment, schema=schema
  1176. )
  1177. )
  1178. elif metadata_table.comment != conn_table.comment:
  1179. modify_table_ops.ops.append(
  1180. ops.CreateTableCommentOp(
  1181. tname,
  1182. metadata_table.comment,
  1183. existing_comment=conn_table.comment,
  1184. schema=schema,
  1185. )
  1186. )