SQLAlchemy 2.0.47__cp313-cp313t-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3557 @@
1
+ # testing/suite/test_reflection.py
2
+ # Copyright (C) 2005-2026 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
+
9
+ import contextlib
10
+ import operator
11
+ import re
12
+
13
+ import sqlalchemy as sa
14
+ from .. import config
15
+ from .. import engines
16
+ from .. import eq_
17
+ from .. import eq_regex
18
+ from .. import expect_raises
19
+ from .. import expect_raises_message
20
+ from .. import expect_warnings
21
+ from .. import fixtures
22
+ from .. import is_
23
+ from ..provision import get_temp_table_name
24
+ from ..provision import temp_table_keyword_args
25
+ from ..schema import Column
26
+ from ..schema import Table
27
+ from ... import Boolean
28
+ from ... import DateTime
29
+ from ... import event
30
+ from ... import ForeignKey
31
+ from ... import func
32
+ from ... import Identity
33
+ from ... import inspect
34
+ from ... import Integer
35
+ from ... import MetaData
36
+ from ... import String
37
+ from ... import testing
38
+ from ... import types as sql_types
39
+ from ...engine import Inspector
40
+ from ...engine import ObjectKind
41
+ from ...engine import ObjectScope
42
+ from ...exc import NoSuchTableError
43
+ from ...exc import UnreflectableTableError
44
+ from ...schema import DDL
45
+ from ...schema import Index
46
+ from ...sql.elements import quoted_name
47
+ from ...sql.schema import BLANK_SCHEMA
48
+ from ...testing import ComparesIndexes
49
+ from ...testing import ComparesTables
50
+ from ...testing import is_false
51
+ from ...testing import is_none
52
+ from ...testing import is_true
53
+ from ...testing import mock
54
+
55
+
56
+ metadata, users = None, None
57
+
58
+
59
+ class OneConnectionTablesTest(fixtures.TablesTest):
60
+ @classmethod
61
+ def setup_bind(cls):
62
+ # TODO: when temp tables are subject to server reset,
63
+ # this will also have to disable that server reset from
64
+ # happening
65
+ if config.requirements.independent_connections.enabled:
66
+ from sqlalchemy import pool
67
+
68
+ return engines.testing_engine(
69
+ options=dict(poolclass=pool.StaticPool, scope="class"),
70
+ )
71
+ else:
72
+ return config.db
73
+
74
+
75
+ class HasTableTest(OneConnectionTablesTest):
76
+ __sparse_driver_backend__ = True
77
+
78
+ run_deletes = None
79
+
80
+ @classmethod
81
+ def define_tables(cls, metadata):
82
+ Table(
83
+ "test_table",
84
+ metadata,
85
+ Column("id", Integer, primary_key=True),
86
+ Column("data", String(50)),
87
+ )
88
+ if testing.requires.schemas.enabled:
89
+ Table(
90
+ "test_table_s",
91
+ metadata,
92
+ Column("id", Integer, primary_key=True),
93
+ Column("data", String(50)),
94
+ schema=config.test_schema,
95
+ )
96
+
97
+ if testing.requires.view_reflection:
98
+ cls.define_views(metadata)
99
+ if testing.requires.has_temp_table.enabled:
100
+ cls.define_temp_tables(metadata)
101
+
102
+ @classmethod
103
+ def define_views(cls, metadata):
104
+ query = "CREATE VIEW vv AS SELECT id, data FROM test_table"
105
+
106
+ event.listen(metadata, "after_create", DDL(query))
107
+ event.listen(metadata, "before_drop", DDL("DROP VIEW vv"))
108
+
109
+ if testing.requires.schemas.enabled:
110
+ query = (
111
+ "CREATE VIEW %s.vv AS SELECT id, data FROM %s.test_table_s"
112
+ % (
113
+ config.test_schema,
114
+ config.test_schema,
115
+ )
116
+ )
117
+ event.listen(metadata, "after_create", DDL(query))
118
+ event.listen(
119
+ metadata,
120
+ "before_drop",
121
+ DDL("DROP VIEW %s.vv" % (config.test_schema)),
122
+ )
123
+
124
+ @classmethod
125
+ def temp_table_name(cls):
126
+ return get_temp_table_name(
127
+ config, config.db, f"user_tmp_{config.ident}"
128
+ )
129
+
130
+ @classmethod
131
+ def define_temp_tables(cls, metadata):
132
+ kw = temp_table_keyword_args(config, config.db)
133
+ table_name = cls.temp_table_name()
134
+ user_tmp = Table(
135
+ table_name,
136
+ metadata,
137
+ Column("id", sa.INT, primary_key=True),
138
+ Column("name", sa.VARCHAR(50)),
139
+ **kw,
140
+ )
141
+ if (
142
+ testing.requires.view_reflection.enabled
143
+ and testing.requires.temporary_views.enabled
144
+ ):
145
+ event.listen(
146
+ user_tmp,
147
+ "after_create",
148
+ DDL(
149
+ "create temporary view user_tmp_v as "
150
+ "select * from user_tmp_%s" % config.ident
151
+ ),
152
+ )
153
+ event.listen(user_tmp, "before_drop", DDL("drop view user_tmp_v"))
154
+
155
+ def test_has_table(self):
156
+ with config.db.begin() as conn:
157
+ is_true(config.db.dialect.has_table(conn, "test_table"))
158
+ is_false(config.db.dialect.has_table(conn, "test_table_s"))
159
+ is_false(config.db.dialect.has_table(conn, "nonexistent_table"))
160
+
161
+ def test_has_table_cache(self, metadata):
162
+ insp = inspect(config.db)
163
+ is_true(insp.has_table("test_table"))
164
+ nt = Table("new_table", metadata, Column("col", Integer))
165
+ is_false(insp.has_table("new_table"))
166
+ nt.create(config.db)
167
+ try:
168
+ is_false(insp.has_table("new_table"))
169
+ insp.clear_cache()
170
+ is_true(insp.has_table("new_table"))
171
+ finally:
172
+ nt.drop(config.db)
173
+
174
+ @testing.requires.schemas
175
+ def test_has_table_schema(self):
176
+ with config.db.begin() as conn:
177
+ is_false(
178
+ config.db.dialect.has_table(
179
+ conn, "test_table", schema=config.test_schema
180
+ )
181
+ )
182
+ is_true(
183
+ config.db.dialect.has_table(
184
+ conn, "test_table_s", schema=config.test_schema
185
+ )
186
+ )
187
+ is_false(
188
+ config.db.dialect.has_table(
189
+ conn, "nonexistent_table", schema=config.test_schema
190
+ )
191
+ )
192
+
193
+ @testing.requires.schemas
194
+ def test_has_table_nonexistent_schema(self):
195
+ with config.db.begin() as conn:
196
+ is_false(
197
+ config.db.dialect.has_table(
198
+ conn, "test_table", schema="nonexistent_schema"
199
+ )
200
+ )
201
+
202
+ @testing.requires.views
203
+ def test_has_table_view(self, connection):
204
+ insp = inspect(connection)
205
+ is_true(insp.has_table("vv"))
206
+
207
+ @testing.requires.has_temp_table
208
+ def test_has_table_temp_table(self, connection):
209
+ insp = inspect(connection)
210
+ temp_table_name = self.temp_table_name()
211
+ is_true(insp.has_table(temp_table_name))
212
+
213
+ @testing.requires.has_temp_table
214
+ @testing.requires.view_reflection
215
+ @testing.requires.temporary_views
216
+ def test_has_table_temp_view(self, connection):
217
+ insp = inspect(connection)
218
+ is_true(insp.has_table("user_tmp_v"))
219
+
220
+ @testing.requires.views
221
+ @testing.requires.schemas
222
+ def test_has_table_view_schema(self, connection):
223
+ insp = inspect(connection)
224
+ is_true(insp.has_table("vv", config.test_schema))
225
+
226
+
227
+ class HasIndexTest(fixtures.TablesTest):
228
+ __sparse_driver_backend__ = True
229
+ __requires__ = ("index_reflection",)
230
+
231
+ @classmethod
232
+ def define_tables(cls, metadata):
233
+ tt = Table(
234
+ "test_table",
235
+ metadata,
236
+ Column("id", Integer, primary_key=True),
237
+ Column("data", String(50)),
238
+ Column("data2", String(50)),
239
+ )
240
+ Index("my_idx", tt.c.data)
241
+
242
+ if testing.requires.schemas.enabled:
243
+ tt = Table(
244
+ "test_table",
245
+ metadata,
246
+ Column("id", Integer, primary_key=True),
247
+ Column("data", String(50)),
248
+ schema=config.test_schema,
249
+ )
250
+ Index("my_idx_s", tt.c.data)
251
+
252
+ kind = testing.combinations("dialect", "inspector", argnames="kind")
253
+
254
+ def _has_index(self, kind, conn):
255
+ if kind == "dialect":
256
+ return lambda *a, **k: config.db.dialect.has_index(conn, *a, **k)
257
+ else:
258
+ return inspect(conn).has_index
259
+
260
+ @kind
261
+ def test_has_index(self, kind, connection, metadata):
262
+ meth = self._has_index(kind, connection)
263
+ assert meth("test_table", "my_idx")
264
+ assert not meth("test_table", "my_idx_s")
265
+ assert not meth("nonexistent_table", "my_idx")
266
+ assert not meth("test_table", "nonexistent_idx")
267
+
268
+ assert not meth("test_table", "my_idx_2")
269
+ assert not meth("test_table_2", "my_idx_3")
270
+ idx = Index("my_idx_2", self.tables.test_table.c.data2)
271
+ tbl = Table(
272
+ "test_table_2",
273
+ metadata,
274
+ Column("foo", Integer),
275
+ Index("my_idx_3", "foo"),
276
+ )
277
+ idx.create(connection)
278
+ tbl.create(connection)
279
+ try:
280
+ if kind == "inspector":
281
+ assert not meth("test_table", "my_idx_2")
282
+ assert not meth("test_table_2", "my_idx_3")
283
+ meth.__self__.clear_cache()
284
+ assert meth("test_table", "my_idx_2") is True
285
+ assert meth("test_table_2", "my_idx_3") is True
286
+ finally:
287
+ tbl.drop(connection)
288
+ idx.drop(connection)
289
+
290
+ @testing.requires.schemas
291
+ @kind
292
+ def test_has_index_schema(self, kind, connection):
293
+ meth = self._has_index(kind, connection)
294
+ assert meth("test_table", "my_idx_s", schema=config.test_schema)
295
+ assert not meth("test_table", "my_idx", schema=config.test_schema)
296
+ assert not meth(
297
+ "nonexistent_table", "my_idx_s", schema=config.test_schema
298
+ )
299
+ assert not meth(
300
+ "test_table", "nonexistent_idx_s", schema=config.test_schema
301
+ )
302
+
303
+
304
+ class BizarroCharacterTest(fixtures.TestBase):
305
+
306
+ __sparse_driver_backend__ = True
307
+
308
+ def column_names():
309
+ return testing.combinations(
310
+ ("plainname",),
311
+ ("(3)",),
312
+ ("col%p",),
313
+ ("[brack]",),
314
+ argnames="columnname",
315
+ )
316
+
317
+ def table_names():
318
+ return testing.combinations(
319
+ ("plain",),
320
+ ("(2)",),
321
+ ("per % cent",),
322
+ ("[brackets]",),
323
+ argnames="tablename",
324
+ )
325
+
326
+ @testing.variation("use_composite", [True, False])
327
+ @column_names()
328
+ @table_names()
329
+ @testing.requires.foreign_key_constraint_reflection
330
+ def test_fk_ref(
331
+ self, connection, metadata, use_composite, tablename, columnname
332
+ ):
333
+ """tests for #10275"""
334
+ tt = Table(
335
+ tablename,
336
+ metadata,
337
+ Column(columnname, Integer, key="id", primary_key=True),
338
+ test_needs_fk=True,
339
+ )
340
+ if use_composite:
341
+ tt.append_column(Column("id2", Integer, primary_key=True))
342
+
343
+ if use_composite:
344
+ Table(
345
+ "other",
346
+ metadata,
347
+ Column("id", Integer, primary_key=True),
348
+ Column("ref", Integer),
349
+ Column("ref2", Integer),
350
+ sa.ForeignKeyConstraint(["ref", "ref2"], [tt.c.id, tt.c.id2]),
351
+ test_needs_fk=True,
352
+ )
353
+ else:
354
+ Table(
355
+ "other",
356
+ metadata,
357
+ Column("id", Integer, primary_key=True),
358
+ Column("ref", ForeignKey(tt.c.id)),
359
+ test_needs_fk=True,
360
+ )
361
+
362
+ metadata.create_all(connection)
363
+
364
+ m2 = MetaData()
365
+
366
+ o2 = Table("other", m2, autoload_with=connection)
367
+ t1 = m2.tables[tablename]
368
+
369
+ assert o2.c.ref.references(t1.c[0])
370
+ if use_composite:
371
+ assert o2.c.ref2.references(t1.c[1])
372
+
373
+ @column_names()
374
+ @table_names()
375
+ @testing.requires.identity_columns
376
+ def test_reflect_identity(
377
+ self, tablename, columnname, connection, metadata
378
+ ):
379
+ Table(
380
+ tablename,
381
+ metadata,
382
+ Column(columnname, Integer, Identity(), primary_key=True),
383
+ )
384
+ metadata.create_all(connection)
385
+ insp = inspect(connection)
386
+
387
+ eq_(insp.get_columns(tablename)[0]["identity"]["start"], 1)
388
+
389
+ @column_names()
390
+ @table_names()
391
+ @testing.requires.comment_reflection
392
+ def test_reflect_comments(
393
+ self, tablename, columnname, connection, metadata
394
+ ):
395
+ Table(
396
+ tablename,
397
+ metadata,
398
+ Column("id", Integer, primary_key=True),
399
+ Column(columnname, Integer, comment="some comment"),
400
+ )
401
+ metadata.create_all(connection)
402
+ insp = inspect(connection)
403
+
404
+ eq_(insp.get_columns(tablename)[1]["comment"], "some comment")
405
+
406
+
407
+ class TempTableElementsTest(fixtures.TestBase):
408
+
409
+ __sparse_driver_backend__ = True
410
+
411
+ __requires__ = ("temp_table_reflection",)
412
+
413
+ @testing.fixture
414
+ def tablename(self):
415
+ return get_temp_table_name(
416
+ config, config.db, f"ident_tmp_{config.ident}"
417
+ )
418
+
419
+ @testing.requires.identity_columns
420
+ def test_reflect_identity(self, tablename, connection, metadata):
421
+ Table(
422
+ tablename,
423
+ metadata,
424
+ Column("id", Integer, Identity(), primary_key=True),
425
+ )
426
+ metadata.create_all(connection)
427
+ insp = inspect(connection)
428
+
429
+ eq_(insp.get_columns(tablename)[0]["identity"]["start"], 1)
430
+
431
+ @testing.requires.temp_table_comment_reflection
432
+ def test_reflect_comments(self, tablename, connection, metadata):
433
+ Table(
434
+ tablename,
435
+ metadata,
436
+ Column("id", Integer, primary_key=True),
437
+ Column("foobar", Integer, comment="some comment"),
438
+ )
439
+ metadata.create_all(connection)
440
+ insp = inspect(connection)
441
+
442
+ eq_(insp.get_columns(tablename)[1]["comment"], "some comment")
443
+
444
+
445
+ class QuotedNameArgumentTest(fixtures.TablesTest):
446
+ run_create_tables = "once"
447
+ __sparse_driver_backend__ = True
448
+
449
+ @classmethod
450
+ def define_tables(cls, metadata):
451
+ Table(
452
+ "quote ' one",
453
+ metadata,
454
+ Column("id", Integer),
455
+ Column("name", String(50)),
456
+ Column("data", String(50)),
457
+ Column("related_id", Integer),
458
+ sa.PrimaryKeyConstraint("id", name="pk quote ' one"),
459
+ sa.Index("ix quote ' one", "name"),
460
+ sa.UniqueConstraint(
461
+ "data",
462
+ name="uq quote' one",
463
+ ),
464
+ sa.ForeignKeyConstraint(
465
+ ["id"], ["related.id"], name="fk quote ' one"
466
+ ),
467
+ sa.CheckConstraint("name != 'foo'", name="ck quote ' one"),
468
+ comment=r"""quote ' one comment""",
469
+ test_needs_fk=True,
470
+ )
471
+
472
+ if testing.requires.symbol_names_w_double_quote.enabled:
473
+ Table(
474
+ 'quote " two',
475
+ metadata,
476
+ Column("id", Integer),
477
+ Column("name", String(50)),
478
+ Column("data", String(50)),
479
+ Column("related_id", Integer),
480
+ sa.PrimaryKeyConstraint("id", name='pk quote " two'),
481
+ sa.Index('ix quote " two', "name"),
482
+ sa.UniqueConstraint(
483
+ "data",
484
+ name='uq quote" two',
485
+ ),
486
+ sa.ForeignKeyConstraint(
487
+ ["id"], ["related.id"], name='fk quote " two'
488
+ ),
489
+ sa.CheckConstraint("name != 'foo'", name='ck quote " two '),
490
+ comment=r"""quote " two comment""",
491
+ test_needs_fk=True,
492
+ )
493
+
494
+ Table(
495
+ "related",
496
+ metadata,
497
+ Column("id", Integer, primary_key=True),
498
+ Column("related", Integer),
499
+ test_needs_fk=True,
500
+ )
501
+
502
+ if testing.requires.view_column_reflection.enabled:
503
+ if testing.requires.symbol_names_w_double_quote.enabled:
504
+ names = [
505
+ "quote ' one",
506
+ 'quote " two',
507
+ ]
508
+ else:
509
+ names = [
510
+ "quote ' one",
511
+ ]
512
+ for name in names:
513
+ query = "CREATE VIEW %s AS SELECT * FROM %s" % (
514
+ config.db.dialect.identifier_preparer.quote(
515
+ "view %s" % name
516
+ ),
517
+ config.db.dialect.identifier_preparer.quote(name),
518
+ )
519
+
520
+ event.listen(metadata, "after_create", DDL(query))
521
+ event.listen(
522
+ metadata,
523
+ "before_drop",
524
+ DDL(
525
+ "DROP VIEW %s"
526
+ % config.db.dialect.identifier_preparer.quote(
527
+ "view %s" % name
528
+ )
529
+ ),
530
+ )
531
+
532
+ def quote_fixtures(fn):
533
+ return testing.combinations(
534
+ ("quote ' one",),
535
+ ('quote " two', testing.requires.symbol_names_w_double_quote),
536
+ )(fn)
537
+
538
+ @quote_fixtures
539
+ def test_get_table_options(self, name):
540
+ insp = inspect(config.db)
541
+
542
+ if testing.requires.reflect_table_options.enabled:
543
+ res = insp.get_table_options(name)
544
+ is_true(isinstance(res, dict))
545
+ else:
546
+ with expect_raises(NotImplementedError):
547
+ insp.get_table_options(name)
548
+
549
+ @quote_fixtures
550
+ @testing.requires.view_column_reflection
551
+ def test_get_view_definition(self, name):
552
+ insp = inspect(config.db)
553
+ assert insp.get_view_definition("view %s" % name)
554
+
555
+ @quote_fixtures
556
+ def test_get_columns(self, name):
557
+ insp = inspect(config.db)
558
+ assert insp.get_columns(name)
559
+
560
+ @quote_fixtures
561
+ def test_get_pk_constraint(self, name):
562
+ insp = inspect(config.db)
563
+ assert insp.get_pk_constraint(name)
564
+
565
+ @quote_fixtures
566
+ @testing.requires.foreign_key_constraint_reflection
567
+ def test_get_foreign_keys(self, name):
568
+ insp = inspect(config.db)
569
+ assert insp.get_foreign_keys(name)
570
+
571
+ @quote_fixtures
572
+ @testing.requires.index_reflection
573
+ def test_get_indexes(self, name):
574
+ insp = inspect(config.db)
575
+ assert insp.get_indexes(name)
576
+
577
+ @quote_fixtures
578
+ @testing.requires.unique_constraint_reflection
579
+ def test_get_unique_constraints(self, name):
580
+ insp = inspect(config.db)
581
+ assert insp.get_unique_constraints(name)
582
+
583
+ @quote_fixtures
584
+ @testing.requires.comment_reflection
585
+ def test_get_table_comment(self, name):
586
+ insp = inspect(config.db)
587
+ assert insp.get_table_comment(name)
588
+
589
+ @quote_fixtures
590
+ @testing.requires.check_constraint_reflection
591
+ def test_get_check_constraints(self, name):
592
+ insp = inspect(config.db)
593
+ assert insp.get_check_constraints(name)
594
+
595
+
596
+ def _multi_combination(fn):
597
+ schema = testing.combinations(
598
+ None,
599
+ (
600
+ lambda: config.test_schema,
601
+ testing.requires.schemas,
602
+ ),
603
+ argnames="schema",
604
+ )
605
+ scope = testing.combinations(
606
+ ObjectScope.DEFAULT,
607
+ ObjectScope.TEMPORARY,
608
+ ObjectScope.ANY,
609
+ argnames="scope",
610
+ )
611
+ kind = testing.combinations(
612
+ ObjectKind.TABLE,
613
+ ObjectKind.VIEW,
614
+ ObjectKind.MATERIALIZED_VIEW,
615
+ ObjectKind.ANY,
616
+ ObjectKind.ANY_VIEW,
617
+ ObjectKind.TABLE | ObjectKind.VIEW,
618
+ ObjectKind.TABLE | ObjectKind.MATERIALIZED_VIEW,
619
+ argnames="kind",
620
+ )
621
+ filter_names = testing.combinations(True, False, argnames="use_filter")
622
+
623
+ return schema(scope(kind(filter_names(fn))))
624
+
625
+
626
+ class ComponentReflectionTest(ComparesTables, OneConnectionTablesTest):
627
+ run_inserts = run_deletes = None
628
+
629
+ __sparse_driver_backend__ = True
630
+
631
+ @classmethod
632
+ def define_tables(cls, metadata):
633
+ cls.define_reflected_tables(metadata, None)
634
+ if testing.requires.schemas.enabled:
635
+ cls.define_reflected_tables(metadata, testing.config.test_schema)
636
+
637
+ @classmethod
638
+ def define_reflected_tables(cls, metadata, schema):
639
+ if schema:
640
+ schema_prefix = schema + "."
641
+ else:
642
+ schema_prefix = ""
643
+
644
+ if testing.requires.self_referential_foreign_keys.enabled:
645
+ parent_id_args = (
646
+ ForeignKey(
647
+ "%susers.user_id" % schema_prefix, name="user_id_fk"
648
+ ),
649
+ )
650
+ else:
651
+ parent_id_args = ()
652
+ users = Table(
653
+ "users",
654
+ metadata,
655
+ Column("user_id", sa.INT, primary_key=True),
656
+ Column("test1", sa.CHAR(5), nullable=False),
657
+ Column("test2", sa.Float(), nullable=False),
658
+ Column("parent_user_id", sa.Integer, *parent_id_args),
659
+ sa.CheckConstraint(
660
+ "test2 > 0",
661
+ name="zz_test2_gt_zero",
662
+ comment="users check constraint",
663
+ ),
664
+ sa.CheckConstraint("test2 <= 1000"),
665
+ schema=schema,
666
+ test_needs_fk=True,
667
+ )
668
+
669
+ Table(
670
+ "dingalings",
671
+ metadata,
672
+ Column("dingaling_id", sa.Integer, primary_key=True),
673
+ Column(
674
+ "address_id",
675
+ sa.Integer,
676
+ ForeignKey(
677
+ "%semail_addresses.address_id" % schema_prefix,
678
+ name="zz_email_add_id_fg",
679
+ comment="di fk comment",
680
+ ),
681
+ ),
682
+ Column(
683
+ "id_user",
684
+ sa.Integer,
685
+ ForeignKey("%susers.user_id" % schema_prefix),
686
+ ),
687
+ Column("data", sa.String(30), unique=True),
688
+ sa.CheckConstraint(
689
+ "address_id > 0 AND address_id < 1000",
690
+ name="address_id_gt_zero",
691
+ ),
692
+ sa.UniqueConstraint(
693
+ "address_id",
694
+ "dingaling_id",
695
+ name="zz_dingalings_multiple",
696
+ comment="di unique comment",
697
+ ),
698
+ schema=schema,
699
+ test_needs_fk=True,
700
+ )
701
+ Table(
702
+ "email_addresses",
703
+ metadata,
704
+ Column("address_id", sa.Integer),
705
+ Column("remote_user_id", sa.Integer, ForeignKey(users.c.user_id)),
706
+ Column("email_address", sa.String(20), index=True),
707
+ sa.PrimaryKeyConstraint(
708
+ "address_id", name="email_ad_pk", comment="ea pk comment"
709
+ ),
710
+ schema=schema,
711
+ test_needs_fk=True,
712
+ )
713
+ Table(
714
+ "comment_test",
715
+ metadata,
716
+ Column("id", sa.Integer, primary_key=True, comment="id comment"),
717
+ Column("data", sa.String(20), comment="data % comment"),
718
+ Column(
719
+ "d2",
720
+ sa.String(20),
721
+ comment=r"""Comment types type speedily ' " \ '' Fun!""",
722
+ ),
723
+ Column("d3", sa.String(42), comment="Comment\nwith\rescapes"),
724
+ schema=schema,
725
+ comment=r"""the test % ' " \ table comment""",
726
+ )
727
+ Table(
728
+ "no_constraints",
729
+ metadata,
730
+ Column("data", sa.String(20)),
731
+ schema=schema,
732
+ comment="no\nconstraints\rhas\fescaped\vcomment",
733
+ )
734
+
735
+ if testing.requires.cross_schema_fk_reflection.enabled:
736
+ if schema is None:
737
+ Table(
738
+ "local_table",
739
+ metadata,
740
+ Column("id", sa.Integer, primary_key=True),
741
+ Column("data", sa.String(20)),
742
+ Column(
743
+ "remote_id",
744
+ ForeignKey(
745
+ "%s.remote_table_2.id" % testing.config.test_schema
746
+ ),
747
+ ),
748
+ test_needs_fk=True,
749
+ schema=config.db.dialect.default_schema_name,
750
+ )
751
+ else:
752
+ Table(
753
+ "remote_table",
754
+ metadata,
755
+ Column("id", sa.Integer, primary_key=True),
756
+ Column(
757
+ "local_id",
758
+ ForeignKey(
759
+ "%s.local_table.id"
760
+ % config.db.dialect.default_schema_name
761
+ ),
762
+ ),
763
+ Column("data", sa.String(20)),
764
+ schema=schema,
765
+ test_needs_fk=True,
766
+ )
767
+ Table(
768
+ "remote_table_2",
769
+ metadata,
770
+ Column("id", sa.Integer, primary_key=True),
771
+ Column("data", sa.String(20)),
772
+ schema=schema,
773
+ test_needs_fk=True,
774
+ )
775
+
776
+ if testing.requires.index_reflection.enabled:
777
+ Index("users_t_idx", users.c.test1, users.c.test2, unique=True)
778
+ Index(
779
+ "users_all_idx", users.c.user_id, users.c.test2, users.c.test1
780
+ )
781
+
782
+ if not schema:
783
+ # test_needs_fk is at the moment to force MySQL InnoDB
784
+ noncol_idx_test_nopk = Table(
785
+ "noncol_idx_test_nopk",
786
+ metadata,
787
+ Column("q", sa.String(5)),
788
+ test_needs_fk=True,
789
+ )
790
+
791
+ noncol_idx_test_pk = Table(
792
+ "noncol_idx_test_pk",
793
+ metadata,
794
+ Column("id", sa.Integer, primary_key=True),
795
+ Column("q", sa.String(5)),
796
+ test_needs_fk=True,
797
+ )
798
+
799
+ if (
800
+ testing.requires.indexes_with_ascdesc.enabled
801
+ and testing.requires.reflect_indexes_with_ascdesc.enabled
802
+ ):
803
+ Index("noncol_idx_nopk", noncol_idx_test_nopk.c.q.desc())
804
+ Index("noncol_idx_pk", noncol_idx_test_pk.c.q.desc())
805
+
806
+ if testing.requires.view_column_reflection.enabled:
807
+ cls.define_views(metadata, schema)
808
+ if not schema and testing.requires.temp_table_reflection.enabled:
809
+ cls.define_temp_tables(metadata)
810
+
811
+ @classmethod
812
+ def temp_table_name(cls):
813
+ return get_temp_table_name(
814
+ config, config.db, f"user_tmp_{config.ident}"
815
+ )
816
+
817
+ @classmethod
818
+ def define_temp_tables(cls, metadata):
819
+ kw = temp_table_keyword_args(config, config.db)
820
+ table_name = cls.temp_table_name()
821
+ user_tmp = Table(
822
+ table_name,
823
+ metadata,
824
+ Column("id", sa.INT, primary_key=True),
825
+ Column("name", sa.VARCHAR(50)),
826
+ Column("foo", sa.INT),
827
+ # disambiguate temp table unique constraint names. this is
828
+ # pretty arbitrary for a generic dialect however we are doing
829
+ # it to suit SQL Server which will produce name conflicts for
830
+ # unique constraints created against temp tables in different
831
+ # databases.
832
+ # https://www.arbinada.com/en/node/1645
833
+ sa.UniqueConstraint("name", name=f"user_tmp_uq_{config.ident}"),
834
+ sa.Index("user_tmp_ix", "foo"),
835
+ **kw,
836
+ )
837
+ if (
838
+ testing.requires.view_reflection.enabled
839
+ and testing.requires.temporary_views.enabled
840
+ ):
841
+ event.listen(
842
+ user_tmp,
843
+ "after_create",
844
+ DDL(
845
+ "create temporary view user_tmp_v as "
846
+ "select * from user_tmp_%s" % config.ident
847
+ ),
848
+ )
849
+ event.listen(user_tmp, "before_drop", DDL("drop view user_tmp_v"))
850
+
851
+ @classmethod
852
+ def define_views(cls, metadata, schema):
853
+ if testing.requires.materialized_views.enabled:
854
+ materialized = {"dingalings"}
855
+ else:
856
+ materialized = set()
857
+ for table_name in ("users", "email_addresses", "dingalings"):
858
+ fullname = table_name
859
+ if schema:
860
+ fullname = f"{schema}.{table_name}"
861
+ view_name = fullname + "_v"
862
+ prefix = "MATERIALIZED " if table_name in materialized else ""
863
+ query = (
864
+ f"CREATE {prefix}VIEW {view_name} AS SELECT * FROM {fullname}"
865
+ )
866
+
867
+ event.listen(metadata, "after_create", DDL(query))
868
+ if table_name in materialized:
869
+ index_name = "mat_index"
870
+ if schema and testing.against("oracle"):
871
+ index_name = f"{schema}.{index_name}"
872
+ idx = f"CREATE INDEX {index_name} ON {view_name}(data)"
873
+ event.listen(metadata, "after_create", DDL(idx))
874
+ event.listen(
875
+ metadata, "before_drop", DDL(f"DROP {prefix}VIEW {view_name}")
876
+ )
877
+
878
+ def _resolve_kind(self, kind, tables, views, materialized):
879
+ res = {}
880
+ if ObjectKind.TABLE in kind:
881
+ res.update(tables)
882
+ if ObjectKind.VIEW in kind:
883
+ res.update(views)
884
+ if ObjectKind.MATERIALIZED_VIEW in kind:
885
+ res.update(materialized)
886
+ return res
887
+
888
+ def _resolve_views(self, views, materialized):
889
+ if not testing.requires.view_column_reflection.enabled:
890
+ materialized.clear()
891
+ views.clear()
892
+ elif not testing.requires.materialized_views.enabled:
893
+ views.update(materialized)
894
+ materialized.clear()
895
+
896
+ def _resolve_names(self, schema, scope, filter_names, values):
897
+ scope_filter = lambda _: True # noqa: E731
898
+ if scope is ObjectScope.DEFAULT:
899
+ scope_filter = lambda k: "tmp" not in k[1] # noqa: E731
900
+ if scope is ObjectScope.TEMPORARY:
901
+ scope_filter = lambda k: "tmp" in k[1] # noqa: E731
902
+
903
+ removed = {
904
+ None: {"remote_table", "remote_table_2"},
905
+ testing.config.test_schema: {
906
+ "local_table",
907
+ "noncol_idx_test_nopk",
908
+ "noncol_idx_test_pk",
909
+ "user_tmp_v",
910
+ self.temp_table_name(),
911
+ },
912
+ }
913
+ if not testing.requires.cross_schema_fk_reflection.enabled:
914
+ removed[None].add("local_table")
915
+ removed[testing.config.test_schema].update(
916
+ ["remote_table", "remote_table_2"]
917
+ )
918
+ if not testing.requires.index_reflection.enabled:
919
+ removed[None].update(
920
+ ["noncol_idx_test_nopk", "noncol_idx_test_pk"]
921
+ )
922
+ if (
923
+ not testing.requires.temp_table_reflection.enabled
924
+ or not testing.requires.temp_table_names.enabled
925
+ ):
926
+ removed[None].update(["user_tmp_v", self.temp_table_name()])
927
+ if not testing.requires.temporary_views.enabled:
928
+ removed[None].update(["user_tmp_v"])
929
+
930
+ res = {
931
+ k: v
932
+ for k, v in values.items()
933
+ if scope_filter(k)
934
+ and k[1] not in removed[schema]
935
+ and (not filter_names or k[1] in filter_names)
936
+ }
937
+ return res
938
+
939
+ def exp_options(
940
+ self,
941
+ schema=None,
942
+ scope=ObjectScope.ANY,
943
+ kind=ObjectKind.ANY,
944
+ filter_names=None,
945
+ ):
946
+ materialized = {(schema, "dingalings_v"): mock.ANY}
947
+ views = {
948
+ (schema, "email_addresses_v"): mock.ANY,
949
+ (schema, "users_v"): mock.ANY,
950
+ (schema, "user_tmp_v"): mock.ANY,
951
+ }
952
+ self._resolve_views(views, materialized)
953
+ tables = {
954
+ (schema, "users"): mock.ANY,
955
+ (schema, "dingalings"): mock.ANY,
956
+ (schema, "email_addresses"): mock.ANY,
957
+ (schema, "comment_test"): mock.ANY,
958
+ (schema, "no_constraints"): mock.ANY,
959
+ (schema, "local_table"): mock.ANY,
960
+ (schema, "remote_table"): mock.ANY,
961
+ (schema, "remote_table_2"): mock.ANY,
962
+ (schema, "noncol_idx_test_nopk"): mock.ANY,
963
+ (schema, "noncol_idx_test_pk"): mock.ANY,
964
+ (schema, self.temp_table_name()): mock.ANY,
965
+ }
966
+ res = self._resolve_kind(kind, tables, views, materialized)
967
+ res = self._resolve_names(schema, scope, filter_names, res)
968
+ return res
969
+
970
+ def exp_comments(
971
+ self,
972
+ schema=None,
973
+ scope=ObjectScope.ANY,
974
+ kind=ObjectKind.ANY,
975
+ filter_names=None,
976
+ ):
977
+ empty = {"text": None}
978
+ materialized = {(schema, "dingalings_v"): empty}
979
+ views = {
980
+ (schema, "email_addresses_v"): empty,
981
+ (schema, "users_v"): empty,
982
+ (schema, "user_tmp_v"): empty,
983
+ }
984
+ self._resolve_views(views, materialized)
985
+ tables = {
986
+ (schema, "users"): empty,
987
+ (schema, "dingalings"): empty,
988
+ (schema, "email_addresses"): empty,
989
+ (schema, "comment_test"): {
990
+ "text": r"""the test % ' " \ table comment"""
991
+ },
992
+ (schema, "no_constraints"): {
993
+ "text": "no\nconstraints\rhas\fescaped\vcomment"
994
+ },
995
+ (schema, "local_table"): empty,
996
+ (schema, "remote_table"): empty,
997
+ (schema, "remote_table_2"): empty,
998
+ (schema, "noncol_idx_test_nopk"): empty,
999
+ (schema, "noncol_idx_test_pk"): empty,
1000
+ (schema, self.temp_table_name()): empty,
1001
+ }
1002
+ res = self._resolve_kind(kind, tables, views, materialized)
1003
+ res = self._resolve_names(schema, scope, filter_names, res)
1004
+ return res
1005
+
1006
+ def exp_columns(
1007
+ self,
1008
+ schema=None,
1009
+ scope=ObjectScope.ANY,
1010
+ kind=ObjectKind.ANY,
1011
+ filter_names=None,
1012
+ ):
1013
+ def col(
1014
+ name, auto=False, default=mock.ANY, comment=None, nullable=True
1015
+ ):
1016
+ res = {
1017
+ "name": name,
1018
+ "autoincrement": auto,
1019
+ "type": mock.ANY,
1020
+ "default": default,
1021
+ "comment": comment,
1022
+ "nullable": nullable,
1023
+ }
1024
+ if auto == "omit":
1025
+ res.pop("autoincrement")
1026
+ return res
1027
+
1028
+ def pk(name, **kw):
1029
+ kw = {"auto": True, "default": mock.ANY, "nullable": False, **kw}
1030
+ return col(name, **kw)
1031
+
1032
+ materialized = {
1033
+ (schema, "dingalings_v"): [
1034
+ col("dingaling_id", auto="omit", nullable=mock.ANY),
1035
+ col("address_id"),
1036
+ col("id_user"),
1037
+ col("data"),
1038
+ ]
1039
+ }
1040
+ views = {
1041
+ (schema, "email_addresses_v"): [
1042
+ col("address_id", auto="omit", nullable=mock.ANY),
1043
+ col("remote_user_id"),
1044
+ col("email_address"),
1045
+ ],
1046
+ (schema, "users_v"): [
1047
+ col("user_id", auto="omit", nullable=mock.ANY),
1048
+ col("test1", nullable=mock.ANY),
1049
+ col("test2", nullable=mock.ANY),
1050
+ col("parent_user_id"),
1051
+ ],
1052
+ (schema, "user_tmp_v"): [
1053
+ col("id", auto="omit", nullable=mock.ANY),
1054
+ col("name"),
1055
+ col("foo"),
1056
+ ],
1057
+ }
1058
+ self._resolve_views(views, materialized)
1059
+ tables = {
1060
+ (schema, "users"): [
1061
+ pk("user_id"),
1062
+ col("test1", nullable=False),
1063
+ col("test2", nullable=False),
1064
+ col("parent_user_id"),
1065
+ ],
1066
+ (schema, "dingalings"): [
1067
+ pk("dingaling_id"),
1068
+ col("address_id"),
1069
+ col("id_user"),
1070
+ col("data"),
1071
+ ],
1072
+ (schema, "email_addresses"): [
1073
+ pk("address_id"),
1074
+ col("remote_user_id"),
1075
+ col("email_address"),
1076
+ ],
1077
+ (schema, "comment_test"): [
1078
+ pk("id", comment="id comment"),
1079
+ col("data", comment="data % comment"),
1080
+ col(
1081
+ "d2",
1082
+ comment=r"""Comment types type speedily ' " \ '' Fun!""",
1083
+ ),
1084
+ col("d3", comment="Comment\nwith\rescapes"),
1085
+ ],
1086
+ (schema, "no_constraints"): [col("data")],
1087
+ (schema, "local_table"): [pk("id"), col("data"), col("remote_id")],
1088
+ (schema, "remote_table"): [pk("id"), col("local_id"), col("data")],
1089
+ (schema, "remote_table_2"): [pk("id"), col("data")],
1090
+ (schema, "noncol_idx_test_nopk"): [col("q")],
1091
+ (schema, "noncol_idx_test_pk"): [pk("id"), col("q")],
1092
+ (schema, self.temp_table_name()): [
1093
+ pk("id"),
1094
+ col("name"),
1095
+ col("foo"),
1096
+ ],
1097
+ }
1098
+ res = self._resolve_kind(kind, tables, views, materialized)
1099
+ res = self._resolve_names(schema, scope, filter_names, res)
1100
+ return res
1101
+
1102
+ @property
1103
+ def _required_column_keys(self):
1104
+ return {"name", "type", "nullable", "default"}
1105
+
1106
+ def exp_pks(
1107
+ self,
1108
+ schema=None,
1109
+ scope=ObjectScope.ANY,
1110
+ kind=ObjectKind.ANY,
1111
+ filter_names=None,
1112
+ ):
1113
+ def pk(*cols, name=mock.ANY, comment=None):
1114
+ return {
1115
+ "constrained_columns": list(cols),
1116
+ "name": name,
1117
+ "comment": comment,
1118
+ }
1119
+
1120
+ empty = pk(name=None)
1121
+ if testing.requires.materialized_views_reflect_pk.enabled:
1122
+ materialized = {(schema, "dingalings_v"): pk("dingaling_id")}
1123
+ else:
1124
+ materialized = {(schema, "dingalings_v"): empty}
1125
+ views = {
1126
+ (schema, "email_addresses_v"): empty,
1127
+ (schema, "users_v"): empty,
1128
+ (schema, "user_tmp_v"): empty,
1129
+ }
1130
+ self._resolve_views(views, materialized)
1131
+ tables = {
1132
+ (schema, "users"): pk("user_id"),
1133
+ (schema, "dingalings"): pk("dingaling_id"),
1134
+ (schema, "email_addresses"): pk(
1135
+ "address_id", name="email_ad_pk", comment="ea pk comment"
1136
+ ),
1137
+ (schema, "comment_test"): pk("id"),
1138
+ (schema, "no_constraints"): empty,
1139
+ (schema, "local_table"): pk("id"),
1140
+ (schema, "remote_table"): pk("id"),
1141
+ (schema, "remote_table_2"): pk("id"),
1142
+ (schema, "noncol_idx_test_nopk"): empty,
1143
+ (schema, "noncol_idx_test_pk"): pk("id"),
1144
+ (schema, self.temp_table_name()): pk("id"),
1145
+ }
1146
+ if not testing.requires.reflects_pk_names.enabled:
1147
+ for val in tables.values():
1148
+ if val["name"] is not None:
1149
+ val["name"] = mock.ANY
1150
+ res = self._resolve_kind(kind, tables, views, materialized)
1151
+ res = self._resolve_names(schema, scope, filter_names, res)
1152
+ return res
1153
+
1154
+ @property
1155
+ def _required_pk_keys(self):
1156
+ return {"name", "constrained_columns"}
1157
+
1158
+ def exp_fks(
1159
+ self,
1160
+ schema=None,
1161
+ scope=ObjectScope.ANY,
1162
+ kind=ObjectKind.ANY,
1163
+ filter_names=None,
1164
+ ):
1165
+ class tt:
1166
+ def __eq__(self, other):
1167
+ return (
1168
+ other is None
1169
+ or config.db.dialect.default_schema_name == other
1170
+ )
1171
+
1172
+ def fk(
1173
+ cols,
1174
+ ref_col,
1175
+ ref_table,
1176
+ ref_schema=schema,
1177
+ name=mock.ANY,
1178
+ comment=None,
1179
+ ):
1180
+ return {
1181
+ "constrained_columns": cols,
1182
+ "referred_columns": ref_col,
1183
+ "name": name,
1184
+ "options": mock.ANY,
1185
+ "referred_schema": (
1186
+ ref_schema if ref_schema is not None else tt()
1187
+ ),
1188
+ "referred_table": ref_table,
1189
+ "comment": comment,
1190
+ }
1191
+
1192
+ materialized = {(schema, "dingalings_v"): []}
1193
+ views = {
1194
+ (schema, "email_addresses_v"): [],
1195
+ (schema, "users_v"): [],
1196
+ (schema, "user_tmp_v"): [],
1197
+ }
1198
+ self._resolve_views(views, materialized)
1199
+ tables = {
1200
+ (schema, "users"): [
1201
+ fk(["parent_user_id"], ["user_id"], "users", name="user_id_fk")
1202
+ ],
1203
+ (schema, "dingalings"): [
1204
+ fk(["id_user"], ["user_id"], "users"),
1205
+ fk(
1206
+ ["address_id"],
1207
+ ["address_id"],
1208
+ "email_addresses",
1209
+ name="zz_email_add_id_fg",
1210
+ comment="di fk comment",
1211
+ ),
1212
+ ],
1213
+ (schema, "email_addresses"): [
1214
+ fk(["remote_user_id"], ["user_id"], "users")
1215
+ ],
1216
+ (schema, "comment_test"): [],
1217
+ (schema, "no_constraints"): [],
1218
+ (schema, "local_table"): [
1219
+ fk(
1220
+ ["remote_id"],
1221
+ ["id"],
1222
+ "remote_table_2",
1223
+ ref_schema=config.test_schema,
1224
+ )
1225
+ ],
1226
+ (schema, "remote_table"): [
1227
+ fk(["local_id"], ["id"], "local_table", ref_schema=None)
1228
+ ],
1229
+ (schema, "remote_table_2"): [],
1230
+ (schema, "noncol_idx_test_nopk"): [],
1231
+ (schema, "noncol_idx_test_pk"): [],
1232
+ (schema, self.temp_table_name()): [],
1233
+ }
1234
+ if not testing.requires.self_referential_foreign_keys.enabled:
1235
+ tables[(schema, "users")].clear()
1236
+ if not testing.requires.named_constraints.enabled:
1237
+ for vals in tables.values():
1238
+ for val in vals:
1239
+ if val["name"] is not mock.ANY:
1240
+ val["name"] = mock.ANY
1241
+
1242
+ res = self._resolve_kind(kind, tables, views, materialized)
1243
+ res = self._resolve_names(schema, scope, filter_names, res)
1244
+ return res
1245
+
1246
+ @property
1247
+ def _required_fk_keys(self):
1248
+ return {
1249
+ "name",
1250
+ "constrained_columns",
1251
+ "referred_schema",
1252
+ "referred_table",
1253
+ "referred_columns",
1254
+ }
1255
+
1256
+ def exp_indexes(
1257
+ self,
1258
+ schema=None,
1259
+ scope=ObjectScope.ANY,
1260
+ kind=ObjectKind.ANY,
1261
+ filter_names=None,
1262
+ ):
1263
+ def idx(
1264
+ *cols,
1265
+ name,
1266
+ unique=False,
1267
+ column_sorting=None,
1268
+ duplicates=False,
1269
+ fk=False,
1270
+ ):
1271
+ fk_req = testing.requires.foreign_keys_reflect_as_index
1272
+ dup_req = testing.requires.unique_constraints_reflect_as_index
1273
+ sorting_expression = (
1274
+ testing.requires.reflect_indexes_with_ascdesc_as_expression
1275
+ )
1276
+
1277
+ if (fk and not fk_req.enabled) or (
1278
+ duplicates and not dup_req.enabled
1279
+ ):
1280
+ return ()
1281
+ res = {
1282
+ "unique": unique,
1283
+ "column_names": list(cols),
1284
+ "name": name,
1285
+ "dialect_options": mock.ANY,
1286
+ "include_columns": [],
1287
+ }
1288
+ if column_sorting:
1289
+ res["column_sorting"] = column_sorting
1290
+ if sorting_expression.enabled:
1291
+ res["expressions"] = orig = res["column_names"]
1292
+ res["column_names"] = [
1293
+ None if c in column_sorting else c for c in orig
1294
+ ]
1295
+
1296
+ if duplicates:
1297
+ res["duplicates_constraint"] = name
1298
+ return [res]
1299
+
1300
+ materialized = {(schema, "dingalings_v"): []}
1301
+ views = {
1302
+ (schema, "email_addresses_v"): [],
1303
+ (schema, "users_v"): [],
1304
+ (schema, "user_tmp_v"): [],
1305
+ }
1306
+ self._resolve_views(views, materialized)
1307
+ if materialized:
1308
+ materialized[(schema, "dingalings_v")].extend(
1309
+ idx("data", name="mat_index")
1310
+ )
1311
+ tables = {
1312
+ (schema, "users"): [
1313
+ *idx("parent_user_id", name="user_id_fk", fk=True),
1314
+ *idx("user_id", "test2", "test1", name="users_all_idx"),
1315
+ *idx("test1", "test2", name="users_t_idx", unique=True),
1316
+ ],
1317
+ (schema, "dingalings"): [
1318
+ *idx("data", name=mock.ANY, unique=True, duplicates=True),
1319
+ *idx("id_user", name=mock.ANY, fk=True),
1320
+ *idx(
1321
+ "address_id",
1322
+ "dingaling_id",
1323
+ name="zz_dingalings_multiple",
1324
+ unique=True,
1325
+ duplicates=True,
1326
+ ),
1327
+ ],
1328
+ (schema, "email_addresses"): [
1329
+ *idx("email_address", name=mock.ANY),
1330
+ *idx("remote_user_id", name=mock.ANY, fk=True),
1331
+ ],
1332
+ (schema, "comment_test"): [],
1333
+ (schema, "no_constraints"): [],
1334
+ (schema, "local_table"): [
1335
+ *idx("remote_id", name=mock.ANY, fk=True)
1336
+ ],
1337
+ (schema, "remote_table"): [
1338
+ *idx("local_id", name=mock.ANY, fk=True)
1339
+ ],
1340
+ (schema, "remote_table_2"): [],
1341
+ (schema, "noncol_idx_test_nopk"): [
1342
+ *idx(
1343
+ "q",
1344
+ name="noncol_idx_nopk",
1345
+ column_sorting={"q": ("desc",)},
1346
+ )
1347
+ ],
1348
+ (schema, "noncol_idx_test_pk"): [
1349
+ *idx(
1350
+ "q", name="noncol_idx_pk", column_sorting={"q": ("desc",)}
1351
+ )
1352
+ ],
1353
+ (schema, self.temp_table_name()): [
1354
+ *idx("foo", name="user_tmp_ix"),
1355
+ *idx(
1356
+ "name",
1357
+ name=f"user_tmp_uq_{config.ident}",
1358
+ duplicates=True,
1359
+ unique=True,
1360
+ ),
1361
+ ],
1362
+ }
1363
+ if (
1364
+ not testing.requires.indexes_with_ascdesc.enabled
1365
+ or not testing.requires.reflect_indexes_with_ascdesc.enabled
1366
+ ):
1367
+ tables[(schema, "noncol_idx_test_nopk")].clear()
1368
+ tables[(schema, "noncol_idx_test_pk")].clear()
1369
+ res = self._resolve_kind(kind, tables, views, materialized)
1370
+ res = self._resolve_names(schema, scope, filter_names, res)
1371
+ return res
1372
+
1373
+ @property
1374
+ def _required_index_keys(self):
1375
+ return {"name", "column_names", "unique"}
1376
+
1377
+ def exp_ucs(
1378
+ self,
1379
+ schema=None,
1380
+ scope=ObjectScope.ANY,
1381
+ kind=ObjectKind.ANY,
1382
+ filter_names=None,
1383
+ all_=False,
1384
+ ):
1385
+ def uc(
1386
+ *cols, name, duplicates_index=None, is_index=False, comment=None
1387
+ ):
1388
+ req = testing.requires.unique_index_reflect_as_unique_constraints
1389
+ if is_index and not req.enabled:
1390
+ return ()
1391
+ res = {
1392
+ "column_names": list(cols),
1393
+ "name": name,
1394
+ "comment": comment,
1395
+ }
1396
+ if duplicates_index:
1397
+ res["duplicates_index"] = duplicates_index
1398
+ return [res]
1399
+
1400
+ materialized = {(schema, "dingalings_v"): []}
1401
+ views = {
1402
+ (schema, "email_addresses_v"): [],
1403
+ (schema, "users_v"): [],
1404
+ (schema, "user_tmp_v"): [],
1405
+ }
1406
+ self._resolve_views(views, materialized)
1407
+ tables = {
1408
+ (schema, "users"): [
1409
+ *uc(
1410
+ "test1",
1411
+ "test2",
1412
+ name="users_t_idx",
1413
+ duplicates_index="users_t_idx",
1414
+ is_index=True,
1415
+ )
1416
+ ],
1417
+ (schema, "dingalings"): [
1418
+ *uc("data", name=mock.ANY, duplicates_index=mock.ANY),
1419
+ *uc(
1420
+ "address_id",
1421
+ "dingaling_id",
1422
+ name="zz_dingalings_multiple",
1423
+ duplicates_index="zz_dingalings_multiple",
1424
+ comment="di unique comment",
1425
+ ),
1426
+ ],
1427
+ (schema, "email_addresses"): [],
1428
+ (schema, "comment_test"): [],
1429
+ (schema, "no_constraints"): [],
1430
+ (schema, "local_table"): [],
1431
+ (schema, "remote_table"): [],
1432
+ (schema, "remote_table_2"): [],
1433
+ (schema, "noncol_idx_test_nopk"): [],
1434
+ (schema, "noncol_idx_test_pk"): [],
1435
+ (schema, self.temp_table_name()): [
1436
+ *uc("name", name=f"user_tmp_uq_{config.ident}")
1437
+ ],
1438
+ }
1439
+ if all_:
1440
+ return {**materialized, **views, **tables}
1441
+ else:
1442
+ res = self._resolve_kind(kind, tables, views, materialized)
1443
+ res = self._resolve_names(schema, scope, filter_names, res)
1444
+ return res
1445
+
1446
+ @property
1447
+ def _required_unique_cst_keys(self):
1448
+ return {"name", "column_names"}
1449
+
1450
+ def exp_ccs(
1451
+ self,
1452
+ schema=None,
1453
+ scope=ObjectScope.ANY,
1454
+ kind=ObjectKind.ANY,
1455
+ filter_names=None,
1456
+ ):
1457
+ class tt(str):
1458
+ def __eq__(self, other):
1459
+ res = (
1460
+ other.lower()
1461
+ .replace("(", "")
1462
+ .replace(")", "")
1463
+ .replace("`", "")
1464
+ )
1465
+ return self in res
1466
+
1467
+ def cc(text, name, comment=None):
1468
+ return {"sqltext": tt(text), "name": name, "comment": comment}
1469
+
1470
+ # print({1: "test2 > (0)::double precision"} == {1: tt("test2 > 0")})
1471
+ # assert 0
1472
+ materialized = {(schema, "dingalings_v"): []}
1473
+ views = {
1474
+ (schema, "email_addresses_v"): [],
1475
+ (schema, "users_v"): [],
1476
+ (schema, "user_tmp_v"): [],
1477
+ }
1478
+ self._resolve_views(views, materialized)
1479
+ tables = {
1480
+ (schema, "users"): [
1481
+ cc("test2 <= 1000", mock.ANY),
1482
+ cc(
1483
+ "test2 > 0",
1484
+ "zz_test2_gt_zero",
1485
+ comment="users check constraint",
1486
+ ),
1487
+ ],
1488
+ (schema, "dingalings"): [
1489
+ cc(
1490
+ "address_id > 0 and address_id < 1000",
1491
+ name="address_id_gt_zero",
1492
+ ),
1493
+ ],
1494
+ (schema, "email_addresses"): [],
1495
+ (schema, "comment_test"): [],
1496
+ (schema, "no_constraints"): [],
1497
+ (schema, "local_table"): [],
1498
+ (schema, "remote_table"): [],
1499
+ (schema, "remote_table_2"): [],
1500
+ (schema, "noncol_idx_test_nopk"): [],
1501
+ (schema, "noncol_idx_test_pk"): [],
1502
+ (schema, self.temp_table_name()): [],
1503
+ }
1504
+ res = self._resolve_kind(kind, tables, views, materialized)
1505
+ res = self._resolve_names(schema, scope, filter_names, res)
1506
+ return res
1507
+
1508
+ @property
1509
+ def _required_cc_keys(self):
1510
+ return {"name", "sqltext"}
1511
+
1512
+ @testing.requires.schema_reflection
1513
+ def test_get_schema_names(self, connection):
1514
+ insp = inspect(connection)
1515
+
1516
+ is_true(testing.config.test_schema in insp.get_schema_names())
1517
+
1518
+ @testing.requires.schema_reflection
1519
+ def test_has_schema(self, connection):
1520
+ insp = inspect(connection)
1521
+
1522
+ is_true(insp.has_schema(testing.config.test_schema))
1523
+ is_false(insp.has_schema("sa_fake_schema_foo"))
1524
+
1525
+ @testing.requires.schema_reflection
1526
+ def test_get_schema_names_w_translate_map(self, connection):
1527
+ """test #7300"""
1528
+
1529
+ connection = connection.execution_options(
1530
+ schema_translate_map={
1531
+ "foo": "bar",
1532
+ BLANK_SCHEMA: testing.config.test_schema,
1533
+ }
1534
+ )
1535
+ insp = inspect(connection)
1536
+
1537
+ is_true(testing.config.test_schema in insp.get_schema_names())
1538
+
1539
+ @testing.requires.schema_reflection
1540
+ def test_has_schema_w_translate_map(self, connection):
1541
+ connection = connection.execution_options(
1542
+ schema_translate_map={
1543
+ "foo": "bar",
1544
+ BLANK_SCHEMA: testing.config.test_schema,
1545
+ }
1546
+ )
1547
+ insp = inspect(connection)
1548
+
1549
+ is_true(insp.has_schema(testing.config.test_schema))
1550
+ is_false(insp.has_schema("sa_fake_schema_foo"))
1551
+
1552
+ @testing.requires.schema_reflection
1553
+ @testing.requires.schema_create_delete
1554
+ def test_schema_cache(self, connection):
1555
+ insp = inspect(connection)
1556
+
1557
+ is_false("foo_bar" in insp.get_schema_names())
1558
+ is_false(insp.has_schema("foo_bar"))
1559
+ connection.execute(DDL("CREATE SCHEMA foo_bar"))
1560
+ try:
1561
+ is_false("foo_bar" in insp.get_schema_names())
1562
+ is_false(insp.has_schema("foo_bar"))
1563
+ insp.clear_cache()
1564
+ is_true("foo_bar" in insp.get_schema_names())
1565
+ is_true(insp.has_schema("foo_bar"))
1566
+ finally:
1567
+ connection.execute(DDL("DROP SCHEMA foo_bar"))
1568
+
1569
+ @testing.requires.schema_reflection
1570
+ def test_dialect_initialize(self):
1571
+ engine = engines.testing_engine()
1572
+ inspect(engine)
1573
+ assert hasattr(engine.dialect, "default_schema_name")
1574
+
1575
+ @testing.requires.schema_reflection
1576
+ def test_get_default_schema_name(self, connection):
1577
+ insp = inspect(connection)
1578
+ eq_(insp.default_schema_name, connection.dialect.default_schema_name)
1579
+
1580
+ @testing.combinations(
1581
+ None,
1582
+ ("foreign_key", testing.requires.foreign_key_constraint_reflection),
1583
+ argnames="order_by",
1584
+ )
1585
+ @testing.combinations(
1586
+ (True, testing.requires.schemas), False, argnames="use_schema"
1587
+ )
1588
+ def test_get_table_names(self, connection, order_by, use_schema):
1589
+ if use_schema:
1590
+ schema = config.test_schema
1591
+ else:
1592
+ schema = None
1593
+
1594
+ _ignore_tables = {
1595
+ "comment_test",
1596
+ "noncol_idx_test_pk",
1597
+ "noncol_idx_test_nopk",
1598
+ "local_table",
1599
+ "remote_table",
1600
+ "remote_table_2",
1601
+ "no_constraints",
1602
+ }
1603
+
1604
+ insp = inspect(connection)
1605
+
1606
+ if order_by:
1607
+ tables = [
1608
+ rec[0]
1609
+ for rec in insp.get_sorted_table_and_fkc_names(schema)
1610
+ if rec[0]
1611
+ ]
1612
+ else:
1613
+ tables = insp.get_table_names(schema)
1614
+ table_names = [t for t in tables if t not in _ignore_tables]
1615
+
1616
+ if order_by == "foreign_key":
1617
+ answer = ["users", "email_addresses", "dingalings"]
1618
+ eq_(table_names, answer)
1619
+ else:
1620
+ answer = ["dingalings", "email_addresses", "users"]
1621
+ eq_(sorted(table_names), answer)
1622
+
1623
+ @testing.combinations(
1624
+ (True, testing.requires.schemas), False, argnames="use_schema"
1625
+ )
1626
+ def test_get_view_names(self, connection, use_schema):
1627
+ insp = inspect(connection)
1628
+ if use_schema:
1629
+ schema = config.test_schema
1630
+ else:
1631
+ schema = None
1632
+ table_names = insp.get_view_names(schema)
1633
+ if testing.requires.materialized_views.enabled:
1634
+ eq_(sorted(table_names), ["email_addresses_v", "users_v"])
1635
+ eq_(insp.get_materialized_view_names(schema), ["dingalings_v"])
1636
+ else:
1637
+ answer = ["dingalings_v", "email_addresses_v", "users_v"]
1638
+ eq_(sorted(table_names), answer)
1639
+
1640
+ @testing.requires.temp_table_names
1641
+ def test_get_temp_table_names(self, connection):
1642
+ insp = inspect(connection)
1643
+ temp_table_names = insp.get_temp_table_names()
1644
+ eq_(sorted(temp_table_names), [f"user_tmp_{config.ident}"])
1645
+
1646
+ @testing.requires.view_reflection
1647
+ @testing.requires.temporary_views
1648
+ def test_get_temp_view_names(self, connection):
1649
+ insp = inspect(connection)
1650
+ temp_table_names = insp.get_temp_view_names()
1651
+ eq_(sorted(temp_table_names), ["user_tmp_v"])
1652
+
1653
+ @testing.requires.comment_reflection
1654
+ def test_get_comments(self, connection):
1655
+ self._test_get_comments(connection)
1656
+
1657
+ @testing.requires.comment_reflection
1658
+ @testing.requires.schemas
1659
+ def test_get_comments_with_schema(self, connection):
1660
+ self._test_get_comments(connection, testing.config.test_schema)
1661
+
1662
+ def _test_get_comments(self, connection, schema=None):
1663
+ insp = inspect(connection)
1664
+ exp = self.exp_comments(schema=schema)
1665
+ eq_(
1666
+ insp.get_table_comment("comment_test", schema=schema),
1667
+ exp[(schema, "comment_test")],
1668
+ )
1669
+
1670
+ eq_(
1671
+ insp.get_table_comment("users", schema=schema),
1672
+ exp[(schema, "users")],
1673
+ )
1674
+
1675
+ eq_(
1676
+ insp.get_table_comment("comment_test", schema=schema),
1677
+ exp[(schema, "comment_test")],
1678
+ )
1679
+
1680
+ no_cst = self.tables.no_constraints.name
1681
+ eq_(
1682
+ insp.get_table_comment(no_cst, schema=schema),
1683
+ exp[(schema, no_cst)],
1684
+ )
1685
+
1686
+ @testing.combinations(
1687
+ (False, False),
1688
+ (False, True, testing.requires.schemas),
1689
+ (True, False, testing.requires.view_reflection),
1690
+ (
1691
+ True,
1692
+ True,
1693
+ testing.requires.schemas + testing.requires.view_reflection,
1694
+ ),
1695
+ argnames="use_views,use_schema",
1696
+ )
1697
+ def test_get_columns(self, connection, use_views, use_schema):
1698
+ if use_schema:
1699
+ schema = config.test_schema
1700
+ else:
1701
+ schema = None
1702
+
1703
+ users, addresses = (self.tables.users, self.tables.email_addresses)
1704
+ if use_views:
1705
+ table_names = ["users_v", "email_addresses_v", "dingalings_v"]
1706
+ else:
1707
+ table_names = ["users", "email_addresses"]
1708
+
1709
+ insp = inspect(connection)
1710
+ for table_name, table in zip(table_names, (users, addresses)):
1711
+ schema_name = schema
1712
+ cols = insp.get_columns(table_name, schema=schema_name)
1713
+ is_true(len(cols) > 0, len(cols))
1714
+
1715
+ # should be in order
1716
+
1717
+ for i, col in enumerate(table.columns):
1718
+ eq_(col.name, cols[i]["name"])
1719
+ ctype = cols[i]["type"].__class__
1720
+ ctype_def = col.type
1721
+ if isinstance(ctype_def, sa.types.TypeEngine):
1722
+ ctype_def = ctype_def.__class__
1723
+
1724
+ # Oracle returns Date for DateTime.
1725
+
1726
+ if testing.against("oracle") and ctype_def in (
1727
+ sql_types.Date,
1728
+ sql_types.DateTime,
1729
+ ):
1730
+ ctype_def = sql_types.Date
1731
+
1732
+ # assert that the desired type and return type share
1733
+ # a base within one of the generic types.
1734
+
1735
+ is_true(
1736
+ len(
1737
+ set(ctype.__mro__)
1738
+ .intersection(ctype_def.__mro__)
1739
+ .intersection(
1740
+ [
1741
+ sql_types.Integer,
1742
+ sql_types.Numeric,
1743
+ sql_types.DateTime,
1744
+ sql_types.Date,
1745
+ sql_types.Time,
1746
+ sql_types.String,
1747
+ sql_types._Binary,
1748
+ ]
1749
+ )
1750
+ )
1751
+ > 0,
1752
+ "%s(%s), %s(%s)"
1753
+ % (col.name, col.type, cols[i]["name"], ctype),
1754
+ )
1755
+
1756
+ if not col.primary_key:
1757
+ assert cols[i]["default"] is None
1758
+
1759
+ # The case of a table with no column
1760
+ # is tested below in TableNoColumnsTest
1761
+
1762
+ @testing.requires.temp_table_reflection
1763
+ def test_reflect_table_temp_table(self, connection):
1764
+ table_name = self.temp_table_name()
1765
+ user_tmp = self.tables[table_name]
1766
+
1767
+ reflected_user_tmp = Table(
1768
+ table_name, MetaData(), autoload_with=connection
1769
+ )
1770
+ self.assert_tables_equal(
1771
+ user_tmp, reflected_user_tmp, strict_constraints=False
1772
+ )
1773
+
1774
+ @testing.requires.temp_table_reflection
1775
+ def test_get_temp_table_columns(self, connection):
1776
+ table_name = self.temp_table_name()
1777
+ user_tmp = self.tables[table_name]
1778
+ insp = inspect(connection)
1779
+ cols = insp.get_columns(table_name)
1780
+ is_true(len(cols) > 0, len(cols))
1781
+
1782
+ for i, col in enumerate(user_tmp.columns):
1783
+ eq_(col.name, cols[i]["name"])
1784
+
1785
+ @testing.requires.temp_table_reflection
1786
+ @testing.requires.view_column_reflection
1787
+ @testing.requires.temporary_views
1788
+ def test_get_temp_view_columns(self, connection):
1789
+ insp = inspect(connection)
1790
+ cols = insp.get_columns("user_tmp_v")
1791
+ eq_([col["name"] for col in cols], ["id", "name", "foo"])
1792
+
1793
+ @testing.combinations(
1794
+ (False,), (True, testing.requires.schemas), argnames="use_schema"
1795
+ )
1796
+ @testing.requires.primary_key_constraint_reflection
1797
+ def test_get_pk_constraint(self, connection, use_schema):
1798
+ if use_schema:
1799
+ schema = testing.config.test_schema
1800
+ else:
1801
+ schema = None
1802
+
1803
+ users, addresses = self.tables.users, self.tables.email_addresses
1804
+ insp = inspect(connection)
1805
+ exp = self.exp_pks(schema=schema)
1806
+
1807
+ users_cons = insp.get_pk_constraint(users.name, schema=schema)
1808
+ self._check_list(
1809
+ [users_cons], [exp[(schema, users.name)]], self._required_pk_keys
1810
+ )
1811
+
1812
+ addr_cons = insp.get_pk_constraint(addresses.name, schema=schema)
1813
+ exp_cols = exp[(schema, addresses.name)]["constrained_columns"]
1814
+ eq_(addr_cons["constrained_columns"], exp_cols)
1815
+
1816
+ with testing.requires.reflects_pk_names.fail_if():
1817
+ eq_(addr_cons["name"], "email_ad_pk")
1818
+
1819
+ no_cst = self.tables.no_constraints.name
1820
+ self._check_list(
1821
+ [insp.get_pk_constraint(no_cst, schema=schema)],
1822
+ [exp[(schema, no_cst)]],
1823
+ self._required_pk_keys,
1824
+ )
1825
+
1826
+ @testing.combinations(
1827
+ "PK_test_table",
1828
+ "pk_test_table",
1829
+ "mixedCasePK",
1830
+ "pk.with.dots",
1831
+ argnames="pk_name",
1832
+ )
1833
+ @testing.requires.primary_key_constraint_reflection
1834
+ @testing.requires.reflects_pk_names
1835
+ def test_get_pk_constraint_quoted_name(
1836
+ self, connection, metadata, pk_name
1837
+ ):
1838
+ """Test that primary key constraint names with various casing are
1839
+ properly reflected."""
1840
+
1841
+ Table(
1842
+ "test_table",
1843
+ metadata,
1844
+ Column("id", Integer),
1845
+ Column("data", String(50)),
1846
+ sa.PrimaryKeyConstraint("id", name=pk_name),
1847
+ )
1848
+
1849
+ metadata.create_all(connection)
1850
+
1851
+ insp = inspect(connection)
1852
+ pk_cons = insp.get_pk_constraint("test_table")
1853
+
1854
+ eq_(pk_cons["name"], pk_name)
1855
+ eq_(pk_cons["constrained_columns"], ["id"])
1856
+
1857
+ @testing.combinations(
1858
+ (False,), (True, testing.requires.schemas), argnames="use_schema"
1859
+ )
1860
+ @testing.requires.foreign_key_constraint_reflection
1861
+ def test_get_foreign_keys(self, connection, use_schema):
1862
+ if use_schema:
1863
+ schema = config.test_schema
1864
+ else:
1865
+ schema = None
1866
+
1867
+ users, addresses = (self.tables.users, self.tables.email_addresses)
1868
+ insp = inspect(connection)
1869
+ expected_schema = schema
1870
+ # users
1871
+
1872
+ if testing.requires.self_referential_foreign_keys.enabled:
1873
+ users_fkeys = insp.get_foreign_keys(users.name, schema=schema)
1874
+ fkey1 = users_fkeys[0]
1875
+
1876
+ with testing.requires.named_constraints.fail_if():
1877
+ eq_(fkey1["name"], "user_id_fk")
1878
+
1879
+ eq_(fkey1["referred_schema"], expected_schema)
1880
+ eq_(fkey1["referred_table"], users.name)
1881
+ eq_(fkey1["referred_columns"], ["user_id"])
1882
+ eq_(fkey1["constrained_columns"], ["parent_user_id"])
1883
+
1884
+ # addresses
1885
+ addr_fkeys = insp.get_foreign_keys(addresses.name, schema=schema)
1886
+ fkey1 = addr_fkeys[0]
1887
+
1888
+ with testing.requires.implicitly_named_constraints.fail_if():
1889
+ is_true(fkey1["name"] is not None)
1890
+
1891
+ eq_(fkey1["referred_schema"], expected_schema)
1892
+ eq_(fkey1["referred_table"], users.name)
1893
+ eq_(fkey1["referred_columns"], ["user_id"])
1894
+ eq_(fkey1["constrained_columns"], ["remote_user_id"])
1895
+
1896
+ no_cst = self.tables.no_constraints.name
1897
+ eq_(insp.get_foreign_keys(no_cst, schema=schema), [])
1898
+
1899
+ @testing.combinations(
1900
+ "FK_users_id",
1901
+ "fk_users_id",
1902
+ "mixedCaseName",
1903
+ "fk.with.dots",
1904
+ argnames="fk_name",
1905
+ )
1906
+ @testing.requires.foreign_key_constraint_reflection
1907
+ def test_get_foreign_keys_quoted_name(self, connection, metadata, fk_name):
1908
+ """Test that foreign key constraint names with various casing are
1909
+ properly reflected."""
1910
+
1911
+ Table(
1912
+ "users_ref",
1913
+ metadata,
1914
+ Column("user_id", Integer, primary_key=True),
1915
+ test_needs_fk=True,
1916
+ )
1917
+
1918
+ Table(
1919
+ "user_orders",
1920
+ metadata,
1921
+ Column("order_id", Integer, primary_key=True),
1922
+ Column("user_id", Integer),
1923
+ sa.ForeignKeyConstraint(
1924
+ ["user_id"],
1925
+ ["users_ref.user_id"],
1926
+ name=fk_name,
1927
+ ),
1928
+ test_needs_fk=True,
1929
+ )
1930
+
1931
+ metadata.create_all(connection)
1932
+
1933
+ insp = inspect(connection)
1934
+ fkeys = insp.get_foreign_keys("user_orders")
1935
+
1936
+ eq_(len(fkeys), 1)
1937
+ fkey = fkeys[0]
1938
+
1939
+ with testing.requires.named_constraints.fail_if():
1940
+ eq_(fkey["name"], fk_name)
1941
+
1942
+ eq_(fkey["referred_table"], "users_ref")
1943
+ eq_(fkey["referred_columns"], ["user_id"])
1944
+ eq_(fkey["constrained_columns"], ["user_id"])
1945
+
1946
+ @testing.requires.cross_schema_fk_reflection
1947
+ @testing.requires.schemas
1948
+ def test_get_inter_schema_foreign_keys(self, connection):
1949
+ local_table, remote_table, remote_table_2 = self.tables(
1950
+ "%s.local_table" % connection.dialect.default_schema_name,
1951
+ "%s.remote_table" % testing.config.test_schema,
1952
+ "%s.remote_table_2" % testing.config.test_schema,
1953
+ )
1954
+
1955
+ insp = inspect(connection)
1956
+
1957
+ local_fkeys = insp.get_foreign_keys(local_table.name)
1958
+ eq_(len(local_fkeys), 1)
1959
+
1960
+ fkey1 = local_fkeys[0]
1961
+ eq_(fkey1["referred_schema"], testing.config.test_schema)
1962
+ eq_(fkey1["referred_table"], remote_table_2.name)
1963
+ eq_(fkey1["referred_columns"], ["id"])
1964
+ eq_(fkey1["constrained_columns"], ["remote_id"])
1965
+
1966
+ remote_fkeys = insp.get_foreign_keys(
1967
+ remote_table.name, schema=testing.config.test_schema
1968
+ )
1969
+ eq_(len(remote_fkeys), 1)
1970
+
1971
+ fkey2 = remote_fkeys[0]
1972
+
1973
+ is_true(
1974
+ fkey2["referred_schema"]
1975
+ in (
1976
+ None,
1977
+ connection.dialect.default_schema_name,
1978
+ )
1979
+ )
1980
+ eq_(fkey2["referred_table"], local_table.name)
1981
+ eq_(fkey2["referred_columns"], ["id"])
1982
+ eq_(fkey2["constrained_columns"], ["local_id"])
1983
+
1984
+ @testing.combinations(
1985
+ (False,), (True, testing.requires.schemas), argnames="use_schema"
1986
+ )
1987
+ @testing.requires.index_reflection
1988
+ def test_get_indexes(self, connection, use_schema):
1989
+ if use_schema:
1990
+ schema = config.test_schema
1991
+ else:
1992
+ schema = None
1993
+
1994
+ # The database may decide to create indexes for foreign keys, etc.
1995
+ # so there may be more indexes than expected.
1996
+ insp = inspect(connection)
1997
+ indexes = insp.get_indexes("users", schema=schema)
1998
+ exp = self.exp_indexes(schema=schema)
1999
+ self._check_list(
2000
+ indexes, exp[(schema, "users")], self._required_index_keys
2001
+ )
2002
+
2003
+ no_cst = self.tables.no_constraints.name
2004
+ self._check_list(
2005
+ insp.get_indexes(no_cst, schema=schema),
2006
+ exp[(schema, no_cst)],
2007
+ self._required_index_keys,
2008
+ )
2009
+
2010
+ @testing.combinations(
2011
+ ("noncol_idx_test_nopk", "noncol_idx_nopk"),
2012
+ ("noncol_idx_test_pk", "noncol_idx_pk"),
2013
+ argnames="tname,ixname",
2014
+ )
2015
+ @testing.requires.index_reflection
2016
+ @testing.requires.indexes_with_ascdesc
2017
+ @testing.requires.reflect_indexes_with_ascdesc
2018
+ def test_get_noncol_index(self, connection, tname, ixname):
2019
+ insp = inspect(connection)
2020
+ indexes = insp.get_indexes(tname)
2021
+ # reflecting an index that has "x DESC" in it as the column.
2022
+ # the DB may or may not give us "x", but make sure we get the index
2023
+ # back, it has a name, it's connected to the table.
2024
+ expected_indexes = self.exp_indexes()[(None, tname)]
2025
+ self._check_list(indexes, expected_indexes, self._required_index_keys)
2026
+
2027
+ t = Table(tname, MetaData(), autoload_with=connection)
2028
+ eq_(len(t.indexes), 1)
2029
+ is_(list(t.indexes)[0].table, t)
2030
+ eq_(list(t.indexes)[0].name, ixname)
2031
+
2032
+ @testing.combinations(
2033
+ "IX_test_data",
2034
+ "ix_test_data",
2035
+ "mixedCaseIndex",
2036
+ "ix.with.dots",
2037
+ argnames="idx_name",
2038
+ )
2039
+ @testing.requires.index_reflection
2040
+ def test_get_indexes_quoted_name(self, connection, metadata, idx_name):
2041
+ """Test that index names with various casing are properly reflected."""
2042
+
2043
+ t = Table(
2044
+ "test_table",
2045
+ metadata,
2046
+ Column("id", Integer, primary_key=True),
2047
+ Column("data", String(50)),
2048
+ )
2049
+ Index(idx_name, t.c.data)
2050
+
2051
+ metadata.create_all(connection)
2052
+
2053
+ insp = inspect(connection)
2054
+ indexes = insp.get_indexes("test_table")
2055
+
2056
+ index_names = [idx["name"] for idx in indexes]
2057
+ assert idx_name in index_names, f"Expected {idx_name} in {index_names}"
2058
+
2059
+ # Find the specific index
2060
+ matching_idx = [idx for idx in indexes if idx["name"] == idx_name]
2061
+ eq_(len(matching_idx), 1)
2062
+ eq_(matching_idx[0]["column_names"], ["data"])
2063
+
2064
+ @testing.requires.temp_table_reflection
2065
+ @testing.requires.unique_constraint_reflection
2066
+ def test_get_temp_table_unique_constraints(self, connection):
2067
+ insp = inspect(connection)
2068
+ name = self.temp_table_name()
2069
+ reflected = insp.get_unique_constraints(name)
2070
+ exp = self.exp_ucs(all_=True)[(None, name)]
2071
+ self._check_list(reflected, exp, self._required_index_keys)
2072
+
2073
+ @testing.requires.temp_table_reflect_indexes
2074
+ def test_get_temp_table_indexes(self, connection):
2075
+ insp = inspect(connection)
2076
+ table_name = self.temp_table_name()
2077
+ indexes = insp.get_indexes(table_name)
2078
+ for ind in indexes:
2079
+ ind.pop("dialect_options", None)
2080
+ expected = [
2081
+ {"unique": False, "column_names": ["foo"], "name": "user_tmp_ix"}
2082
+ ]
2083
+ if testing.requires.index_reflects_included_columns.enabled:
2084
+ expected[0]["include_columns"] = []
2085
+ eq_(
2086
+ [idx for idx in indexes if idx["name"] == "user_tmp_ix"],
2087
+ expected,
2088
+ )
2089
+
2090
+ @testing.combinations(
2091
+ (True, testing.requires.schemas), (False,), argnames="use_schema"
2092
+ )
2093
+ @testing.requires.unique_constraint_reflection
2094
+ def test_get_unique_constraints(self, metadata, connection, use_schema):
2095
+ # SQLite dialect needs to parse the names of the constraints
2096
+ # separately from what it gets from PRAGMA index_list(), and
2097
+ # then matches them up. so same set of column_names in two
2098
+ # constraints will confuse it. Perhaps we should no longer
2099
+ # bother with index_list() here since we have the whole
2100
+ # CREATE TABLE?
2101
+
2102
+ if use_schema:
2103
+ schema = config.test_schema
2104
+ else:
2105
+ schema = None
2106
+ uniques = sorted(
2107
+ [
2108
+ {"name": "unique_a", "column_names": ["a"]},
2109
+ {"name": "unique_a_b_c", "column_names": ["a", "b", "c"]},
2110
+ {"name": "unique_c_a_b", "column_names": ["c", "a", "b"]},
2111
+ {"name": "unique_asc_key", "column_names": ["asc", "key"]},
2112
+ {"name": "i.have.dots", "column_names": ["b"]},
2113
+ {"name": "i have spaces", "column_names": ["c"]},
2114
+ ],
2115
+ key=operator.itemgetter("name"),
2116
+ )
2117
+ table = Table(
2118
+ "testtbl",
2119
+ metadata,
2120
+ Column("a", sa.String(20)),
2121
+ Column("b", sa.String(30)),
2122
+ Column("c", sa.Integer),
2123
+ # reserved identifiers
2124
+ Column("asc", sa.String(30)),
2125
+ Column("key", sa.String(30)),
2126
+ schema=schema,
2127
+ )
2128
+ for uc in uniques:
2129
+ table.append_constraint(
2130
+ sa.UniqueConstraint(*uc["column_names"], name=uc["name"])
2131
+ )
2132
+ table.create(connection)
2133
+
2134
+ insp = inspect(connection)
2135
+ reflected = sorted(
2136
+ insp.get_unique_constraints("testtbl", schema=schema),
2137
+ key=operator.itemgetter("name"),
2138
+ )
2139
+
2140
+ names_that_duplicate_index = set()
2141
+
2142
+ eq_(len(uniques), len(reflected))
2143
+
2144
+ for orig, refl in zip(uniques, reflected):
2145
+ # Different dialects handle duplicate index and constraints
2146
+ # differently, so ignore this flag
2147
+ dupe = refl.pop("duplicates_index", None)
2148
+ if dupe:
2149
+ names_that_duplicate_index.add(dupe)
2150
+ eq_(refl.pop("comment", None), None)
2151
+ # ignore dialect_options
2152
+ refl.pop("dialect_options", None)
2153
+ eq_(orig, refl)
2154
+
2155
+ reflected_metadata = MetaData()
2156
+ reflected = Table(
2157
+ "testtbl",
2158
+ reflected_metadata,
2159
+ autoload_with=connection,
2160
+ schema=schema,
2161
+ )
2162
+
2163
+ # test "deduplicates for index" logic. MySQL and Oracle
2164
+ # "unique constraints" are actually unique indexes (with possible
2165
+ # exception of a unique that is a dupe of another one in the case
2166
+ # of Oracle). make sure # they aren't duplicated.
2167
+ idx_names = {idx.name for idx in reflected.indexes}
2168
+ uq_names = {
2169
+ uq.name
2170
+ for uq in reflected.constraints
2171
+ if isinstance(uq, sa.UniqueConstraint)
2172
+ }.difference(["unique_c_a_b"])
2173
+
2174
+ assert not idx_names.intersection(uq_names)
2175
+ if names_that_duplicate_index:
2176
+ eq_(names_that_duplicate_index, idx_names)
2177
+ eq_(uq_names, set())
2178
+
2179
+ no_cst = self.tables.no_constraints.name
2180
+ eq_(insp.get_unique_constraints(no_cst, schema=schema), [])
2181
+
2182
+ @testing.combinations(
2183
+ "UQ_email",
2184
+ "uq_email",
2185
+ "mixedCaseUQ",
2186
+ "uq.with.dots",
2187
+ argnames="uq_name",
2188
+ )
2189
+ @testing.requires.unique_constraint_reflection
2190
+ def test_get_unique_constraints_quoted_name(
2191
+ self, connection, metadata, uq_name
2192
+ ):
2193
+ """Test that unique constraint names with various casing are
2194
+ properly reflected."""
2195
+
2196
+ Table(
2197
+ "test_table",
2198
+ metadata,
2199
+ Column("id", Integer, primary_key=True),
2200
+ Column("email", String(50)),
2201
+ sa.UniqueConstraint("email", name=uq_name),
2202
+ )
2203
+
2204
+ metadata.create_all(connection)
2205
+
2206
+ insp = inspect(connection)
2207
+ uq_cons = insp.get_unique_constraints("test_table")
2208
+
2209
+ eq_(len(uq_cons), 1)
2210
+ eq_(uq_cons[0]["name"], uq_name)
2211
+ eq_(uq_cons[0]["column_names"], ["email"])
2212
+
2213
+ @testing.requires.view_reflection
2214
+ @testing.combinations(
2215
+ (False,), (True, testing.requires.schemas), argnames="use_schema"
2216
+ )
2217
+ def test_get_view_definition(self, connection, use_schema):
2218
+ if use_schema:
2219
+ schema = config.test_schema
2220
+ else:
2221
+ schema = None
2222
+ insp = inspect(connection)
2223
+ for view in ["users_v", "email_addresses_v", "dingalings_v"]:
2224
+ v = insp.get_view_definition(view, schema=schema)
2225
+ is_true(bool(v))
2226
+
2227
+ @testing.requires.view_reflection
2228
+ def test_get_view_definition_does_not_exist(self, connection):
2229
+ insp = inspect(connection)
2230
+ with expect_raises(NoSuchTableError):
2231
+ insp.get_view_definition("view_does_not_exist")
2232
+ with expect_raises(NoSuchTableError):
2233
+ insp.get_view_definition("users") # a table
2234
+
2235
+ @testing.requires.table_reflection
2236
+ def test_autoincrement_col(self, connection):
2237
+ """test that 'autoincrement' is reflected according to sqla's policy.
2238
+
2239
+ Don't mark this test as unsupported for any backend !
2240
+
2241
+ (technically it fails with MySQL InnoDB since "id" comes before "id2")
2242
+
2243
+ A backend is better off not returning "autoincrement" at all,
2244
+ instead of potentially returning "False" for an auto-incrementing
2245
+ primary key column.
2246
+
2247
+ """
2248
+
2249
+ insp = inspect(connection)
2250
+
2251
+ for tname, cname in [
2252
+ ("users", "user_id"),
2253
+ ("email_addresses", "address_id"),
2254
+ ("dingalings", "dingaling_id"),
2255
+ ]:
2256
+ cols = insp.get_columns(tname)
2257
+ id_ = {c["name"]: c for c in cols}[cname]
2258
+ assert id_.get("autoincrement", True)
2259
+
2260
+ @testing.combinations(
2261
+ (True, testing.requires.schemas), (False,), argnames="use_schema"
2262
+ )
2263
+ def test_get_table_options(self, use_schema):
2264
+ insp = inspect(config.db)
2265
+ schema = config.test_schema if use_schema else None
2266
+
2267
+ if testing.requires.reflect_table_options.enabled:
2268
+ res = insp.get_table_options("users", schema=schema)
2269
+ is_true(isinstance(res, dict))
2270
+ # NOTE: can't really create a table with no option
2271
+ res = insp.get_table_options("no_constraints", schema=schema)
2272
+ is_true(isinstance(res, dict))
2273
+ else:
2274
+ with expect_raises(NotImplementedError):
2275
+ insp.get_table_options("users", schema=schema)
2276
+
2277
+ @testing.combinations((True, testing.requires.schemas), False)
2278
+ def test_multi_get_table_options(self, use_schema):
2279
+ insp = inspect(config.db)
2280
+ if testing.requires.reflect_table_options.enabled:
2281
+ schema = config.test_schema if use_schema else None
2282
+ res = insp.get_multi_table_options(schema=schema)
2283
+
2284
+ exp = {
2285
+ (schema, table): insp.get_table_options(table, schema=schema)
2286
+ for table in insp.get_table_names(schema=schema)
2287
+ }
2288
+ eq_(res, exp)
2289
+ else:
2290
+ with expect_raises(NotImplementedError):
2291
+ insp.get_multi_table_options()
2292
+
2293
+ @testing.fixture
2294
+ def get_multi_exp(self, connection):
2295
+ def provide_fixture(
2296
+ schema, scope, kind, use_filter, single_reflect_fn, exp_method
2297
+ ):
2298
+ insp = inspect(connection)
2299
+ # call the reflection function at least once to avoid
2300
+ # "Unexpected success" errors if the result is actually empty
2301
+ # and NotImplementedError is not raised
2302
+ single_reflect_fn(insp, "email_addresses")
2303
+ kw = {"scope": scope, "kind": kind}
2304
+ if schema:
2305
+ schema = schema()
2306
+
2307
+ filter_names = []
2308
+
2309
+ if ObjectKind.TABLE in kind:
2310
+ filter_names.extend(
2311
+ ["comment_test", "users", "does-not-exist"]
2312
+ )
2313
+ if ObjectKind.VIEW in kind:
2314
+ filter_names.extend(["email_addresses_v", "does-not-exist"])
2315
+ if ObjectKind.MATERIALIZED_VIEW in kind:
2316
+ filter_names.extend(["dingalings_v", "does-not-exist"])
2317
+
2318
+ if schema:
2319
+ kw["schema"] = schema
2320
+ if use_filter:
2321
+ kw["filter_names"] = filter_names
2322
+
2323
+ exp = exp_method(
2324
+ schema=schema,
2325
+ scope=scope,
2326
+ kind=kind,
2327
+ filter_names=kw.get("filter_names"),
2328
+ )
2329
+ kws = [kw]
2330
+ if scope == ObjectScope.DEFAULT:
2331
+ nkw = kw.copy()
2332
+ nkw.pop("scope")
2333
+ kws.append(nkw)
2334
+ if kind == ObjectKind.TABLE:
2335
+ nkw = kw.copy()
2336
+ nkw.pop("kind")
2337
+ kws.append(nkw)
2338
+
2339
+ return inspect(connection), kws, exp
2340
+
2341
+ return provide_fixture
2342
+
2343
+ @testing.requires.reflect_table_options
2344
+ @_multi_combination
2345
+ def test_multi_get_table_options_tables(
2346
+ self, get_multi_exp, schema, scope, kind, use_filter
2347
+ ):
2348
+ insp, kws, exp = get_multi_exp(
2349
+ schema,
2350
+ scope,
2351
+ kind,
2352
+ use_filter,
2353
+ Inspector.get_table_options,
2354
+ self.exp_options,
2355
+ )
2356
+ for kw in kws:
2357
+ insp.clear_cache()
2358
+ result = insp.get_multi_table_options(**kw)
2359
+ eq_(result, exp)
2360
+
2361
+ @testing.requires.comment_reflection
2362
+ @_multi_combination
2363
+ def test_get_multi_table_comment(
2364
+ self, get_multi_exp, schema, scope, kind, use_filter
2365
+ ):
2366
+ insp, kws, exp = get_multi_exp(
2367
+ schema,
2368
+ scope,
2369
+ kind,
2370
+ use_filter,
2371
+ Inspector.get_table_comment,
2372
+ self.exp_comments,
2373
+ )
2374
+ for kw in kws:
2375
+ insp.clear_cache()
2376
+ eq_(insp.get_multi_table_comment(**kw), exp)
2377
+
2378
+ def _check_expressions(self, result, exp, err_msg):
2379
+ def _clean(text: str):
2380
+ return re.sub(r"['\" ]", "", text).lower()
2381
+
2382
+ if isinstance(exp, dict):
2383
+ eq_({_clean(e): v for e, v in result.items()}, exp, err_msg)
2384
+ else:
2385
+ eq_([_clean(e) for e in result], exp, err_msg)
2386
+
2387
+ def _check_list(self, result, exp, req_keys=None, msg=None):
2388
+ if req_keys is None:
2389
+ eq_(result, exp, msg)
2390
+ else:
2391
+ eq_(len(result), len(exp), msg)
2392
+ for r, e in zip(result, exp):
2393
+ for k in set(r) | set(e):
2394
+ if k in req_keys or (k in r and k in e):
2395
+ err_msg = f"{msg} - {k} - {r}"
2396
+ if k in ("expressions", "column_sorting"):
2397
+ self._check_expressions(r[k], e[k], err_msg)
2398
+ else:
2399
+ eq_(r[k], e[k], err_msg)
2400
+
2401
+ def _check_table_dict(self, result, exp, req_keys=None, make_lists=False):
2402
+ eq_(set(result.keys()), set(exp.keys()))
2403
+ for k in result:
2404
+ r, e = result[k], exp[k]
2405
+ if make_lists:
2406
+ r, e = [r], [e]
2407
+ self._check_list(r, e, req_keys, k)
2408
+
2409
+ @_multi_combination
2410
+ def test_get_multi_columns(
2411
+ self, get_multi_exp, schema, scope, kind, use_filter
2412
+ ):
2413
+ insp, kws, exp = get_multi_exp(
2414
+ schema,
2415
+ scope,
2416
+ kind,
2417
+ use_filter,
2418
+ Inspector.get_columns,
2419
+ self.exp_columns,
2420
+ )
2421
+
2422
+ for kw in kws:
2423
+ insp.clear_cache()
2424
+ result = insp.get_multi_columns(**kw)
2425
+ self._check_table_dict(result, exp, self._required_column_keys)
2426
+
2427
+ @testing.requires.primary_key_constraint_reflection
2428
+ @_multi_combination
2429
+ def test_get_multi_pk_constraint(
2430
+ self, get_multi_exp, schema, scope, kind, use_filter
2431
+ ):
2432
+ insp, kws, exp = get_multi_exp(
2433
+ schema,
2434
+ scope,
2435
+ kind,
2436
+ use_filter,
2437
+ Inspector.get_pk_constraint,
2438
+ self.exp_pks,
2439
+ )
2440
+ for kw in kws:
2441
+ insp.clear_cache()
2442
+ result = insp.get_multi_pk_constraint(**kw)
2443
+ self._check_table_dict(
2444
+ result, exp, self._required_pk_keys, make_lists=True
2445
+ )
2446
+
2447
+ def _adjust_sort(self, result, expected, key):
2448
+ if not testing.requires.implicitly_named_constraints.enabled:
2449
+ for obj in [result, expected]:
2450
+ for val in obj.values():
2451
+ if len(val) > 1 and any(
2452
+ v.get("name") in (None, mock.ANY) for v in val
2453
+ ):
2454
+ val.sort(key=key)
2455
+
2456
+ @testing.requires.foreign_key_constraint_reflection
2457
+ @_multi_combination
2458
+ def test_get_multi_foreign_keys(
2459
+ self, get_multi_exp, schema, scope, kind, use_filter
2460
+ ):
2461
+ insp, kws, exp = get_multi_exp(
2462
+ schema,
2463
+ scope,
2464
+ kind,
2465
+ use_filter,
2466
+ Inspector.get_foreign_keys,
2467
+ self.exp_fks,
2468
+ )
2469
+ for kw in kws:
2470
+ insp.clear_cache()
2471
+ result = insp.get_multi_foreign_keys(**kw)
2472
+ self._adjust_sort(
2473
+ result, exp, lambda d: tuple(d["constrained_columns"])
2474
+ )
2475
+ self._check_table_dict(result, exp, self._required_fk_keys)
2476
+
2477
+ @testing.requires.index_reflection
2478
+ @_multi_combination
2479
+ def test_get_multi_indexes(
2480
+ self, get_multi_exp, schema, scope, kind, use_filter
2481
+ ):
2482
+ insp, kws, exp = get_multi_exp(
2483
+ schema,
2484
+ scope,
2485
+ kind,
2486
+ use_filter,
2487
+ Inspector.get_indexes,
2488
+ self.exp_indexes,
2489
+ )
2490
+ for kw in kws:
2491
+ insp.clear_cache()
2492
+ result = insp.get_multi_indexes(**kw)
2493
+ self._check_table_dict(result, exp, self._required_index_keys)
2494
+
2495
+ @testing.requires.unique_constraint_reflection
2496
+ @_multi_combination
2497
+ def test_get_multi_unique_constraints(
2498
+ self, get_multi_exp, schema, scope, kind, use_filter
2499
+ ):
2500
+ insp, kws, exp = get_multi_exp(
2501
+ schema,
2502
+ scope,
2503
+ kind,
2504
+ use_filter,
2505
+ Inspector.get_unique_constraints,
2506
+ self.exp_ucs,
2507
+ )
2508
+ for kw in kws:
2509
+ insp.clear_cache()
2510
+ result = insp.get_multi_unique_constraints(**kw)
2511
+ self._adjust_sort(result, exp, lambda d: tuple(d["column_names"]))
2512
+ self._check_table_dict(result, exp, self._required_unique_cst_keys)
2513
+
2514
+ @testing.requires.check_constraint_reflection
2515
+ @_multi_combination
2516
+ def test_get_multi_check_constraints(
2517
+ self, get_multi_exp, schema, scope, kind, use_filter
2518
+ ):
2519
+ insp, kws, exp = get_multi_exp(
2520
+ schema,
2521
+ scope,
2522
+ kind,
2523
+ use_filter,
2524
+ Inspector.get_check_constraints,
2525
+ self.exp_ccs,
2526
+ )
2527
+ for kw in kws:
2528
+ insp.clear_cache()
2529
+ result = insp.get_multi_check_constraints(**kw)
2530
+ self._adjust_sort(result, exp, lambda d: tuple(d["sqltext"]))
2531
+ self._check_table_dict(result, exp, self._required_cc_keys)
2532
+
2533
+ @testing.combinations(
2534
+ ("get_table_options", testing.requires.reflect_table_options),
2535
+ "get_columns",
2536
+ (
2537
+ "get_pk_constraint",
2538
+ testing.requires.primary_key_constraint_reflection,
2539
+ ),
2540
+ (
2541
+ "get_foreign_keys",
2542
+ testing.requires.foreign_key_constraint_reflection,
2543
+ ),
2544
+ ("get_indexes", testing.requires.index_reflection),
2545
+ (
2546
+ "get_unique_constraints",
2547
+ testing.requires.unique_constraint_reflection,
2548
+ ),
2549
+ (
2550
+ "get_check_constraints",
2551
+ testing.requires.check_constraint_reflection,
2552
+ ),
2553
+ ("get_table_comment", testing.requires.comment_reflection),
2554
+ argnames="method",
2555
+ )
2556
+ def test_not_existing_table(self, method, connection):
2557
+ insp = inspect(connection)
2558
+ meth = getattr(insp, method)
2559
+ with expect_raises(NoSuchTableError):
2560
+ meth("table_does_not_exists")
2561
+
2562
+ def test_unreflectable(self, connection):
2563
+ mc = Inspector.get_multi_columns
2564
+
2565
+ def patched(*a, **k):
2566
+ ur = k.setdefault("unreflectable", {})
2567
+ ur[(None, "some_table")] = UnreflectableTableError("err")
2568
+ return mc(*a, **k)
2569
+
2570
+ with mock.patch.object(Inspector, "get_multi_columns", patched):
2571
+ with expect_raises_message(UnreflectableTableError, "err"):
2572
+ inspect(connection).reflect_table(
2573
+ Table("some_table", MetaData()), None
2574
+ )
2575
+
2576
+ @testing.combinations(True, False, argnames="use_schema")
2577
+ @testing.combinations(
2578
+ (True, testing.requires.views), False, argnames="views"
2579
+ )
2580
+ def test_metadata(self, connection, use_schema, views):
2581
+ m = MetaData()
2582
+ schema = config.test_schema if use_schema else None
2583
+ m.reflect(connection, schema=schema, views=views, resolve_fks=False)
2584
+
2585
+ insp = inspect(connection)
2586
+ tables = insp.get_table_names(schema)
2587
+ if views:
2588
+ tables += insp.get_view_names(schema)
2589
+ try:
2590
+ tables += insp.get_materialized_view_names(schema)
2591
+ except NotImplementedError:
2592
+ pass
2593
+ if schema:
2594
+ tables = [f"{schema}.{t}" for t in tables]
2595
+ eq_(sorted(m.tables), sorted(tables))
2596
+
2597
+ @testing.requires.comment_reflection
2598
+ def test_comments_unicode(self, connection, metadata):
2599
+ Table(
2600
+ "unicode_comments",
2601
+ metadata,
2602
+ Column("unicode", Integer, comment="é試蛇ẟΩ"),
2603
+ Column("emoji", Integer, comment="☁️✨"),
2604
+ comment="試蛇ẟΩ✨",
2605
+ )
2606
+
2607
+ metadata.create_all(connection)
2608
+
2609
+ insp = inspect(connection)
2610
+ tc = insp.get_table_comment("unicode_comments")
2611
+ eq_(tc, {"text": "試蛇ẟΩ✨"})
2612
+
2613
+ cols = insp.get_columns("unicode_comments")
2614
+ value = {c["name"]: c["comment"] for c in cols}
2615
+ exp = {"unicode": "é試蛇ẟΩ", "emoji": "☁️✨"}
2616
+ eq_(value, exp)
2617
+
2618
+ @testing.requires.comment_reflection_full_unicode
2619
+ def test_comments_unicode_full(self, connection, metadata):
2620
+ Table(
2621
+ "unicode_comments",
2622
+ metadata,
2623
+ Column("emoji", Integer, comment="🐍🧙🝝🧙‍♂️🧙‍♀️"),
2624
+ comment="🎩🁰🝑🤷‍♀️🤷‍♂️",
2625
+ )
2626
+
2627
+ metadata.create_all(connection)
2628
+
2629
+ insp = inspect(connection)
2630
+ tc = insp.get_table_comment("unicode_comments")
2631
+ eq_(tc, {"text": "🎩🁰🝑🤷‍♀️🤷‍♂️"})
2632
+ c = insp.get_columns("unicode_comments")[0]
2633
+ eq_({c["name"]: c["comment"]}, {"emoji": "🐍🧙🝝🧙‍♂️🧙‍♀️"})
2634
+
2635
+ @testing.requires.column_collation_reflection
2636
+ @testing.requires.order_by_collation
2637
+ def test_column_collation_reflection(self, connection, metadata):
2638
+ collation = testing.requires.get_order_by_collation(config)
2639
+ Table(
2640
+ "t",
2641
+ metadata,
2642
+ Column("collated", sa.String(collation=collation)),
2643
+ Column("not_collated", sa.String()),
2644
+ )
2645
+ metadata.create_all(connection)
2646
+
2647
+ m2 = MetaData()
2648
+ t2 = Table("t", m2, autoload_with=connection)
2649
+
2650
+ eq_(t2.c.collated.type.collation, collation)
2651
+ is_none(t2.c.not_collated.type.collation)
2652
+
2653
+ insp = inspect(connection)
2654
+ collated, not_collated = insp.get_columns("t")
2655
+ eq_(collated["type"].collation, collation)
2656
+ is_none(not_collated["type"].collation)
2657
+
2658
+
2659
+ class TableNoColumnsTest(fixtures.TestBase):
2660
+ __requires__ = ("reflect_tables_no_columns",)
2661
+ __sparse_driver_backend__ = True
2662
+
2663
+ @testing.fixture
2664
+ def table_no_columns(self, connection, metadata):
2665
+ Table("empty", metadata)
2666
+ metadata.create_all(connection)
2667
+
2668
+ @testing.fixture
2669
+ def view_no_columns(self, connection, metadata):
2670
+ Table("empty", metadata)
2671
+ event.listen(
2672
+ metadata,
2673
+ "after_create",
2674
+ DDL("CREATE VIEW empty_v AS SELECT * FROM empty"),
2675
+ )
2676
+
2677
+ # for transactional DDL the transaction is rolled back before this
2678
+ # drop statement is invoked
2679
+ event.listen(
2680
+ metadata, "before_drop", DDL("DROP VIEW IF EXISTS empty_v")
2681
+ )
2682
+ metadata.create_all(connection)
2683
+
2684
+ def test_reflect_table_no_columns(self, connection, table_no_columns):
2685
+ t2 = Table("empty", MetaData(), autoload_with=connection)
2686
+ eq_(list(t2.c), [])
2687
+
2688
+ def test_get_columns_table_no_columns(self, connection, table_no_columns):
2689
+ insp = inspect(connection)
2690
+ eq_(insp.get_columns("empty"), [])
2691
+ multi = insp.get_multi_columns()
2692
+ eq_(multi, {(None, "empty"): []})
2693
+
2694
+ def test_reflect_incl_table_no_columns(self, connection, table_no_columns):
2695
+ m = MetaData()
2696
+ m.reflect(connection)
2697
+ assert set(m.tables).intersection(["empty"])
2698
+
2699
+ @testing.requires.views
2700
+ def test_reflect_view_no_columns(self, connection, view_no_columns):
2701
+ t2 = Table("empty_v", MetaData(), autoload_with=connection)
2702
+ eq_(list(t2.c), [])
2703
+
2704
+ @testing.requires.views
2705
+ def test_get_columns_view_no_columns(self, connection, view_no_columns):
2706
+ insp = inspect(connection)
2707
+ eq_(insp.get_columns("empty_v"), [])
2708
+ multi = insp.get_multi_columns(kind=ObjectKind.VIEW)
2709
+ eq_(multi, {(None, "empty_v"): []})
2710
+
2711
+
2712
+ class ComponentReflectionTestExtra(ComparesIndexes, fixtures.TestBase):
2713
+ __sparse_driver_backend__ = True
2714
+
2715
+ @testing.fixture(params=[True, False])
2716
+ def use_schema_fixture(self, request):
2717
+ if request.param:
2718
+ return config.test_schema
2719
+ else:
2720
+ return None
2721
+
2722
+ @testing.fixture()
2723
+ def inspect_for_table(self, metadata, connection, use_schema_fixture):
2724
+ @contextlib.contextmanager
2725
+ def go(tablename):
2726
+ yield use_schema_fixture, inspect(connection)
2727
+
2728
+ metadata.create_all(connection)
2729
+
2730
+ return go
2731
+
2732
+ def ck_eq(self, reflected, expected):
2733
+ # trying to minimize effect of quoting, parenthesis, etc.
2734
+ # may need to add more to this as new dialects get CHECK
2735
+ # constraint reflection support
2736
+ def normalize(sqltext):
2737
+ return " ".join(
2738
+ re.findall(r"and|\d|=|a|b|c|or|<|>", sqltext.lower(), re.I)
2739
+ )
2740
+
2741
+ reflected = sorted(
2742
+ [
2743
+ {"name": item["name"], "sqltext": normalize(item["sqltext"])}
2744
+ for item in reflected
2745
+ ],
2746
+ key=lambda item: (item["sqltext"]),
2747
+ )
2748
+
2749
+ expected = sorted(
2750
+ expected,
2751
+ key=lambda item: (item["sqltext"]),
2752
+ )
2753
+ eq_(reflected, expected)
2754
+
2755
+ @testing.requires.check_constraint_reflection
2756
+ def test_check_constraint_no_constraint(self, metadata, inspect_for_table):
2757
+ with inspect_for_table("no_constraints") as (schema, inspector):
2758
+ Table(
2759
+ "no_constraints",
2760
+ metadata,
2761
+ Column("data", sa.String(20)),
2762
+ schema=schema,
2763
+ )
2764
+
2765
+ self.ck_eq(
2766
+ inspector.get_check_constraints("no_constraints", schema=schema),
2767
+ [],
2768
+ )
2769
+
2770
+ @testing.requires.inline_check_constraint_reflection
2771
+ @testing.combinations(
2772
+ "my_inline", "MyInline", None, argnames="constraint_name"
2773
+ )
2774
+ def test_check_constraint_inline(
2775
+ self, metadata, inspect_for_table, constraint_name
2776
+ ):
2777
+
2778
+ with inspect_for_table("sa_cc") as (schema, inspector):
2779
+ Table(
2780
+ "sa_cc",
2781
+ metadata,
2782
+ Column("id", Integer(), primary_key=True),
2783
+ Column(
2784
+ "a",
2785
+ Integer(),
2786
+ sa.CheckConstraint(
2787
+ "a > 1 AND a < 5", name=constraint_name
2788
+ ),
2789
+ ),
2790
+ Column("data", String(50)),
2791
+ schema=schema,
2792
+ )
2793
+
2794
+ reflected = inspector.get_check_constraints("sa_cc", schema=schema)
2795
+
2796
+ self.ck_eq(
2797
+ reflected,
2798
+ [
2799
+ {
2800
+ "name": constraint_name or mock.ANY,
2801
+ "sqltext": "a > 1 and a < 5",
2802
+ },
2803
+ ],
2804
+ )
2805
+
2806
+ @testing.requires.check_constraint_reflection
2807
+ @testing.combinations(
2808
+ "my_ck_const", "MyCkConst", None, argnames="constraint_name"
2809
+ )
2810
+ def test_check_constraint_standalone(
2811
+ self, metadata, inspect_for_table, constraint_name
2812
+ ):
2813
+ with inspect_for_table("sa_cc") as (schema, inspector):
2814
+ Table(
2815
+ "sa_cc",
2816
+ metadata,
2817
+ Column("a", Integer()),
2818
+ sa.CheckConstraint(
2819
+ "a = 1 OR (a > 2 AND a < 5)", name=constraint_name
2820
+ ),
2821
+ schema=schema,
2822
+ )
2823
+
2824
+ reflected = inspector.get_check_constraints("sa_cc", schema=schema)
2825
+
2826
+ self.ck_eq(
2827
+ reflected,
2828
+ [
2829
+ {
2830
+ "name": constraint_name or mock.ANY,
2831
+ "sqltext": "a = 1 or a > 2 and a < 5",
2832
+ },
2833
+ ],
2834
+ )
2835
+
2836
+ @testing.requires.inline_check_constraint_reflection
2837
+ def test_check_constraint_mixed(self, metadata, inspect_for_table):
2838
+ with inspect_for_table("sa_cc") as (schema, inspector):
2839
+ Table(
2840
+ "sa_cc",
2841
+ metadata,
2842
+ Column("id", Integer(), primary_key=True),
2843
+ Column("a", Integer(), sa.CheckConstraint("a > 1 AND a < 5")),
2844
+ Column(
2845
+ "b",
2846
+ Integer(),
2847
+ sa.CheckConstraint("b > 1 AND b < 5", name="my_inline"),
2848
+ ),
2849
+ Column("c", Integer()),
2850
+ Column("data", String(50)),
2851
+ sa.UniqueConstraint("data", name="some_uq"),
2852
+ sa.CheckConstraint("c > 1 AND c < 5", name="cc1"),
2853
+ sa.UniqueConstraint("c", name="some_c_uq"),
2854
+ schema=schema,
2855
+ )
2856
+
2857
+ reflected = inspector.get_check_constraints("sa_cc", schema=schema)
2858
+
2859
+ self.ck_eq(
2860
+ reflected,
2861
+ [
2862
+ {"name": "cc1", "sqltext": "c > 1 and c < 5"},
2863
+ {"name": "my_inline", "sqltext": "b > 1 and b < 5"},
2864
+ {"name": mock.ANY, "sqltext": "a > 1 and a < 5"},
2865
+ ],
2866
+ )
2867
+
2868
+ @testing.requires.indexes_check_column_order
2869
+ def test_index_column_order(self, metadata, inspect_for_table):
2870
+ """test for #12894"""
2871
+ with inspect_for_table("sa_multi_index") as (schema, inspector):
2872
+ test_table = Table(
2873
+ "sa_multi_index",
2874
+ metadata,
2875
+ Column("Column1", Integer, primary_key=True),
2876
+ Column("Column2", Integer),
2877
+ Column("Column3", Integer),
2878
+ )
2879
+ Index(
2880
+ "Index_Example",
2881
+ test_table.c.Column3,
2882
+ test_table.c.Column1,
2883
+ test_table.c.Column2,
2884
+ )
2885
+ indexes = inspector.get_indexes("sa_multi_index")
2886
+ eq_(indexes[0]["column_names"], ["Column3", "Column1", "Column2"])
2887
+
2888
+ @testing.requires.indexes_with_expressions
2889
+ def test_reflect_expression_based_indexes(self, metadata, connection):
2890
+ t = Table(
2891
+ "t",
2892
+ metadata,
2893
+ Column("x", String(30)),
2894
+ Column("y", String(30)),
2895
+ Column("z", String(30)),
2896
+ )
2897
+
2898
+ Index("t_idx", func.lower(t.c.x), t.c.z, func.lower(t.c.y))
2899
+ long_str = "long string " * 100
2900
+ Index("t_idx_long", func.coalesce(t.c.x, long_str))
2901
+ Index("t_idx_2", t.c.x)
2902
+
2903
+ metadata.create_all(connection)
2904
+
2905
+ insp = inspect(connection)
2906
+
2907
+ expected = [
2908
+ {
2909
+ "name": "t_idx_2",
2910
+ "column_names": ["x"],
2911
+ "unique": False,
2912
+ "dialect_options": {},
2913
+ }
2914
+ ]
2915
+
2916
+ def completeIndex(entry):
2917
+ if testing.requires.index_reflects_included_columns.enabled:
2918
+ entry["include_columns"] = []
2919
+ entry["dialect_options"] = {
2920
+ f"{connection.engine.name}_include": []
2921
+ }
2922
+ else:
2923
+ entry.setdefault("dialect_options", {})
2924
+
2925
+ completeIndex(expected[0])
2926
+
2927
+ class lower_index_str(str):
2928
+ def __eq__(self, other):
2929
+ ol = other.lower()
2930
+ # test that lower and x or y are in the string
2931
+ return "lower" in ol and ("x" in ol or "y" in ol)
2932
+
2933
+ class coalesce_index_str(str):
2934
+ def __eq__(self, other):
2935
+ # test that coalesce and the string is in other
2936
+ return "coalesce" in other.lower() and long_str in other
2937
+
2938
+ if testing.requires.reflect_indexes_with_expressions.enabled:
2939
+ expr_index = {
2940
+ "name": "t_idx",
2941
+ "column_names": [None, "z", None],
2942
+ "expressions": [
2943
+ lower_index_str("lower(x)"),
2944
+ "z",
2945
+ lower_index_str("lower(y)"),
2946
+ ],
2947
+ "unique": False,
2948
+ }
2949
+ completeIndex(expr_index)
2950
+ expected.insert(0, expr_index)
2951
+
2952
+ expr_index_long = {
2953
+ "name": "t_idx_long",
2954
+ "column_names": [None],
2955
+ "expressions": [
2956
+ coalesce_index_str(f"coalesce(x, '{long_str}')")
2957
+ ],
2958
+ "unique": False,
2959
+ }
2960
+ completeIndex(expr_index_long)
2961
+ expected.append(expr_index_long)
2962
+
2963
+ eq_(insp.get_indexes("t"), expected)
2964
+ m2 = MetaData()
2965
+ t2 = Table("t", m2, autoload_with=connection)
2966
+ else:
2967
+ with expect_warnings(
2968
+ "Skipped unsupported reflection of expression-based "
2969
+ "index t_idx"
2970
+ ):
2971
+ eq_(insp.get_indexes("t"), expected)
2972
+ m2 = MetaData()
2973
+ t2 = Table("t", m2, autoload_with=connection)
2974
+
2975
+ self.compare_table_index_with_expected(
2976
+ t2, expected, connection.engine.name
2977
+ )
2978
+
2979
+ @testing.requires.index_reflects_included_columns
2980
+ def test_reflect_covering_index(self, metadata, connection):
2981
+ t = Table(
2982
+ "t",
2983
+ metadata,
2984
+ Column("x", String(30)),
2985
+ Column("y", String(30)),
2986
+ )
2987
+ idx = Index("t_idx", t.c.x)
2988
+ idx.dialect_options[connection.engine.name]["include"] = ["y"]
2989
+
2990
+ metadata.create_all(connection)
2991
+
2992
+ insp = inspect(connection)
2993
+
2994
+ get_indexes = insp.get_indexes("t")
2995
+ eq_(
2996
+ get_indexes,
2997
+ [
2998
+ {
2999
+ "name": "t_idx",
3000
+ "column_names": ["x"],
3001
+ "include_columns": ["y"],
3002
+ "unique": False,
3003
+ "dialect_options": mock.ANY,
3004
+ }
3005
+ ],
3006
+ )
3007
+ eq_(
3008
+ get_indexes[0]["dialect_options"][
3009
+ "%s_include" % connection.engine.name
3010
+ ],
3011
+ ["y"],
3012
+ )
3013
+
3014
+ t2 = Table("t", MetaData(), autoload_with=connection)
3015
+ eq_(
3016
+ list(t2.indexes)[0].dialect_options[connection.engine.name][
3017
+ "include"
3018
+ ],
3019
+ ["y"],
3020
+ )
3021
+
3022
+ def _type_round_trip(self, connection, metadata, *types):
3023
+ t = Table(
3024
+ "t",
3025
+ metadata,
3026
+ *[Column("t%d" % i, type_) for i, type_ in enumerate(types)],
3027
+ )
3028
+ t.create(connection)
3029
+
3030
+ return [c["type"] for c in inspect(connection).get_columns("t")]
3031
+
3032
+ @testing.requires.table_reflection
3033
+ def test_numeric_reflection(self, connection, metadata):
3034
+ for typ in self._type_round_trip(
3035
+ connection, metadata, sql_types.Numeric(18, 5)
3036
+ ):
3037
+ assert isinstance(typ, sql_types.Numeric)
3038
+ eq_(typ.precision, 18)
3039
+ eq_(typ.scale, 5)
3040
+
3041
+ @testing.requires.table_reflection
3042
+ @testing.combinations(
3043
+ sql_types.String,
3044
+ sql_types.VARCHAR,
3045
+ sql_types.CHAR,
3046
+ (sql_types.NVARCHAR, testing.requires.nvarchar_types),
3047
+ (sql_types.NCHAR, testing.requires.nvarchar_types),
3048
+ argnames="type_",
3049
+ )
3050
+ def test_string_length_reflection(self, connection, metadata, type_):
3051
+ typ = self._type_round_trip(connection, metadata, type_(52))[0]
3052
+ if issubclass(type_, sql_types.VARCHAR):
3053
+ assert isinstance(typ, sql_types.VARCHAR)
3054
+ elif issubclass(type_, sql_types.CHAR):
3055
+ assert isinstance(typ, sql_types.CHAR)
3056
+ else:
3057
+ assert isinstance(typ, sql_types.String)
3058
+
3059
+ eq_(typ.length, 52)
3060
+ assert isinstance(typ.length, int)
3061
+
3062
+ @testing.requires.table_reflection
3063
+ def test_nullable_reflection(self, connection, metadata):
3064
+ t = Table(
3065
+ "t",
3066
+ metadata,
3067
+ Column("a", Integer, nullable=True),
3068
+ Column("b", Integer, nullable=False),
3069
+ )
3070
+ t.create(connection)
3071
+ eq_(
3072
+ {
3073
+ col["name"]: col["nullable"]
3074
+ for col in inspect(connection).get_columns("t")
3075
+ },
3076
+ {"a": True, "b": False},
3077
+ )
3078
+
3079
+ @testing.combinations(
3080
+ (
3081
+ None,
3082
+ "CASCADE",
3083
+ None,
3084
+ testing.requires.foreign_key_constraint_option_reflection_ondelete,
3085
+ ),
3086
+ (
3087
+ None,
3088
+ None,
3089
+ "SET NULL",
3090
+ testing.requires.foreign_key_constraint_option_reflection_onupdate,
3091
+ ),
3092
+ (
3093
+ {},
3094
+ None,
3095
+ "NO ACTION",
3096
+ testing.requires.foreign_key_constraint_option_reflection_onupdate,
3097
+ ),
3098
+ (
3099
+ {},
3100
+ "NO ACTION",
3101
+ None,
3102
+ testing.requires.fk_constraint_option_reflection_ondelete_noaction,
3103
+ ),
3104
+ (
3105
+ None,
3106
+ None,
3107
+ "RESTRICT",
3108
+ testing.requires.fk_constraint_option_reflection_onupdate_restrict,
3109
+ ),
3110
+ (
3111
+ None,
3112
+ "RESTRICT",
3113
+ None,
3114
+ testing.requires.fk_constraint_option_reflection_ondelete_restrict,
3115
+ ),
3116
+ argnames="expected,ondelete,onupdate",
3117
+ )
3118
+ def test_get_foreign_key_options(
3119
+ self, connection, metadata, expected, ondelete, onupdate
3120
+ ):
3121
+ options = {}
3122
+ if ondelete:
3123
+ options["ondelete"] = ondelete
3124
+ if onupdate:
3125
+ options["onupdate"] = onupdate
3126
+
3127
+ if expected is None:
3128
+ expected = options
3129
+
3130
+ Table(
3131
+ "x",
3132
+ metadata,
3133
+ Column("id", Integer, primary_key=True),
3134
+ test_needs_fk=True,
3135
+ )
3136
+
3137
+ Table(
3138
+ "table",
3139
+ metadata,
3140
+ Column("id", Integer, primary_key=True),
3141
+ Column("x_id", Integer, ForeignKey("x.id", name="xid")),
3142
+ Column("test", String(10)),
3143
+ test_needs_fk=True,
3144
+ )
3145
+
3146
+ Table(
3147
+ "user",
3148
+ metadata,
3149
+ Column("id", Integer, primary_key=True),
3150
+ Column("name", String(50), nullable=False),
3151
+ Column("tid", Integer),
3152
+ sa.ForeignKeyConstraint(
3153
+ ["tid"], ["table.id"], name="myfk", **options
3154
+ ),
3155
+ test_needs_fk=True,
3156
+ )
3157
+
3158
+ metadata.create_all(connection)
3159
+
3160
+ insp = inspect(connection)
3161
+
3162
+ # test 'options' is always present for a backend
3163
+ # that can reflect these, since alembic looks for this
3164
+ opts = insp.get_foreign_keys("table")[0]["options"]
3165
+
3166
+ eq_({k: opts[k] for k in opts if opts[k]}, {})
3167
+
3168
+ opts = insp.get_foreign_keys("user")[0]["options"]
3169
+ eq_(opts, expected)
3170
+ # eq_(dict((k, opts[k]) for k in opts if opts[k]), expected)
3171
+
3172
+ @testing.combinations(
3173
+ (Integer, sa.text("10"), r"'?10'?"),
3174
+ (Integer, "10", r"'?10'?"),
3175
+ (Boolean, sa.true(), r"1|true"),
3176
+ (
3177
+ Integer,
3178
+ sa.text("3 + 5"),
3179
+ r"3\+5",
3180
+ testing.requires.expression_server_defaults,
3181
+ ),
3182
+ (
3183
+ Integer,
3184
+ sa.text("(3 * 5)"),
3185
+ r"3\*5",
3186
+ testing.requires.expression_server_defaults,
3187
+ ),
3188
+ (DateTime, func.now(), r"current_timestamp|now|getdate"),
3189
+ (
3190
+ Integer,
3191
+ sa.literal_column("3") + sa.literal_column("5"),
3192
+ r"3\+5",
3193
+ testing.requires.expression_server_defaults,
3194
+ ),
3195
+ argnames="datatype, default, expected_reg",
3196
+ )
3197
+ @testing.requires.server_defaults
3198
+ def test_server_defaults(
3199
+ self, metadata, connection, datatype, default, expected_reg
3200
+ ):
3201
+ t = Table(
3202
+ "t",
3203
+ metadata,
3204
+ Column("id", Integer, primary_key=True),
3205
+ Column("thecol", datatype, server_default=default),
3206
+ )
3207
+ t.create(connection)
3208
+
3209
+ reflected = inspect(connection).get_columns("t")[1]["default"]
3210
+ reflected_sanitized = re.sub(r"[\(\) \']", "", reflected)
3211
+ eq_regex(reflected_sanitized, expected_reg, flags=re.IGNORECASE)
3212
+
3213
+
3214
+ class NormalizedNameTest(fixtures.TablesTest):
3215
+ __requires__ = ("denormalized_names",)
3216
+ __sparse_driver_backend__ = True
3217
+
3218
+ @classmethod
3219
+ def define_tables(cls, metadata):
3220
+ Table(
3221
+ quoted_name("t1", quote=True),
3222
+ metadata,
3223
+ Column("id", Integer, primary_key=True),
3224
+ )
3225
+ Table(
3226
+ quoted_name("t2", quote=True),
3227
+ metadata,
3228
+ Column("id", Integer, primary_key=True),
3229
+ Column("t1id", ForeignKey("t1.id")),
3230
+ )
3231
+
3232
+ def test_reflect_lowercase_forced_tables(self):
3233
+ m2 = MetaData()
3234
+ t2_ref = Table(
3235
+ quoted_name("t2", quote=True), m2, autoload_with=config.db
3236
+ )
3237
+ t1_ref = m2.tables["t1"]
3238
+ assert t2_ref.c.t1id.references(t1_ref.c.id)
3239
+
3240
+ m3 = MetaData()
3241
+ m3.reflect(
3242
+ config.db, only=lambda name, m: name.lower() in ("t1", "t2")
3243
+ )
3244
+ assert m3.tables["t2"].c.t1id.references(m3.tables["t1"].c.id)
3245
+
3246
+ def test_get_table_names(self):
3247
+ tablenames = [
3248
+ t
3249
+ for t in inspect(config.db).get_table_names()
3250
+ if t.lower() in ("t1", "t2")
3251
+ ]
3252
+
3253
+ eq_(tablenames[0].upper(), tablenames[0].lower())
3254
+ eq_(tablenames[1].upper(), tablenames[1].lower())
3255
+
3256
+
3257
+ class ComputedReflectionTest(fixtures.ComputedReflectionFixtureTest):
3258
+ def test_computed_col_default_not_set(self):
3259
+ insp = inspect(config.db)
3260
+
3261
+ cols = insp.get_columns("computed_default_table")
3262
+ col_data = {c["name"]: c for c in cols}
3263
+ is_true("42" in col_data["with_default"]["default"])
3264
+ is_(col_data["normal"]["default"], None)
3265
+ is_(col_data["computed_col"]["default"], None)
3266
+
3267
+ def test_get_column_returns_computed(self):
3268
+ insp = inspect(config.db)
3269
+
3270
+ cols = insp.get_columns("computed_default_table")
3271
+ data = {c["name"]: c for c in cols}
3272
+ for key in ("id", "normal", "with_default"):
3273
+ is_true("computed" not in data[key])
3274
+ compData = data["computed_col"]
3275
+ is_true("computed" in compData)
3276
+ is_true("sqltext" in compData["computed"])
3277
+ eq_(self.normalize(compData["computed"]["sqltext"]), "normal+42")
3278
+ eq_(
3279
+ "persisted" in compData["computed"],
3280
+ testing.requires.computed_columns_reflect_persisted.enabled,
3281
+ )
3282
+ if testing.requires.computed_columns_reflect_persisted.enabled:
3283
+ eq_(
3284
+ compData["computed"]["persisted"],
3285
+ testing.requires.computed_columns_default_persisted.enabled,
3286
+ )
3287
+
3288
+ def check_column(self, data, column, sqltext, persisted):
3289
+ is_true("computed" in data[column])
3290
+ compData = data[column]["computed"]
3291
+ eq_(self.normalize(compData["sqltext"]), sqltext)
3292
+ if testing.requires.computed_columns_reflect_persisted.enabled:
3293
+ is_true("persisted" in compData)
3294
+ is_(compData["persisted"], persisted)
3295
+
3296
+ def test_get_column_returns_persisted(self):
3297
+ insp = inspect(config.db)
3298
+
3299
+ cols = insp.get_columns("computed_column_table")
3300
+ data = {c["name"]: c for c in cols}
3301
+
3302
+ self.check_column(
3303
+ data,
3304
+ "computed_no_flag",
3305
+ "normal+42",
3306
+ testing.requires.computed_columns_default_persisted.enabled,
3307
+ )
3308
+ if testing.requires.computed_columns_virtual.enabled:
3309
+ self.check_column(
3310
+ data,
3311
+ "computed_virtual",
3312
+ "normal+2",
3313
+ False,
3314
+ )
3315
+ if testing.requires.computed_columns_stored.enabled:
3316
+ self.check_column(
3317
+ data,
3318
+ "computed_stored",
3319
+ "normal-42",
3320
+ True,
3321
+ )
3322
+
3323
+ @testing.requires.schemas
3324
+ def test_get_column_returns_persisted_with_schema(self):
3325
+ insp = inspect(config.db)
3326
+
3327
+ cols = insp.get_columns(
3328
+ "computed_column_table", schema=config.test_schema
3329
+ )
3330
+ data = {c["name"]: c for c in cols}
3331
+
3332
+ self.check_column(
3333
+ data,
3334
+ "computed_no_flag",
3335
+ "normal/42",
3336
+ testing.requires.computed_columns_default_persisted.enabled,
3337
+ )
3338
+ if testing.requires.computed_columns_virtual.enabled:
3339
+ self.check_column(
3340
+ data,
3341
+ "computed_virtual",
3342
+ "normal/2",
3343
+ False,
3344
+ )
3345
+ if testing.requires.computed_columns_stored.enabled:
3346
+ self.check_column(
3347
+ data,
3348
+ "computed_stored",
3349
+ "normal*42",
3350
+ True,
3351
+ )
3352
+
3353
+
3354
+ class IdentityReflectionTest(fixtures.TablesTest):
3355
+ run_inserts = run_deletes = None
3356
+
3357
+ __sparse_driver_backend__ = True
3358
+ __requires__ = ("identity_columns", "table_reflection")
3359
+
3360
+ @classmethod
3361
+ def define_tables(cls, metadata):
3362
+ Table(
3363
+ "t1",
3364
+ metadata,
3365
+ Column("normal", Integer),
3366
+ Column("id1", Integer, Identity()),
3367
+ )
3368
+ Table(
3369
+ "t2",
3370
+ metadata,
3371
+ Column(
3372
+ "id2",
3373
+ Integer,
3374
+ Identity(
3375
+ always=True,
3376
+ start=2,
3377
+ increment=3,
3378
+ minvalue=-2,
3379
+ maxvalue=42,
3380
+ cycle=True,
3381
+ cache=4,
3382
+ ),
3383
+ ),
3384
+ )
3385
+ if testing.requires.schemas.enabled:
3386
+ Table(
3387
+ "t1",
3388
+ metadata,
3389
+ Column("normal", Integer),
3390
+ Column("id1", Integer, Identity(always=True, start=20)),
3391
+ schema=config.test_schema,
3392
+ )
3393
+
3394
+ def check(self, value, exp, approx):
3395
+ if testing.requires.identity_columns_standard.enabled:
3396
+ common_keys = (
3397
+ "always",
3398
+ "start",
3399
+ "increment",
3400
+ "minvalue",
3401
+ "maxvalue",
3402
+ "cycle",
3403
+ "cache",
3404
+ )
3405
+ for k in list(value):
3406
+ if k not in common_keys:
3407
+ value.pop(k)
3408
+ if approx:
3409
+ eq_(len(value), len(exp))
3410
+ for k in value:
3411
+ if k == "minvalue":
3412
+ is_true(value[k] <= exp[k])
3413
+ elif k in {"maxvalue", "cache"}:
3414
+ is_true(value[k] >= exp[k])
3415
+ else:
3416
+ eq_(value[k], exp[k], k)
3417
+ else:
3418
+ eq_(value, exp)
3419
+ else:
3420
+ eq_(value["start"], exp["start"])
3421
+ eq_(value["increment"], exp["increment"])
3422
+
3423
+ def test_reflect_identity(self):
3424
+ insp = inspect(config.db)
3425
+
3426
+ cols = insp.get_columns("t1") + insp.get_columns("t2")
3427
+ for col in cols:
3428
+ if col["name"] == "normal":
3429
+ is_false("identity" in col)
3430
+ elif col["name"] == "id1":
3431
+ if "autoincrement" in col:
3432
+ is_true(col["autoincrement"])
3433
+ eq_(col["default"], None)
3434
+ is_true("identity" in col)
3435
+ self.check(
3436
+ col["identity"],
3437
+ dict(
3438
+ always=False,
3439
+ start=1,
3440
+ increment=1,
3441
+ minvalue=1,
3442
+ maxvalue=2147483647,
3443
+ cycle=False,
3444
+ cache=1,
3445
+ ),
3446
+ approx=True,
3447
+ )
3448
+ elif col["name"] == "id2":
3449
+ if "autoincrement" in col:
3450
+ is_true(col["autoincrement"])
3451
+ eq_(col["default"], None)
3452
+ is_true("identity" in col)
3453
+ self.check(
3454
+ col["identity"],
3455
+ dict(
3456
+ always=True,
3457
+ start=2,
3458
+ increment=3,
3459
+ minvalue=-2,
3460
+ maxvalue=42,
3461
+ cycle=True,
3462
+ cache=4,
3463
+ ),
3464
+ approx=False,
3465
+ )
3466
+
3467
+ @testing.requires.schemas
3468
+ def test_reflect_identity_schema(self):
3469
+ insp = inspect(config.db)
3470
+
3471
+ cols = insp.get_columns("t1", schema=config.test_schema)
3472
+ for col in cols:
3473
+ if col["name"] == "normal":
3474
+ is_false("identity" in col)
3475
+ elif col["name"] == "id1":
3476
+ if "autoincrement" in col:
3477
+ is_true(col["autoincrement"])
3478
+ eq_(col["default"], None)
3479
+ is_true("identity" in col)
3480
+ self.check(
3481
+ col["identity"],
3482
+ dict(
3483
+ always=True,
3484
+ start=20,
3485
+ increment=1,
3486
+ minvalue=1,
3487
+ maxvalue=2147483647,
3488
+ cycle=False,
3489
+ cache=1,
3490
+ ),
3491
+ approx=True,
3492
+ )
3493
+
3494
+
3495
+ class CompositeKeyReflectionTest(fixtures.TablesTest):
3496
+ __sparse_driver_backend__ = True
3497
+
3498
+ @classmethod
3499
+ def define_tables(cls, metadata):
3500
+ tb1 = Table(
3501
+ "tb1",
3502
+ metadata,
3503
+ Column("id", Integer),
3504
+ Column("attr", Integer),
3505
+ Column("name", sql_types.VARCHAR(20)),
3506
+ sa.PrimaryKeyConstraint("name", "id", "attr", name="pk_tb1"),
3507
+ schema=None,
3508
+ test_needs_fk=True,
3509
+ )
3510
+ Table(
3511
+ "tb2",
3512
+ metadata,
3513
+ Column("id", Integer, primary_key=True),
3514
+ Column("pid", Integer),
3515
+ Column("pattr", Integer),
3516
+ Column("pname", sql_types.VARCHAR(20)),
3517
+ sa.ForeignKeyConstraint(
3518
+ ["pname", "pid", "pattr"],
3519
+ [tb1.c.name, tb1.c.id, tb1.c.attr],
3520
+ name="fk_tb1_name_id_attr",
3521
+ ),
3522
+ schema=None,
3523
+ test_needs_fk=True,
3524
+ )
3525
+
3526
+ @testing.requires.primary_key_constraint_reflection
3527
+ def test_pk_column_order(self, connection):
3528
+ # test for issue #5661
3529
+ insp = inspect(connection)
3530
+ primary_key = insp.get_pk_constraint(self.tables.tb1.name)
3531
+ eq_(primary_key.get("constrained_columns"), ["name", "id", "attr"])
3532
+
3533
+ @testing.requires.foreign_key_constraint_reflection
3534
+ def test_fk_column_order(self, connection):
3535
+ # test for issue #5661
3536
+ insp = inspect(connection)
3537
+ foreign_keys = insp.get_foreign_keys(self.tables.tb2.name)
3538
+ eq_(len(foreign_keys), 1)
3539
+ fkey1 = foreign_keys[0]
3540
+ eq_(fkey1.get("referred_columns"), ["name", "id", "attr"])
3541
+ eq_(fkey1.get("constrained_columns"), ["pname", "pid", "pattr"])
3542
+
3543
+
3544
+ __all__ = (
3545
+ "ComponentReflectionTest",
3546
+ "ComponentReflectionTestExtra",
3547
+ "TableNoColumnsTest",
3548
+ "QuotedNameArgumentTest",
3549
+ "BizarroCharacterTest",
3550
+ "HasTableTest",
3551
+ "HasIndexTest",
3552
+ "NormalizedNameTest",
3553
+ "ComputedReflectionTest",
3554
+ "IdentityReflectionTest",
3555
+ "CompositeKeyReflectionTest",
3556
+ "TempTableElementsTest",
3557
+ )