You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1150 lines
38KB

  1. # engine/reflection.py
  2. # Copyright (C) 2005-2021 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: http://www.opensource.org/licenses/mit-license.php
  7. """Provides an abstraction for obtaining database schema information.
  8. Usage Notes:
  9. Here are some general conventions when accessing the low level inspector
  10. methods such as get_table_names, get_columns, etc.
  11. 1. Inspector methods return lists of dicts in most cases for the following
  12. reasons:
  13. * They're both standard types that can be serialized.
  14. * Using a dict instead of a tuple allows easy expansion of attributes.
  15. * Using a list for the outer structure maintains order and is easy to work
  16. with (e.g. list comprehension [d['name'] for d in cols]).
  17. 2. Records that contain a name, such as the column name in a column record
  18. use the key 'name'. So for most return values, each record will have a
  19. 'name' attribute..
  20. """
  21. import contextlib
  22. from .base import Connectable
  23. from .base import Connection
  24. from .base import Engine
  25. from .. import exc
  26. from .. import inspection
  27. from .. import sql
  28. from .. import util
  29. from ..sql import operators
  30. from ..sql import schema as sa_schema
  31. from ..sql.type_api import TypeEngine
  32. from ..util import topological
  33. @util.decorator
  34. def cache(fn, self, con, *args, **kw):
  35. info_cache = kw.get("info_cache", None)
  36. if info_cache is None:
  37. return fn(self, con, *args, **kw)
  38. key = (
  39. fn.__name__,
  40. tuple(a for a in args if isinstance(a, util.string_types)),
  41. tuple((k, v) for k, v in kw.items() if k != "info_cache"),
  42. )
  43. ret = info_cache.get(key)
  44. if ret is None:
  45. ret = fn(self, con, *args, **kw)
  46. info_cache[key] = ret
  47. return ret
  48. @inspection._self_inspects
  49. class Inspector(object):
  50. """Performs database schema inspection.
  51. The Inspector acts as a proxy to the reflection methods of the
  52. :class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
  53. consistent interface as well as caching support for previously
  54. fetched metadata.
  55. A :class:`_reflection.Inspector` object is usually created via the
  56. :func:`_sa.inspect` function, which may be passed an
  57. :class:`_engine.Engine`
  58. or a :class:`_engine.Connection`::
  59. from sqlalchemy import inspect, create_engine
  60. engine = create_engine('...')
  61. insp = inspect(engine)
  62. Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` associated
  63. with the engine may opt to return an :class:`_reflection.Inspector`
  64. subclass that
  65. provides additional methods specific to the dialect's target database.
  66. """
  67. @util.deprecated(
  68. "1.4",
  69. "The __init__() method on :class:`_reflection.Inspector` "
  70. "is deprecated and "
  71. "will be removed in a future release. Please use the "
  72. ":func:`.sqlalchemy.inspect` "
  73. "function on an :class:`_engine.Engine` or "
  74. ":class:`_engine.Connection` "
  75. "in order to "
  76. "acquire an :class:`_reflection.Inspector`.",
  77. )
  78. def __init__(self, bind):
  79. """Initialize a new :class:`_reflection.Inspector`.
  80. :param bind: a :class:`~sqlalchemy.engine.Connectable`,
  81. which is typically an instance of
  82. :class:`~sqlalchemy.engine.Engine` or
  83. :class:`~sqlalchemy.engine.Connection`.
  84. For a dialect-specific instance of :class:`_reflection.Inspector`, see
  85. :meth:`_reflection.Inspector.from_engine`
  86. """
  87. return self._init_legacy(bind)
  88. @classmethod
  89. def _construct(cls, init, bind):
  90. if hasattr(bind.dialect, "inspector"):
  91. cls = bind.dialect.inspector
  92. self = cls.__new__(cls)
  93. init(self, bind)
  94. return self
  95. def _init_legacy(self, bind):
  96. if hasattr(bind, "exec_driver_sql"):
  97. self._init_connection(bind)
  98. else:
  99. self._init_engine(bind)
  100. def _init_engine(self, engine):
  101. self.bind = self.engine = engine
  102. engine.connect().close()
  103. self._op_context_requires_connect = True
  104. self.dialect = self.engine.dialect
  105. self.info_cache = {}
  106. def _init_connection(self, connection):
  107. self.bind = connection
  108. self.engine = connection.engine
  109. self._op_context_requires_connect = False
  110. self.dialect = self.engine.dialect
  111. self.info_cache = {}
  112. @classmethod
  113. @util.deprecated(
  114. "1.4",
  115. "The from_engine() method on :class:`_reflection.Inspector` "
  116. "is deprecated and "
  117. "will be removed in a future release. Please use the "
  118. ":func:`.sqlalchemy.inspect` "
  119. "function on an :class:`_engine.Engine` or "
  120. ":class:`_engine.Connection` "
  121. "in order to "
  122. "acquire an :class:`_reflection.Inspector`.",
  123. )
  124. def from_engine(cls, bind):
  125. """Construct a new dialect-specific Inspector object from the given
  126. engine or connection.
  127. :param bind: a :class:`~sqlalchemy.engine.Connectable`,
  128. which is typically an instance of
  129. :class:`~sqlalchemy.engine.Engine` or
  130. :class:`~sqlalchemy.engine.Connection`.
  131. This method differs from direct a direct constructor call of
  132. :class:`_reflection.Inspector` in that the
  133. :class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
  134. provide a dialect-specific :class:`_reflection.Inspector` instance,
  135. which may
  136. provide additional methods.
  137. See the example at :class:`_reflection.Inspector`.
  138. """
  139. return cls._construct(cls._init_legacy, bind)
  140. @inspection._inspects(Connectable)
  141. def _connectable_insp(bind):
  142. # this method should not be used unless some unusual case
  143. # has subclassed "Connectable"
  144. return Inspector._construct(Inspector._init_legacy, bind)
  145. @inspection._inspects(Engine)
  146. def _engine_insp(bind):
  147. return Inspector._construct(Inspector._init_engine, bind)
  148. @inspection._inspects(Connection)
  149. def _connection_insp(bind):
  150. return Inspector._construct(Inspector._init_connection, bind)
  151. @contextlib.contextmanager
  152. def _operation_context(self):
  153. """Return a context that optimizes for multiple operations on a single
  154. transaction.
  155. This essentially allows connect()/close() to be called if we detected
  156. that we're against an :class:`_engine.Engine` and not a
  157. :class:`_engine.Connection`.
  158. """
  159. if self._op_context_requires_connect:
  160. conn = self.bind.connect()
  161. else:
  162. conn = self.bind
  163. try:
  164. yield conn
  165. finally:
  166. if self._op_context_requires_connect:
  167. conn.close()
  168. @contextlib.contextmanager
  169. def _inspection_context(self):
  170. """Return an :class:`_reflection.Inspector`
  171. from this one that will run all
  172. operations on a single connection.
  173. """
  174. with self._operation_context() as conn:
  175. sub_insp = self._construct(self.__class__._init_connection, conn)
  176. sub_insp.info_cache = self.info_cache
  177. yield sub_insp
  178. @property
  179. def default_schema_name(self):
  180. """Return the default schema name presented by the dialect
  181. for the current engine's database user.
  182. E.g. this is typically ``public`` for PostgreSQL and ``dbo``
  183. for SQL Server.
  184. """
  185. return self.dialect.default_schema_name
  186. def get_schema_names(self):
  187. """Return all schema names."""
  188. if hasattr(self.dialect, "get_schema_names"):
  189. with self._operation_context() as conn:
  190. return self.dialect.get_schema_names(
  191. conn, info_cache=self.info_cache
  192. )
  193. return []
  194. def get_table_names(self, schema=None):
  195. """Return all table names in referred to within a particular schema.
  196. The names are expected to be real tables only, not views.
  197. Views are instead returned using the
  198. :meth:`_reflection.Inspector.get_view_names`
  199. method.
  200. :param schema: Schema name. If ``schema`` is left at ``None``, the
  201. database's default schema is
  202. used, else the named schema is searched. If the database does not
  203. support named schemas, behavior is undefined if ``schema`` is not
  204. passed as ``None``. For special quoting, use :class:`.quoted_name`.
  205. .. seealso::
  206. :meth:`_reflection.Inspector.get_sorted_table_and_fkc_names`
  207. :attr:`_schema.MetaData.sorted_tables`
  208. """
  209. with self._operation_context() as conn:
  210. return self.dialect.get_table_names(
  211. conn, schema, info_cache=self.info_cache
  212. )
  213. def has_table(self, table_name, schema=None):
  214. """Return True if the backend has a table of the given name.
  215. :param table_name: name of the table to check
  216. :param schema: schema name to query, if not the default schema.
  217. .. versionadded:: 1.4 - the :meth:`.Inspector.has_table` method
  218. replaces the :meth:`_engine.Engine.has_table` method.
  219. """
  220. # TODO: info_cache?
  221. with self._operation_context() as conn:
  222. return self.dialect.has_table(conn, table_name, schema)
  223. def has_sequence(self, sequence_name, schema=None):
  224. """Return True if the backend has a table of the given name.
  225. :param sequence_name: name of the table to check
  226. :param schema: schema name to query, if not the default schema.
  227. .. versionadded:: 1.4
  228. """
  229. # TODO: info_cache?
  230. with self._operation_context() as conn:
  231. return self.dialect.has_sequence(conn, sequence_name, schema)
  232. def get_sorted_table_and_fkc_names(self, schema=None):
  233. """Return dependency-sorted table and foreign key constraint names in
  234. referred to within a particular schema.
  235. This will yield 2-tuples of
  236. ``(tablename, [(tname, fkname), (tname, fkname), ...])``
  237. consisting of table names in CREATE order grouped with the foreign key
  238. constraint names that are not detected as belonging to a cycle.
  239. The final element
  240. will be ``(None, [(tname, fkname), (tname, fkname), ..])``
  241. which will consist of remaining
  242. foreign key constraint names that would require a separate CREATE
  243. step after-the-fact, based on dependencies between tables.
  244. .. versionadded:: 1.0.-
  245. .. seealso::
  246. :meth:`_reflection.Inspector.get_table_names`
  247. :func:`.sort_tables_and_constraints` - similar method which works
  248. with an already-given :class:`_schema.MetaData`.
  249. """
  250. with self._operation_context() as conn:
  251. tnames = self.dialect.get_table_names(
  252. conn, schema, info_cache=self.info_cache
  253. )
  254. tuples = set()
  255. remaining_fkcs = set()
  256. fknames_for_table = {}
  257. for tname in tnames:
  258. fkeys = self.get_foreign_keys(tname, schema)
  259. fknames_for_table[tname] = set([fk["name"] for fk in fkeys])
  260. for fkey in fkeys:
  261. if tname != fkey["referred_table"]:
  262. tuples.add((fkey["referred_table"], tname))
  263. try:
  264. candidate_sort = list(topological.sort(tuples, tnames))
  265. except exc.CircularDependencyError as err:
  266. for edge in err.edges:
  267. tuples.remove(edge)
  268. remaining_fkcs.update(
  269. (edge[1], fkc) for fkc in fknames_for_table[edge[1]]
  270. )
  271. candidate_sort = list(topological.sort(tuples, tnames))
  272. return [
  273. (tname, fknames_for_table[tname].difference(remaining_fkcs))
  274. for tname in candidate_sort
  275. ] + [(None, list(remaining_fkcs))]
  276. def get_temp_table_names(self):
  277. """Return a list of temporary table names for the current bind.
  278. This method is unsupported by most dialects; currently
  279. only SQLite implements it.
  280. .. versionadded:: 1.0.0
  281. """
  282. with self._operation_context() as conn:
  283. return self.dialect.get_temp_table_names(
  284. conn, info_cache=self.info_cache
  285. )
  286. def get_temp_view_names(self):
  287. """Return a list of temporary view names for the current bind.
  288. This method is unsupported by most dialects; currently
  289. only SQLite implements it.
  290. .. versionadded:: 1.0.0
  291. """
  292. with self._operation_context() as conn:
  293. return self.dialect.get_temp_view_names(
  294. conn, info_cache=self.info_cache
  295. )
  296. def get_table_options(self, table_name, schema=None, **kw):
  297. """Return a dictionary of options specified when the table of the
  298. given name was created.
  299. This currently includes some options that apply to MySQL tables.
  300. :param table_name: string name of the table. For special quoting,
  301. use :class:`.quoted_name`.
  302. :param schema: string schema name; if omitted, uses the default schema
  303. of the database connection. For special quoting,
  304. use :class:`.quoted_name`.
  305. """
  306. if hasattr(self.dialect, "get_table_options"):
  307. with self._operation_context() as conn:
  308. return self.dialect.get_table_options(
  309. conn, table_name, schema, info_cache=self.info_cache, **kw
  310. )
  311. return {}
  312. def get_view_names(self, schema=None):
  313. """Return all view names in `schema`.
  314. :param schema: Optional, retrieve names from a non-default schema.
  315. For special quoting, use :class:`.quoted_name`.
  316. """
  317. with self._operation_context() as conn:
  318. return self.dialect.get_view_names(
  319. conn, schema, info_cache=self.info_cache
  320. )
  321. def get_sequence_names(self, schema=None):
  322. """Return all sequence names in `schema`.
  323. :param schema: Optional, retrieve names from a non-default schema.
  324. For special quoting, use :class:`.quoted_name`.
  325. """
  326. with self._operation_context() as conn:
  327. return self.dialect.get_sequence_names(
  328. conn, schema, info_cache=self.info_cache
  329. )
  330. def get_view_definition(self, view_name, schema=None):
  331. """Return definition for `view_name`.
  332. :param schema: Optional, retrieve names from a non-default schema.
  333. For special quoting, use :class:`.quoted_name`.
  334. """
  335. with self._operation_context() as conn:
  336. return self.dialect.get_view_definition(
  337. conn, view_name, schema, info_cache=self.info_cache
  338. )
  339. def get_columns(self, table_name, schema=None, **kw):
  340. """Return information about columns in `table_name`.
  341. Given a string `table_name` and an optional string `schema`, return
  342. column information as a list of dicts with these keys:
  343. * ``name`` - the column's name
  344. * ``type`` - the type of this column; an instance of
  345. :class:`~sqlalchemy.types.TypeEngine`
  346. * ``nullable`` - boolean flag if the column is NULL or NOT NULL
  347. * ``default`` - the column's server default value - this is returned
  348. as a string SQL expression.
  349. * ``autoincrement`` - indicates that the column is auto incremented -
  350. this is returned as a boolean or 'auto'
  351. * ``comment`` - (optional) the comment on the column. Only some
  352. dialects return this key
  353. * ``computed`` - (optional) when present it indicates that this column
  354. is computed by the database. Only some dialects return this key.
  355. Returned as a dict with the keys:
  356. * ``sqltext`` - the expression used to generate this column returned
  357. as a string SQL expression
  358. * ``persisted`` - (optional) boolean that indicates if the column is
  359. stored in the table
  360. .. versionadded:: 1.3.16 - added support for computed reflection.
  361. * ``identity`` - (optional) when present it indicates that this column
  362. is a generated always column. Only some dialects return this key.
  363. For a list of keywords on this dict see :class:`_schema.Identity`.
  364. .. versionadded:: 1.4 - added support for identity column reflection.
  365. * ``dialect_options`` - (optional) a dict with dialect specific options
  366. :param table_name: string name of the table. For special quoting,
  367. use :class:`.quoted_name`.
  368. :param schema: string schema name; if omitted, uses the default schema
  369. of the database connection. For special quoting,
  370. use :class:`.quoted_name`.
  371. :return: list of dictionaries, each representing the definition of
  372. a database column.
  373. """
  374. with self._operation_context() as conn:
  375. col_defs = self.dialect.get_columns(
  376. conn, table_name, schema, info_cache=self.info_cache, **kw
  377. )
  378. for col_def in col_defs:
  379. # make this easy and only return instances for coltype
  380. coltype = col_def["type"]
  381. if not isinstance(coltype, TypeEngine):
  382. col_def["type"] = coltype()
  383. return col_defs
  384. def get_pk_constraint(self, table_name, schema=None, **kw):
  385. """Return information about primary key constraint on `table_name`.
  386. Given a string `table_name`, and an optional string `schema`, return
  387. primary key information as a dictionary with these keys:
  388. * ``constrained_columns`` -
  389. a list of column names that make up the primary key
  390. * ``name`` -
  391. optional name of the primary key constraint.
  392. :param table_name: string name of the table. For special quoting,
  393. use :class:`.quoted_name`.
  394. :param schema: string schema name; if omitted, uses the default schema
  395. of the database connection. For special quoting,
  396. use :class:`.quoted_name`.
  397. """
  398. with self._operation_context() as conn:
  399. return self.dialect.get_pk_constraint(
  400. conn, table_name, schema, info_cache=self.info_cache, **kw
  401. )
  402. def get_foreign_keys(self, table_name, schema=None, **kw):
  403. """Return information about foreign_keys in `table_name`.
  404. Given a string `table_name`, and an optional string `schema`, return
  405. foreign key information as a list of dicts with these keys:
  406. * ``constrained_columns`` -
  407. a list of column names that make up the foreign key
  408. * ``referred_schema`` -
  409. the name of the referred schema
  410. * ``referred_table`` -
  411. the name of the referred table
  412. * ``referred_columns`` -
  413. a list of column names in the referred table that correspond to
  414. constrained_columns
  415. * ``name`` -
  416. optional name of the foreign key constraint.
  417. :param table_name: string name of the table. For special quoting,
  418. use :class:`.quoted_name`.
  419. :param schema: string schema name; if omitted, uses the default schema
  420. of the database connection. For special quoting,
  421. use :class:`.quoted_name`.
  422. """
  423. with self._operation_context() as conn:
  424. return self.dialect.get_foreign_keys(
  425. conn, table_name, schema, info_cache=self.info_cache, **kw
  426. )
  427. def get_indexes(self, table_name, schema=None, **kw):
  428. """Return information about indexes in `table_name`.
  429. Given a string `table_name` and an optional string `schema`, return
  430. index information as a list of dicts with these keys:
  431. * ``name`` -
  432. the index's name
  433. * ``column_names`` -
  434. list of column names in order
  435. * ``unique`` -
  436. boolean
  437. * ``column_sorting`` -
  438. optional dict mapping column names to tuple of sort keywords,
  439. which may include ``asc``, ``desc``, ``nulls_first``, ``nulls_last``.
  440. .. versionadded:: 1.3.5
  441. * ``dialect_options`` -
  442. dict of dialect-specific index options. May not be present
  443. for all dialects.
  444. .. versionadded:: 1.0.0
  445. :param table_name: string name of the table. For special quoting,
  446. use :class:`.quoted_name`.
  447. :param schema: string schema name; if omitted, uses the default schema
  448. of the database connection. For special quoting,
  449. use :class:`.quoted_name`.
  450. """
  451. with self._operation_context() as conn:
  452. return self.dialect.get_indexes(
  453. conn, table_name, schema, info_cache=self.info_cache, **kw
  454. )
  455. def get_unique_constraints(self, table_name, schema=None, **kw):
  456. """Return information about unique constraints in `table_name`.
  457. Given a string `table_name` and an optional string `schema`, return
  458. unique constraint information as a list of dicts with these keys:
  459. * ``name`` -
  460. the unique constraint's name
  461. * ``column_names`` -
  462. list of column names in order
  463. :param table_name: string name of the table. For special quoting,
  464. use :class:`.quoted_name`.
  465. :param schema: string schema name; if omitted, uses the default schema
  466. of the database connection. For special quoting,
  467. use :class:`.quoted_name`.
  468. """
  469. with self._operation_context() as conn:
  470. return self.dialect.get_unique_constraints(
  471. conn, table_name, schema, info_cache=self.info_cache, **kw
  472. )
  473. def get_table_comment(self, table_name, schema=None, **kw):
  474. """Return information about the table comment for ``table_name``.
  475. Given a string ``table_name`` and an optional string ``schema``,
  476. return table comment information as a dictionary with these keys:
  477. * ``text`` -
  478. text of the comment.
  479. Raises ``NotImplementedError`` for a dialect that does not support
  480. comments.
  481. .. versionadded:: 1.2
  482. """
  483. with self._operation_context() as conn:
  484. return self.dialect.get_table_comment(
  485. conn, table_name, schema, info_cache=self.info_cache, **kw
  486. )
  487. def get_check_constraints(self, table_name, schema=None, **kw):
  488. """Return information about check constraints in `table_name`.
  489. Given a string `table_name` and an optional string `schema`, return
  490. check constraint information as a list of dicts with these keys:
  491. * ``name`` -
  492. the check constraint's name
  493. * ``sqltext`` -
  494. the check constraint's SQL expression
  495. * ``dialect_options`` -
  496. may or may not be present; a dictionary with additional
  497. dialect-specific options for this CHECK constraint
  498. .. versionadded:: 1.3.8
  499. :param table_name: string name of the table. For special quoting,
  500. use :class:`.quoted_name`.
  501. :param schema: string schema name; if omitted, uses the default schema
  502. of the database connection. For special quoting,
  503. use :class:`.quoted_name`.
  504. .. versionadded:: 1.1.0
  505. """
  506. with self._operation_context() as conn:
  507. return self.dialect.get_check_constraints(
  508. conn, table_name, schema, info_cache=self.info_cache, **kw
  509. )
  510. @util.deprecated_20(
  511. ":meth:`_reflection.Inspector.reflecttable`",
  512. "The :meth:`_reflection.Inspector.reflecttable` "
  513. "method was renamed to "
  514. ":meth:`_reflection.Inspector.reflect_table`. This deprecated alias "
  515. "will be removed in a future release.",
  516. )
  517. def reflecttable(self, *args, **kwargs):
  518. "See reflect_table. This method name is deprecated"
  519. return self.reflect_table(*args, **kwargs)
  520. def reflect_table(
  521. self,
  522. table,
  523. include_columns,
  524. exclude_columns=(),
  525. resolve_fks=True,
  526. _extend_on=None,
  527. ):
  528. """Given a :class:`_schema.Table` object, load its internal
  529. constructs based on introspection.
  530. This is the underlying method used by most dialects to produce
  531. table reflection. Direct usage is like::
  532. from sqlalchemy import create_engine, MetaData, Table
  533. from sqlalchemy import inspect
  534. engine = create_engine('...')
  535. meta = MetaData()
  536. user_table = Table('user', meta)
  537. insp = inspect(engine)
  538. insp.reflect_table(user_table, None)
  539. .. versionchanged:: 1.4 Renamed from ``reflecttable`` to
  540. ``reflect_table``
  541. :param table: a :class:`~sqlalchemy.schema.Table` instance.
  542. :param include_columns: a list of string column names to include
  543. in the reflection process. If ``None``, all columns are reflected.
  544. """
  545. if _extend_on is not None:
  546. if table in _extend_on:
  547. return
  548. else:
  549. _extend_on.add(table)
  550. dialect = self.bind.dialect
  551. with self._operation_context() as conn:
  552. schema = conn.schema_for_object(table)
  553. table_name = table.name
  554. # get table-level arguments that are specifically
  555. # intended for reflection, e.g. oracle_resolve_synonyms.
  556. # these are unconditionally passed to related Table
  557. # objects
  558. reflection_options = dict(
  559. (k, table.dialect_kwargs.get(k))
  560. for k in dialect.reflection_options
  561. if k in table.dialect_kwargs
  562. )
  563. # reflect table options, like mysql_engine
  564. tbl_opts = self.get_table_options(
  565. table_name, schema, **table.dialect_kwargs
  566. )
  567. if tbl_opts:
  568. # add additional kwargs to the Table if the dialect
  569. # returned them
  570. table._validate_dialect_kwargs(tbl_opts)
  571. if util.py2k:
  572. if isinstance(schema, str):
  573. schema = schema.decode(dialect.encoding)
  574. if isinstance(table_name, str):
  575. table_name = table_name.decode(dialect.encoding)
  576. found_table = False
  577. cols_by_orig_name = {}
  578. for col_d in self.get_columns(
  579. table_name, schema, **table.dialect_kwargs
  580. ):
  581. found_table = True
  582. self._reflect_column(
  583. table,
  584. col_d,
  585. include_columns,
  586. exclude_columns,
  587. cols_by_orig_name,
  588. )
  589. if not found_table:
  590. raise exc.NoSuchTableError(table.name)
  591. self._reflect_pk(
  592. table_name, schema, table, cols_by_orig_name, exclude_columns
  593. )
  594. self._reflect_fk(
  595. table_name,
  596. schema,
  597. table,
  598. cols_by_orig_name,
  599. exclude_columns,
  600. resolve_fks,
  601. _extend_on,
  602. reflection_options,
  603. )
  604. self._reflect_indexes(
  605. table_name,
  606. schema,
  607. table,
  608. cols_by_orig_name,
  609. include_columns,
  610. exclude_columns,
  611. reflection_options,
  612. )
  613. self._reflect_unique_constraints(
  614. table_name,
  615. schema,
  616. table,
  617. cols_by_orig_name,
  618. include_columns,
  619. exclude_columns,
  620. reflection_options,
  621. )
  622. self._reflect_check_constraints(
  623. table_name,
  624. schema,
  625. table,
  626. cols_by_orig_name,
  627. include_columns,
  628. exclude_columns,
  629. reflection_options,
  630. )
  631. self._reflect_table_comment(
  632. table_name, schema, table, reflection_options
  633. )
  634. def _reflect_column(
  635. self, table, col_d, include_columns, exclude_columns, cols_by_orig_name
  636. ):
  637. orig_name = col_d["name"]
  638. table.metadata.dispatch.column_reflect(self, table, col_d)
  639. table.dispatch.column_reflect(self, table, col_d)
  640. # fetch name again as column_reflect is allowed to
  641. # change it
  642. name = col_d["name"]
  643. if (include_columns and name not in include_columns) or (
  644. exclude_columns and name in exclude_columns
  645. ):
  646. return
  647. coltype = col_d["type"]
  648. col_kw = dict(
  649. (k, col_d[k])
  650. for k in [
  651. "nullable",
  652. "autoincrement",
  653. "quote",
  654. "info",
  655. "key",
  656. "comment",
  657. ]
  658. if k in col_d
  659. )
  660. if "dialect_options" in col_d:
  661. col_kw.update(col_d["dialect_options"])
  662. colargs = []
  663. if col_d.get("default") is not None:
  664. default = col_d["default"]
  665. if isinstance(default, sql.elements.TextClause):
  666. default = sa_schema.DefaultClause(default, _reflected=True)
  667. elif not isinstance(default, sa_schema.FetchedValue):
  668. default = sa_schema.DefaultClause(
  669. sql.text(col_d["default"]), _reflected=True
  670. )
  671. colargs.append(default)
  672. if "computed" in col_d:
  673. computed = sa_schema.Computed(**col_d["computed"])
  674. colargs.append(computed)
  675. if "identity" in col_d:
  676. computed = sa_schema.Identity(**col_d["identity"])
  677. colargs.append(computed)
  678. if "sequence" in col_d:
  679. self._reflect_col_sequence(col_d, colargs)
  680. cols_by_orig_name[orig_name] = col = sa_schema.Column(
  681. name, coltype, *colargs, **col_kw
  682. )
  683. if col.key in table.primary_key:
  684. col.primary_key = True
  685. table.append_column(col, replace_existing=True)
  686. def _reflect_col_sequence(self, col_d, colargs):
  687. if "sequence" in col_d:
  688. # TODO: mssql and sybase are using this.
  689. seq = col_d["sequence"]
  690. sequence = sa_schema.Sequence(seq["name"], 1, 1)
  691. if "start" in seq:
  692. sequence.start = seq["start"]
  693. if "increment" in seq:
  694. sequence.increment = seq["increment"]
  695. colargs.append(sequence)
  696. def _reflect_pk(
  697. self, table_name, schema, table, cols_by_orig_name, exclude_columns
  698. ):
  699. pk_cons = self.get_pk_constraint(
  700. table_name, schema, **table.dialect_kwargs
  701. )
  702. if pk_cons:
  703. pk_cols = [
  704. cols_by_orig_name[pk]
  705. for pk in pk_cons["constrained_columns"]
  706. if pk in cols_by_orig_name and pk not in exclude_columns
  707. ]
  708. # update pk constraint name
  709. table.primary_key.name = pk_cons.get("name")
  710. # tell the PKConstraint to re-initialize
  711. # its column collection
  712. table.primary_key._reload(pk_cols)
  713. def _reflect_fk(
  714. self,
  715. table_name,
  716. schema,
  717. table,
  718. cols_by_orig_name,
  719. exclude_columns,
  720. resolve_fks,
  721. _extend_on,
  722. reflection_options,
  723. ):
  724. fkeys = self.get_foreign_keys(
  725. table_name, schema, **table.dialect_kwargs
  726. )
  727. for fkey_d in fkeys:
  728. conname = fkey_d["name"]
  729. # look for columns by orig name in cols_by_orig_name,
  730. # but support columns that are in-Python only as fallback
  731. constrained_columns = [
  732. cols_by_orig_name[c].key if c in cols_by_orig_name else c
  733. for c in fkey_d["constrained_columns"]
  734. ]
  735. if exclude_columns and set(constrained_columns).intersection(
  736. exclude_columns
  737. ):
  738. continue
  739. referred_schema = fkey_d["referred_schema"]
  740. referred_table = fkey_d["referred_table"]
  741. referred_columns = fkey_d["referred_columns"]
  742. refspec = []
  743. if referred_schema is not None:
  744. if resolve_fks:
  745. sa_schema.Table(
  746. referred_table,
  747. table.metadata,
  748. schema=referred_schema,
  749. autoload_with=self.bind,
  750. _extend_on=_extend_on,
  751. **reflection_options
  752. )
  753. for column in referred_columns:
  754. refspec.append(
  755. ".".join([referred_schema, referred_table, column])
  756. )
  757. else:
  758. if resolve_fks:
  759. sa_schema.Table(
  760. referred_table,
  761. table.metadata,
  762. autoload_with=self.bind,
  763. schema=sa_schema.BLANK_SCHEMA,
  764. _extend_on=_extend_on,
  765. **reflection_options
  766. )
  767. for column in referred_columns:
  768. refspec.append(".".join([referred_table, column]))
  769. if "options" in fkey_d:
  770. options = fkey_d["options"]
  771. else:
  772. options = {}
  773. table.append_constraint(
  774. sa_schema.ForeignKeyConstraint(
  775. constrained_columns,
  776. refspec,
  777. conname,
  778. link_to_name=True,
  779. **options
  780. )
  781. )
  782. _index_sort_exprs = [
  783. ("asc", operators.asc_op),
  784. ("desc", operators.desc_op),
  785. ("nulls_first", operators.nulls_first_op),
  786. ("nulls_last", operators.nulls_last_op),
  787. ]
  788. def _reflect_indexes(
  789. self,
  790. table_name,
  791. schema,
  792. table,
  793. cols_by_orig_name,
  794. include_columns,
  795. exclude_columns,
  796. reflection_options,
  797. ):
  798. # Indexes
  799. indexes = self.get_indexes(table_name, schema)
  800. for index_d in indexes:
  801. name = index_d["name"]
  802. columns = index_d["column_names"]
  803. column_sorting = index_d.get("column_sorting", {})
  804. unique = index_d["unique"]
  805. flavor = index_d.get("type", "index")
  806. dialect_options = index_d.get("dialect_options", {})
  807. duplicates = index_d.get("duplicates_constraint")
  808. if include_columns and not set(columns).issubset(include_columns):
  809. util.warn(
  810. "Omitting %s key for (%s), key covers omitted columns."
  811. % (flavor, ", ".join(columns))
  812. )
  813. continue
  814. if duplicates:
  815. continue
  816. # look for columns by orig name in cols_by_orig_name,
  817. # but support columns that are in-Python only as fallback
  818. idx_cols = []
  819. for c in columns:
  820. try:
  821. idx_col = (
  822. cols_by_orig_name[c]
  823. if c in cols_by_orig_name
  824. else table.c[c]
  825. )
  826. except KeyError:
  827. util.warn(
  828. "%s key '%s' was not located in "
  829. "columns for table '%s'" % (flavor, c, table_name)
  830. )
  831. continue
  832. c_sorting = column_sorting.get(c, ())
  833. for k, op in self._index_sort_exprs:
  834. if k in c_sorting:
  835. idx_col = op(idx_col)
  836. idx_cols.append(idx_col)
  837. sa_schema.Index(
  838. name,
  839. *idx_cols,
  840. _table=table,
  841. **dict(list(dialect_options.items()) + [("unique", unique)])
  842. )
  843. def _reflect_unique_constraints(
  844. self,
  845. table_name,
  846. schema,
  847. table,
  848. cols_by_orig_name,
  849. include_columns,
  850. exclude_columns,
  851. reflection_options,
  852. ):
  853. # Unique Constraints
  854. try:
  855. constraints = self.get_unique_constraints(table_name, schema)
  856. except NotImplementedError:
  857. # optional dialect feature
  858. return
  859. for const_d in constraints:
  860. conname = const_d["name"]
  861. columns = const_d["column_names"]
  862. duplicates = const_d.get("duplicates_index")
  863. if include_columns and not set(columns).issubset(include_columns):
  864. util.warn(
  865. "Omitting unique constraint key for (%s), "
  866. "key covers omitted columns." % ", ".join(columns)
  867. )
  868. continue
  869. if duplicates:
  870. continue
  871. # look for columns by orig name in cols_by_orig_name,
  872. # but support columns that are in-Python only as fallback
  873. constrained_cols = []
  874. for c in columns:
  875. try:
  876. constrained_col = (
  877. cols_by_orig_name[c]
  878. if c in cols_by_orig_name
  879. else table.c[c]
  880. )
  881. except KeyError:
  882. util.warn(
  883. "unique constraint key '%s' was not located in "
  884. "columns for table '%s'" % (c, table_name)
  885. )
  886. else:
  887. constrained_cols.append(constrained_col)
  888. table.append_constraint(
  889. sa_schema.UniqueConstraint(*constrained_cols, name=conname)
  890. )
  891. def _reflect_check_constraints(
  892. self,
  893. table_name,
  894. schema,
  895. table,
  896. cols_by_orig_name,
  897. include_columns,
  898. exclude_columns,
  899. reflection_options,
  900. ):
  901. try:
  902. constraints = self.get_check_constraints(table_name, schema)
  903. except NotImplementedError:
  904. # optional dialect feature
  905. return
  906. for const_d in constraints:
  907. table.append_constraint(sa_schema.CheckConstraint(**const_d))
  908. def _reflect_table_comment(
  909. self, table_name, schema, table, reflection_options
  910. ):
  911. try:
  912. comment_dict = self.get_table_comment(table_name, schema)
  913. except NotImplementedError:
  914. return
  915. else:
  916. table.comment = comment_dict.get("text", None)