SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.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 (270) hide show
  1. sqlalchemy/__init__.py +298 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.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
+
105
+ test_table = metadata.tables["test_table"]
106
+ sa.CreateView(
107
+ sa.select(test_table.c.id, test_table.c.data),
108
+ "vv",
109
+ metadata=metadata,
110
+ )
111
+
112
+ if testing.requires.schemas.enabled:
113
+ test_table_s = metadata.tables[
114
+ f"{config.test_schema}.test_table_s"
115
+ ]
116
+ sa.CreateView(
117
+ sa.select(test_table_s.c.id, test_table_s.c.data),
118
+ "vv",
119
+ metadata=metadata,
120
+ schema=config.test_schema,
121
+ )
122
+
123
+ @classmethod
124
+ def temp_table_name(cls):
125
+ return get_temp_table_name(
126
+ config, config.db, f"user_tmp_{config.ident}"
127
+ )
128
+
129
+ @classmethod
130
+ def define_temp_tables(cls, metadata):
131
+ kw = temp_table_keyword_args(config, config.db)
132
+ table_name = cls.temp_table_name()
133
+ user_tmp = Table(
134
+ table_name,
135
+ metadata,
136
+ Column("id", sa.INT, primary_key=True),
137
+ Column("name", sa.VARCHAR(50)),
138
+ **kw,
139
+ )
140
+ if (
141
+ testing.requires.view_reflection.enabled
142
+ and testing.requires.temporary_views.enabled
143
+ ):
144
+ event.listen(
145
+ user_tmp,
146
+ "after_create",
147
+ DDL(
148
+ "create temporary view user_tmp_v as "
149
+ "select * from user_tmp_%s" % config.ident
150
+ ),
151
+ )
152
+ event.listen(user_tmp, "before_drop", DDL("drop view user_tmp_v"))
153
+
154
+ def test_has_table(self):
155
+ with config.db.begin() as conn:
156
+ is_true(config.db.dialect.has_table(conn, "test_table"))
157
+ is_false(config.db.dialect.has_table(conn, "test_table_s"))
158
+ is_false(config.db.dialect.has_table(conn, "nonexistent_table"))
159
+
160
+ def test_has_table_cache(self, metadata):
161
+ insp = inspect(config.db)
162
+ is_true(insp.has_table("test_table"))
163
+ nt = Table("new_table", metadata, Column("col", Integer))
164
+ is_false(insp.has_table("new_table"))
165
+ nt.create(config.db)
166
+ try:
167
+ is_false(insp.has_table("new_table"))
168
+ insp.clear_cache()
169
+ is_true(insp.has_table("new_table"))
170
+ finally:
171
+ nt.drop(config.db)
172
+
173
+ @testing.requires.schemas
174
+ def test_has_table_schema(self):
175
+ with config.db.begin() as conn:
176
+ is_false(
177
+ config.db.dialect.has_table(
178
+ conn, "test_table", schema=config.test_schema
179
+ )
180
+ )
181
+ is_true(
182
+ config.db.dialect.has_table(
183
+ conn, "test_table_s", schema=config.test_schema
184
+ )
185
+ )
186
+ is_false(
187
+ config.db.dialect.has_table(
188
+ conn, "nonexistent_table", schema=config.test_schema
189
+ )
190
+ )
191
+
192
+ @testing.requires.schemas
193
+ def test_has_table_nonexistent_schema(self):
194
+ with config.db.begin() as conn:
195
+ is_false(
196
+ config.db.dialect.has_table(
197
+ conn, "test_table", schema="nonexistent_schema"
198
+ )
199
+ )
200
+
201
+ @testing.requires.views
202
+ def test_has_table_view(self, connection):
203
+ insp = inspect(connection)
204
+ is_true(insp.has_table("vv"))
205
+
206
+ @testing.requires.has_temp_table
207
+ def test_has_table_temp_table(self, connection):
208
+ insp = inspect(connection)
209
+ temp_table_name = self.temp_table_name()
210
+ is_true(insp.has_table(temp_table_name))
211
+
212
+ @testing.requires.has_temp_table
213
+ @testing.requires.view_reflection
214
+ @testing.requires.temporary_views
215
+ def test_has_table_temp_view(self, connection):
216
+ insp = inspect(connection)
217
+ is_true(insp.has_table("user_tmp_v"))
218
+
219
+ @testing.requires.views
220
+ @testing.requires.schemas
221
+ def test_has_table_view_schema(self, connection):
222
+ insp = inspect(connection)
223
+ is_true(insp.has_table("vv", config.test_schema))
224
+
225
+
226
+ class HasIndexTest(fixtures.TablesTest):
227
+ __sparse_driver_backend__ = True
228
+ __requires__ = ("index_reflection",)
229
+
230
+ @classmethod
231
+ def define_tables(cls, metadata):
232
+ tt = Table(
233
+ "test_table",
234
+ metadata,
235
+ Column("id", Integer, primary_key=True),
236
+ Column("data", String(50)),
237
+ Column("data2", String(50)),
238
+ )
239
+ Index("my_idx", tt.c.data)
240
+
241
+ if testing.requires.schemas.enabled:
242
+ tt = Table(
243
+ "test_table",
244
+ metadata,
245
+ Column("id", Integer, primary_key=True),
246
+ Column("data", String(50)),
247
+ schema=config.test_schema,
248
+ )
249
+ Index("my_idx_s", tt.c.data)
250
+
251
+ kind = testing.combinations("dialect", "inspector", argnames="kind")
252
+
253
+ def _has_index(self, kind, conn):
254
+ if kind == "dialect":
255
+ return lambda *a, **k: config.db.dialect.has_index(conn, *a, **k)
256
+ else:
257
+ return inspect(conn).has_index
258
+
259
+ @kind
260
+ def test_has_index(self, kind, connection, metadata):
261
+ meth = self._has_index(kind, connection)
262
+ assert meth("test_table", "my_idx")
263
+ assert not meth("test_table", "my_idx_s")
264
+ assert not meth("nonexistent_table", "my_idx")
265
+ assert not meth("test_table", "nonexistent_idx")
266
+
267
+ assert not meth("test_table", "my_idx_2")
268
+ assert not meth("test_table_2", "my_idx_3")
269
+ idx = Index("my_idx_2", self.tables.test_table.c.data2)
270
+ tbl = Table(
271
+ "test_table_2",
272
+ metadata,
273
+ Column("foo", Integer),
274
+ Index("my_idx_3", "foo"),
275
+ )
276
+ idx.create(connection)
277
+ tbl.create(connection)
278
+ try:
279
+ if kind == "inspector":
280
+ assert not meth("test_table", "my_idx_2")
281
+ assert not meth("test_table_2", "my_idx_3")
282
+ meth.__self__.clear_cache()
283
+ assert meth("test_table", "my_idx_2") is True
284
+ assert meth("test_table_2", "my_idx_3") is True
285
+ finally:
286
+ tbl.drop(connection)
287
+ idx.drop(connection)
288
+
289
+ @testing.requires.schemas
290
+ @kind
291
+ def test_has_index_schema(self, kind, connection):
292
+ meth = self._has_index(kind, connection)
293
+ assert meth("test_table", "my_idx_s", schema=config.test_schema)
294
+ assert not meth("test_table", "my_idx", schema=config.test_schema)
295
+ assert not meth(
296
+ "nonexistent_table", "my_idx_s", schema=config.test_schema
297
+ )
298
+ assert not meth(
299
+ "test_table", "nonexistent_idx_s", schema=config.test_schema
300
+ )
301
+
302
+
303
+ class BizarroCharacterTest(fixtures.TestBase):
304
+
305
+ __sparse_driver_backend__ = True
306
+
307
+ def column_names():
308
+ return testing.combinations(
309
+ ("plainname",),
310
+ ("(3)",),
311
+ ("col%p",),
312
+ ("[brack]",),
313
+ argnames="columnname",
314
+ )
315
+
316
+ def table_names():
317
+ return testing.combinations(
318
+ ("plain",),
319
+ ("(2)",),
320
+ ("per % cent",),
321
+ ("[brackets]",),
322
+ argnames="tablename",
323
+ )
324
+
325
+ @testing.variation("use_composite", [True, False])
326
+ @column_names()
327
+ @table_names()
328
+ @testing.requires.foreign_key_constraint_reflection
329
+ def test_fk_ref(
330
+ self, connection, metadata, use_composite, tablename, columnname
331
+ ):
332
+ """tests for #10275"""
333
+ tt = Table(
334
+ tablename,
335
+ metadata,
336
+ Column(columnname, Integer, key="id", primary_key=True),
337
+ test_needs_fk=True,
338
+ )
339
+ if use_composite:
340
+ tt.append_column(Column("id2", Integer, primary_key=True))
341
+
342
+ if use_composite:
343
+ Table(
344
+ "other",
345
+ metadata,
346
+ Column("id", Integer, primary_key=True),
347
+ Column("ref", Integer),
348
+ Column("ref2", Integer),
349
+ sa.ForeignKeyConstraint(["ref", "ref2"], [tt.c.id, tt.c.id2]),
350
+ test_needs_fk=True,
351
+ )
352
+ else:
353
+ Table(
354
+ "other",
355
+ metadata,
356
+ Column("id", Integer, primary_key=True),
357
+ Column("ref", ForeignKey(tt.c.id)),
358
+ test_needs_fk=True,
359
+ )
360
+
361
+ metadata.create_all(connection)
362
+
363
+ m2 = MetaData()
364
+
365
+ o2 = Table("other", m2, autoload_with=connection)
366
+ t1 = m2.tables[tablename]
367
+
368
+ assert o2.c.ref.references(t1.c[0])
369
+ if use_composite:
370
+ assert o2.c.ref2.references(t1.c[1])
371
+
372
+ @column_names()
373
+ @table_names()
374
+ @testing.requires.identity_columns
375
+ def test_reflect_identity(
376
+ self, tablename, columnname, connection, metadata
377
+ ):
378
+ Table(
379
+ tablename,
380
+ metadata,
381
+ Column(columnname, Integer, Identity(), primary_key=True),
382
+ )
383
+ metadata.create_all(connection)
384
+ insp = inspect(connection)
385
+
386
+ eq_(insp.get_columns(tablename)[0]["identity"]["start"], 1)
387
+
388
+ @column_names()
389
+ @table_names()
390
+ @testing.requires.comment_reflection
391
+ def test_reflect_comments(
392
+ self, tablename, columnname, connection, metadata
393
+ ):
394
+ Table(
395
+ tablename,
396
+ metadata,
397
+ Column("id", Integer, primary_key=True),
398
+ Column(columnname, Integer, comment="some comment"),
399
+ )
400
+ metadata.create_all(connection)
401
+ insp = inspect(connection)
402
+
403
+ eq_(insp.get_columns(tablename)[1]["comment"], "some comment")
404
+
405
+
406
+ class TempTableElementsTest(fixtures.TestBase):
407
+
408
+ __sparse_driver_backend__ = True
409
+
410
+ __requires__ = ("temp_table_reflection",)
411
+
412
+ @testing.fixture
413
+ def tablename(self):
414
+ return get_temp_table_name(
415
+ config, config.db, f"ident_tmp_{config.ident}"
416
+ )
417
+
418
+ @testing.requires.identity_columns
419
+ def test_reflect_identity(self, tablename, connection, metadata):
420
+ Table(
421
+ tablename,
422
+ metadata,
423
+ Column("id", Integer, Identity(), primary_key=True),
424
+ )
425
+ metadata.create_all(connection)
426
+ insp = inspect(connection)
427
+
428
+ eq_(insp.get_columns(tablename)[0]["identity"]["start"], 1)
429
+
430
+ @testing.requires.temp_table_comment_reflection
431
+ def test_reflect_comments(self, tablename, connection, metadata):
432
+ Table(
433
+ tablename,
434
+ metadata,
435
+ Column("id", Integer, primary_key=True),
436
+ Column("foobar", Integer, comment="some comment"),
437
+ )
438
+ metadata.create_all(connection)
439
+ insp = inspect(connection)
440
+
441
+ eq_(insp.get_columns(tablename)[1]["comment"], "some comment")
442
+
443
+
444
+ class QuotedNameArgumentTest(fixtures.TablesTest):
445
+ run_create_tables = "once"
446
+ __sparse_driver_backend__ = True
447
+
448
+ @classmethod
449
+ def define_tables(cls, metadata):
450
+ Table(
451
+ "quote ' one",
452
+ metadata,
453
+ Column("id", Integer),
454
+ Column("name", String(50)),
455
+ Column("data", String(50)),
456
+ Column("related_id", Integer),
457
+ sa.PrimaryKeyConstraint("id", name="pk quote ' one"),
458
+ sa.Index("ix quote ' one", "name"),
459
+ sa.UniqueConstraint(
460
+ "data",
461
+ name="uq quote' one",
462
+ ),
463
+ sa.ForeignKeyConstraint(
464
+ ["id"], ["related.id"], name="fk quote ' one"
465
+ ),
466
+ sa.CheckConstraint("name != 'foo'", name="ck quote ' one"),
467
+ comment=r"""quote ' one comment""",
468
+ test_needs_fk=True,
469
+ )
470
+
471
+ if testing.requires.symbol_names_w_double_quote.enabled:
472
+ Table(
473
+ 'quote " two',
474
+ metadata,
475
+ Column("id", Integer),
476
+ Column("name", String(50)),
477
+ Column("data", String(50)),
478
+ Column("related_id", Integer),
479
+ sa.PrimaryKeyConstraint("id", name='pk quote " two'),
480
+ sa.Index('ix quote " two', "name"),
481
+ sa.UniqueConstraint(
482
+ "data",
483
+ name='uq quote" two',
484
+ ),
485
+ sa.ForeignKeyConstraint(
486
+ ["id"], ["related.id"], name='fk quote " two'
487
+ ),
488
+ sa.CheckConstraint("name != 'foo'", name='ck quote " two '),
489
+ comment=r"""quote " two comment""",
490
+ test_needs_fk=True,
491
+ )
492
+
493
+ Table(
494
+ "related",
495
+ metadata,
496
+ Column("id", Integer, primary_key=True),
497
+ Column("related", Integer),
498
+ test_needs_fk=True,
499
+ )
500
+
501
+ if testing.requires.view_column_reflection.enabled:
502
+ if testing.requires.symbol_names_w_double_quote.enabled:
503
+ names = [
504
+ "quote ' one",
505
+ 'quote " two',
506
+ ]
507
+ else:
508
+ names = [
509
+ "quote ' one",
510
+ ]
511
+ for name in names:
512
+ query = "CREATE VIEW %s AS SELECT * FROM %s" % (
513
+ config.db.dialect.identifier_preparer.quote(
514
+ "view %s" % name
515
+ ),
516
+ config.db.dialect.identifier_preparer.quote(name),
517
+ )
518
+
519
+ event.listen(metadata, "after_create", DDL(query))
520
+ event.listen(
521
+ metadata,
522
+ "before_drop",
523
+ DDL(
524
+ "DROP VIEW %s"
525
+ % config.db.dialect.identifier_preparer.quote(
526
+ "view %s" % name
527
+ )
528
+ ),
529
+ )
530
+
531
+ def quote_fixtures(fn):
532
+ return testing.combinations(
533
+ ("quote ' one",),
534
+ ('quote " two', testing.requires.symbol_names_w_double_quote),
535
+ )(fn)
536
+
537
+ @quote_fixtures
538
+ def test_get_table_options(self, name):
539
+ insp = inspect(config.db)
540
+
541
+ if testing.requires.reflect_table_options.enabled:
542
+ res = insp.get_table_options(name)
543
+ is_true(isinstance(res, dict))
544
+ else:
545
+ with expect_raises(NotImplementedError):
546
+ insp.get_table_options(name)
547
+
548
+ @quote_fixtures
549
+ @testing.requires.view_column_reflection
550
+ def test_get_view_definition(self, name):
551
+ insp = inspect(config.db)
552
+ assert insp.get_view_definition("view %s" % name)
553
+
554
+ @quote_fixtures
555
+ def test_get_columns(self, name):
556
+ insp = inspect(config.db)
557
+ assert insp.get_columns(name)
558
+
559
+ @quote_fixtures
560
+ def test_get_pk_constraint(self, name):
561
+ insp = inspect(config.db)
562
+ assert insp.get_pk_constraint(name)
563
+
564
+ @quote_fixtures
565
+ @testing.requires.foreign_key_constraint_reflection
566
+ def test_get_foreign_keys(self, name):
567
+ insp = inspect(config.db)
568
+ assert insp.get_foreign_keys(name)
569
+
570
+ @quote_fixtures
571
+ @testing.requires.index_reflection
572
+ def test_get_indexes(self, name):
573
+ insp = inspect(config.db)
574
+ assert insp.get_indexes(name)
575
+
576
+ @quote_fixtures
577
+ @testing.requires.unique_constraint_reflection
578
+ def test_get_unique_constraints(self, name):
579
+ insp = inspect(config.db)
580
+ assert insp.get_unique_constraints(name)
581
+
582
+ @quote_fixtures
583
+ @testing.requires.comment_reflection
584
+ def test_get_table_comment(self, name):
585
+ insp = inspect(config.db)
586
+ assert insp.get_table_comment(name)
587
+
588
+ @quote_fixtures
589
+ @testing.requires.check_constraint_reflection
590
+ def test_get_check_constraints(self, name):
591
+ insp = inspect(config.db)
592
+ assert insp.get_check_constraints(name)
593
+
594
+
595
+ def _multi_combination(fn):
596
+ schema = testing.combinations(
597
+ None,
598
+ (
599
+ lambda: config.test_schema,
600
+ testing.requires.schemas,
601
+ ),
602
+ argnames="schema",
603
+ )
604
+ scope = testing.combinations(
605
+ ObjectScope.DEFAULT,
606
+ ObjectScope.TEMPORARY,
607
+ ObjectScope.ANY,
608
+ argnames="scope",
609
+ )
610
+ kind = testing.combinations(
611
+ ObjectKind.TABLE,
612
+ ObjectKind.VIEW,
613
+ ObjectKind.MATERIALIZED_VIEW,
614
+ ObjectKind.ANY,
615
+ ObjectKind.ANY_VIEW,
616
+ ObjectKind.TABLE | ObjectKind.VIEW,
617
+ ObjectKind.TABLE | ObjectKind.MATERIALIZED_VIEW,
618
+ argnames="kind",
619
+ )
620
+ filter_names = testing.combinations(True, False, argnames="use_filter")
621
+
622
+ return schema(scope(kind(filter_names(fn))))
623
+
624
+
625
+ class ComponentReflectionTest(ComparesTables, OneConnectionTablesTest):
626
+ run_inserts = run_deletes = None
627
+
628
+ __sparse_driver_backend__ = True
629
+
630
+ @classmethod
631
+ def define_tables(cls, metadata):
632
+ cls.define_reflected_tables(metadata, None)
633
+ if testing.requires.schemas.enabled:
634
+ cls.define_reflected_tables(metadata, testing.config.test_schema)
635
+
636
+ @classmethod
637
+ def define_reflected_tables(cls, metadata, schema):
638
+ if schema:
639
+ schema_prefix = schema + "."
640
+ else:
641
+ schema_prefix = ""
642
+
643
+ if testing.requires.self_referential_foreign_keys.enabled:
644
+ parent_id_args = (
645
+ ForeignKey(
646
+ "%susers.user_id" % schema_prefix, name="user_id_fk"
647
+ ),
648
+ )
649
+ else:
650
+ parent_id_args = ()
651
+ users = Table(
652
+ "users",
653
+ metadata,
654
+ Column("user_id", sa.INT, primary_key=True),
655
+ Column("test1", sa.CHAR(5), nullable=False),
656
+ Column("test2", sa.Float(), nullable=False),
657
+ Column("parent_user_id", sa.Integer, *parent_id_args),
658
+ sa.CheckConstraint(
659
+ "test2 > 0",
660
+ name="zz_test2_gt_zero",
661
+ comment="users check constraint",
662
+ ),
663
+ sa.CheckConstraint("test2 <= 1000"),
664
+ schema=schema,
665
+ test_needs_fk=True,
666
+ )
667
+
668
+ Table(
669
+ "dingalings",
670
+ metadata,
671
+ Column("dingaling_id", sa.Integer, primary_key=True),
672
+ Column(
673
+ "address_id",
674
+ sa.Integer,
675
+ ForeignKey(
676
+ "%semail_addresses.address_id" % schema_prefix,
677
+ name="zz_email_add_id_fg",
678
+ comment="di fk comment",
679
+ ),
680
+ ),
681
+ Column(
682
+ "id_user",
683
+ sa.Integer,
684
+ ForeignKey("%susers.user_id" % schema_prefix),
685
+ ),
686
+ Column("data", sa.String(30), unique=True),
687
+ sa.CheckConstraint(
688
+ "address_id > 0 AND address_id < 1000",
689
+ name="address_id_gt_zero",
690
+ ),
691
+ sa.UniqueConstraint(
692
+ "address_id",
693
+ "dingaling_id",
694
+ name="zz_dingalings_multiple",
695
+ comment="di unique comment",
696
+ ),
697
+ schema=schema,
698
+ test_needs_fk=True,
699
+ )
700
+ Table(
701
+ "email_addresses",
702
+ metadata,
703
+ Column("address_id", sa.Integer),
704
+ Column("remote_user_id", sa.Integer, ForeignKey(users.c.user_id)),
705
+ Column("email_address", sa.String(20), index=True),
706
+ sa.PrimaryKeyConstraint(
707
+ "address_id", name="email_ad_pk", comment="ea pk comment"
708
+ ),
709
+ schema=schema,
710
+ test_needs_fk=True,
711
+ )
712
+ Table(
713
+ "comment_test",
714
+ metadata,
715
+ Column("id", sa.Integer, primary_key=True, comment="id comment"),
716
+ Column("data", sa.String(20), comment="data % comment"),
717
+ Column(
718
+ "d2",
719
+ sa.String(20),
720
+ comment=r"""Comment types type speedily ' " \ '' Fun!""",
721
+ ),
722
+ Column("d3", sa.String(42), comment="Comment\nwith\rescapes"),
723
+ schema=schema,
724
+ comment=r"""the test % ' " \ table comment""",
725
+ )
726
+ Table(
727
+ "no_constraints",
728
+ metadata,
729
+ Column("data", sa.String(20)),
730
+ schema=schema,
731
+ comment="no\nconstraints\rhas\fescaped\vcomment",
732
+ )
733
+
734
+ if testing.requires.cross_schema_fk_reflection.enabled:
735
+ if schema is None:
736
+ Table(
737
+ "local_table",
738
+ metadata,
739
+ Column("id", sa.Integer, primary_key=True),
740
+ Column("data", sa.String(20)),
741
+ Column(
742
+ "remote_id",
743
+ ForeignKey(
744
+ "%s.remote_table_2.id" % testing.config.test_schema
745
+ ),
746
+ ),
747
+ test_needs_fk=True,
748
+ schema=config.db.dialect.default_schema_name,
749
+ )
750
+ else:
751
+ Table(
752
+ "remote_table",
753
+ metadata,
754
+ Column("id", sa.Integer, primary_key=True),
755
+ Column(
756
+ "local_id",
757
+ ForeignKey(
758
+ "%s.local_table.id"
759
+ % config.db.dialect.default_schema_name
760
+ ),
761
+ ),
762
+ Column("data", sa.String(20)),
763
+ schema=schema,
764
+ test_needs_fk=True,
765
+ )
766
+ Table(
767
+ "remote_table_2",
768
+ metadata,
769
+ Column("id", sa.Integer, primary_key=True),
770
+ Column("data", sa.String(20)),
771
+ schema=schema,
772
+ test_needs_fk=True,
773
+ )
774
+
775
+ if testing.requires.index_reflection.enabled:
776
+ Index("users_t_idx", users.c.test1, users.c.test2, unique=True)
777
+ Index(
778
+ "users_all_idx", users.c.user_id, users.c.test2, users.c.test1
779
+ )
780
+
781
+ if not schema:
782
+ # test_needs_fk is at the moment to force MySQL InnoDB
783
+ noncol_idx_test_nopk = Table(
784
+ "noncol_idx_test_nopk",
785
+ metadata,
786
+ Column("q", sa.String(5)),
787
+ test_needs_fk=True,
788
+ )
789
+
790
+ noncol_idx_test_pk = Table(
791
+ "noncol_idx_test_pk",
792
+ metadata,
793
+ Column("id", sa.Integer, primary_key=True),
794
+ Column("q", sa.String(5)),
795
+ test_needs_fk=True,
796
+ )
797
+
798
+ if (
799
+ testing.requires.indexes_with_ascdesc.enabled
800
+ and testing.requires.reflect_indexes_with_ascdesc.enabled
801
+ ):
802
+ Index("noncol_idx_nopk", noncol_idx_test_nopk.c.q.desc())
803
+ Index("noncol_idx_pk", noncol_idx_test_pk.c.q.desc())
804
+
805
+ if testing.requires.view_column_reflection.enabled:
806
+ cls.define_views(metadata, schema)
807
+ if not schema and testing.requires.temp_table_reflection.enabled:
808
+ cls.define_temp_tables(metadata)
809
+
810
+ @classmethod
811
+ def temp_table_name(cls):
812
+ return get_temp_table_name(
813
+ config, config.db, f"user_tmp_{config.ident}"
814
+ )
815
+
816
+ @classmethod
817
+ def define_temp_tables(cls, metadata):
818
+ kw = temp_table_keyword_args(config, config.db)
819
+ table_name = cls.temp_table_name()
820
+ user_tmp = Table(
821
+ table_name,
822
+ metadata,
823
+ Column("id", sa.INT, primary_key=True),
824
+ Column("name", sa.VARCHAR(50)),
825
+ Column("foo", sa.INT),
826
+ # disambiguate temp table unique constraint names. this is
827
+ # pretty arbitrary for a generic dialect however we are doing
828
+ # it to suit SQL Server which will produce name conflicts for
829
+ # unique constraints created against temp tables in different
830
+ # databases.
831
+ # https://www.arbinada.com/en/node/1645
832
+ sa.UniqueConstraint("name", name=f"user_tmp_uq_{config.ident}"),
833
+ sa.Index("user_tmp_ix", "foo"),
834
+ **kw,
835
+ )
836
+ if (
837
+ testing.requires.view_reflection.enabled
838
+ and testing.requires.temporary_views.enabled
839
+ ):
840
+ event.listen(
841
+ user_tmp,
842
+ "after_create",
843
+ DDL(
844
+ "create temporary view user_tmp_v as "
845
+ "select * from user_tmp_%s" % config.ident
846
+ ),
847
+ )
848
+ event.listen(user_tmp, "before_drop", DDL("drop view user_tmp_v"))
849
+
850
+ @classmethod
851
+ def define_views(cls, metadata, schema):
852
+ if testing.requires.materialized_views.enabled:
853
+ materialized = {"dingalings"}
854
+ else:
855
+ materialized = set()
856
+ for table_name in ("users", "email_addresses", "dingalings"):
857
+ fullname = table_name
858
+ if schema:
859
+ fullname = f"{schema}.{table_name}"
860
+ view_name = fullname + "_v"
861
+ prefix = "MATERIALIZED " if table_name in materialized else ""
862
+ query = (
863
+ f"CREATE {prefix}VIEW {view_name} AS SELECT * FROM {fullname}"
864
+ )
865
+
866
+ event.listen(metadata, "after_create", DDL(query))
867
+ if table_name in materialized:
868
+ index_name = "mat_index"
869
+ if schema and testing.against("oracle"):
870
+ index_name = f"{schema}.{index_name}"
871
+ idx = f"CREATE INDEX {index_name} ON {view_name}(data)"
872
+ event.listen(metadata, "after_create", DDL(idx))
873
+ event.listen(
874
+ metadata, "before_drop", DDL(f"DROP {prefix}VIEW {view_name}")
875
+ )
876
+
877
+ def _resolve_kind(self, kind, tables, views, materialized):
878
+ res = {}
879
+ if ObjectKind.TABLE in kind:
880
+ res.update(tables)
881
+ if ObjectKind.VIEW in kind:
882
+ res.update(views)
883
+ if ObjectKind.MATERIALIZED_VIEW in kind:
884
+ res.update(materialized)
885
+ return res
886
+
887
+ def _resolve_views(self, views, materialized):
888
+ if not testing.requires.view_column_reflection.enabled:
889
+ materialized.clear()
890
+ views.clear()
891
+ elif not testing.requires.materialized_views.enabled:
892
+ views.update(materialized)
893
+ materialized.clear()
894
+
895
+ def _resolve_names(self, schema, scope, filter_names, values):
896
+ scope_filter = lambda _: True # noqa: E731
897
+ if scope is ObjectScope.DEFAULT:
898
+ scope_filter = lambda k: "tmp" not in k[1] # noqa: E731
899
+ if scope is ObjectScope.TEMPORARY:
900
+ scope_filter = lambda k: "tmp" in k[1] # noqa: E731
901
+
902
+ removed = {
903
+ None: {"remote_table", "remote_table_2"},
904
+ testing.config.test_schema: {
905
+ "local_table",
906
+ "noncol_idx_test_nopk",
907
+ "noncol_idx_test_pk",
908
+ "user_tmp_v",
909
+ self.temp_table_name(),
910
+ },
911
+ }
912
+ if not testing.requires.cross_schema_fk_reflection.enabled:
913
+ removed[None].add("local_table")
914
+ removed[testing.config.test_schema].update(
915
+ ["remote_table", "remote_table_2"]
916
+ )
917
+ if not testing.requires.index_reflection.enabled:
918
+ removed[None].update(
919
+ ["noncol_idx_test_nopk", "noncol_idx_test_pk"]
920
+ )
921
+ if (
922
+ not testing.requires.temp_table_reflection.enabled
923
+ or not testing.requires.temp_table_names.enabled
924
+ ):
925
+ removed[None].update(["user_tmp_v", self.temp_table_name()])
926
+ if not testing.requires.temporary_views.enabled:
927
+ removed[None].update(["user_tmp_v"])
928
+
929
+ res = {
930
+ k: v
931
+ for k, v in values.items()
932
+ if scope_filter(k)
933
+ and k[1] not in removed[schema]
934
+ and (not filter_names or k[1] in filter_names)
935
+ }
936
+ return res
937
+
938
+ def exp_options(
939
+ self,
940
+ schema=None,
941
+ scope=ObjectScope.ANY,
942
+ kind=ObjectKind.ANY,
943
+ filter_names=None,
944
+ ):
945
+ materialized = {(schema, "dingalings_v"): mock.ANY}
946
+ views = {
947
+ (schema, "email_addresses_v"): mock.ANY,
948
+ (schema, "users_v"): mock.ANY,
949
+ (schema, "user_tmp_v"): mock.ANY,
950
+ }
951
+ self._resolve_views(views, materialized)
952
+ tables = {
953
+ (schema, "users"): mock.ANY,
954
+ (schema, "dingalings"): mock.ANY,
955
+ (schema, "email_addresses"): mock.ANY,
956
+ (schema, "comment_test"): mock.ANY,
957
+ (schema, "no_constraints"): mock.ANY,
958
+ (schema, "local_table"): mock.ANY,
959
+ (schema, "remote_table"): mock.ANY,
960
+ (schema, "remote_table_2"): mock.ANY,
961
+ (schema, "noncol_idx_test_nopk"): mock.ANY,
962
+ (schema, "noncol_idx_test_pk"): mock.ANY,
963
+ (schema, self.temp_table_name()): mock.ANY,
964
+ }
965
+ res = self._resolve_kind(kind, tables, views, materialized)
966
+ res = self._resolve_names(schema, scope, filter_names, res)
967
+ return res
968
+
969
+ def exp_comments(
970
+ self,
971
+ schema=None,
972
+ scope=ObjectScope.ANY,
973
+ kind=ObjectKind.ANY,
974
+ filter_names=None,
975
+ ):
976
+ empty = {"text": None}
977
+ materialized = {(schema, "dingalings_v"): empty}
978
+ views = {
979
+ (schema, "email_addresses_v"): empty,
980
+ (schema, "users_v"): empty,
981
+ (schema, "user_tmp_v"): empty,
982
+ }
983
+ self._resolve_views(views, materialized)
984
+ tables = {
985
+ (schema, "users"): empty,
986
+ (schema, "dingalings"): empty,
987
+ (schema, "email_addresses"): empty,
988
+ (schema, "comment_test"): {
989
+ "text": r"""the test % ' " \ table comment"""
990
+ },
991
+ (schema, "no_constraints"): {
992
+ "text": "no\nconstraints\rhas\fescaped\vcomment"
993
+ },
994
+ (schema, "local_table"): empty,
995
+ (schema, "remote_table"): empty,
996
+ (schema, "remote_table_2"): empty,
997
+ (schema, "noncol_idx_test_nopk"): empty,
998
+ (schema, "noncol_idx_test_pk"): empty,
999
+ (schema, self.temp_table_name()): empty,
1000
+ }
1001
+ res = self._resolve_kind(kind, tables, views, materialized)
1002
+ res = self._resolve_names(schema, scope, filter_names, res)
1003
+ return res
1004
+
1005
+ def exp_columns(
1006
+ self,
1007
+ schema=None,
1008
+ scope=ObjectScope.ANY,
1009
+ kind=ObjectKind.ANY,
1010
+ filter_names=None,
1011
+ ):
1012
+ def col(
1013
+ name, auto=False, default=mock.ANY, comment=None, nullable=True
1014
+ ):
1015
+ res = {
1016
+ "name": name,
1017
+ "autoincrement": auto,
1018
+ "type": mock.ANY,
1019
+ "default": default,
1020
+ "comment": comment,
1021
+ "nullable": nullable,
1022
+ }
1023
+ if auto == "omit":
1024
+ res.pop("autoincrement")
1025
+ return res
1026
+
1027
+ def pk(name, **kw):
1028
+ kw = {"auto": True, "default": mock.ANY, "nullable": False, **kw}
1029
+ return col(name, **kw)
1030
+
1031
+ materialized = {
1032
+ (schema, "dingalings_v"): [
1033
+ col("dingaling_id", auto="omit", nullable=mock.ANY),
1034
+ col("address_id"),
1035
+ col("id_user"),
1036
+ col("data"),
1037
+ ]
1038
+ }
1039
+ views = {
1040
+ (schema, "email_addresses_v"): [
1041
+ col("address_id", auto="omit", nullable=mock.ANY),
1042
+ col("remote_user_id"),
1043
+ col("email_address"),
1044
+ ],
1045
+ (schema, "users_v"): [
1046
+ col("user_id", auto="omit", nullable=mock.ANY),
1047
+ col("test1", nullable=mock.ANY),
1048
+ col("test2", nullable=mock.ANY),
1049
+ col("parent_user_id"),
1050
+ ],
1051
+ (schema, "user_tmp_v"): [
1052
+ col("id", auto="omit", nullable=mock.ANY),
1053
+ col("name"),
1054
+ col("foo"),
1055
+ ],
1056
+ }
1057
+ self._resolve_views(views, materialized)
1058
+ tables = {
1059
+ (schema, "users"): [
1060
+ pk("user_id"),
1061
+ col("test1", nullable=False),
1062
+ col("test2", nullable=False),
1063
+ col("parent_user_id"),
1064
+ ],
1065
+ (schema, "dingalings"): [
1066
+ pk("dingaling_id"),
1067
+ col("address_id"),
1068
+ col("id_user"),
1069
+ col("data"),
1070
+ ],
1071
+ (schema, "email_addresses"): [
1072
+ pk("address_id"),
1073
+ col("remote_user_id"),
1074
+ col("email_address"),
1075
+ ],
1076
+ (schema, "comment_test"): [
1077
+ pk("id", comment="id comment"),
1078
+ col("data", comment="data % comment"),
1079
+ col(
1080
+ "d2",
1081
+ comment=r"""Comment types type speedily ' " \ '' Fun!""",
1082
+ ),
1083
+ col("d3", comment="Comment\nwith\rescapes"),
1084
+ ],
1085
+ (schema, "no_constraints"): [col("data")],
1086
+ (schema, "local_table"): [pk("id"), col("data"), col("remote_id")],
1087
+ (schema, "remote_table"): [pk("id"), col("local_id"), col("data")],
1088
+ (schema, "remote_table_2"): [pk("id"), col("data")],
1089
+ (schema, "noncol_idx_test_nopk"): [col("q")],
1090
+ (schema, "noncol_idx_test_pk"): [pk("id"), col("q")],
1091
+ (schema, self.temp_table_name()): [
1092
+ pk("id"),
1093
+ col("name"),
1094
+ col("foo"),
1095
+ ],
1096
+ }
1097
+ res = self._resolve_kind(kind, tables, views, materialized)
1098
+ res = self._resolve_names(schema, scope, filter_names, res)
1099
+ return res
1100
+
1101
+ @property
1102
+ def _required_column_keys(self):
1103
+ return {"name", "type", "nullable", "default"}
1104
+
1105
+ def exp_pks(
1106
+ self,
1107
+ schema=None,
1108
+ scope=ObjectScope.ANY,
1109
+ kind=ObjectKind.ANY,
1110
+ filter_names=None,
1111
+ ):
1112
+ def pk(*cols, name=mock.ANY, comment=None):
1113
+ return {
1114
+ "constrained_columns": list(cols),
1115
+ "name": name,
1116
+ "comment": comment,
1117
+ }
1118
+
1119
+ empty = pk(name=None)
1120
+ if testing.requires.materialized_views_reflect_pk.enabled:
1121
+ materialized = {(schema, "dingalings_v"): pk("dingaling_id")}
1122
+ else:
1123
+ materialized = {(schema, "dingalings_v"): empty}
1124
+ views = {
1125
+ (schema, "email_addresses_v"): empty,
1126
+ (schema, "users_v"): empty,
1127
+ (schema, "user_tmp_v"): empty,
1128
+ }
1129
+ self._resolve_views(views, materialized)
1130
+ tables = {
1131
+ (schema, "users"): pk("user_id"),
1132
+ (schema, "dingalings"): pk("dingaling_id"),
1133
+ (schema, "email_addresses"): pk(
1134
+ "address_id", name="email_ad_pk", comment="ea pk comment"
1135
+ ),
1136
+ (schema, "comment_test"): pk("id"),
1137
+ (schema, "no_constraints"): empty,
1138
+ (schema, "local_table"): pk("id"),
1139
+ (schema, "remote_table"): pk("id"),
1140
+ (schema, "remote_table_2"): pk("id"),
1141
+ (schema, "noncol_idx_test_nopk"): empty,
1142
+ (schema, "noncol_idx_test_pk"): pk("id"),
1143
+ (schema, self.temp_table_name()): pk("id"),
1144
+ }
1145
+ if not testing.requires.reflects_pk_names.enabled:
1146
+ for val in tables.values():
1147
+ if val["name"] is not None:
1148
+ val["name"] = mock.ANY
1149
+ res = self._resolve_kind(kind, tables, views, materialized)
1150
+ res = self._resolve_names(schema, scope, filter_names, res)
1151
+ return res
1152
+
1153
+ @property
1154
+ def _required_pk_keys(self):
1155
+ return {"name", "constrained_columns"}
1156
+
1157
+ def exp_fks(
1158
+ self,
1159
+ schema=None,
1160
+ scope=ObjectScope.ANY,
1161
+ kind=ObjectKind.ANY,
1162
+ filter_names=None,
1163
+ ):
1164
+ class tt:
1165
+ def __eq__(self, other):
1166
+ return (
1167
+ other is None
1168
+ or config.db.dialect.default_schema_name == other
1169
+ )
1170
+
1171
+ def fk(
1172
+ cols,
1173
+ ref_col,
1174
+ ref_table,
1175
+ ref_schema=schema,
1176
+ name=mock.ANY,
1177
+ comment=None,
1178
+ ):
1179
+ return {
1180
+ "constrained_columns": cols,
1181
+ "referred_columns": ref_col,
1182
+ "name": name,
1183
+ "options": mock.ANY,
1184
+ "referred_schema": (
1185
+ ref_schema if ref_schema is not None else tt()
1186
+ ),
1187
+ "referred_table": ref_table,
1188
+ "comment": comment,
1189
+ }
1190
+
1191
+ materialized = {(schema, "dingalings_v"): []}
1192
+ views = {
1193
+ (schema, "email_addresses_v"): [],
1194
+ (schema, "users_v"): [],
1195
+ (schema, "user_tmp_v"): [],
1196
+ }
1197
+ self._resolve_views(views, materialized)
1198
+ tables = {
1199
+ (schema, "users"): [
1200
+ fk(["parent_user_id"], ["user_id"], "users", name="user_id_fk")
1201
+ ],
1202
+ (schema, "dingalings"): [
1203
+ fk(["id_user"], ["user_id"], "users"),
1204
+ fk(
1205
+ ["address_id"],
1206
+ ["address_id"],
1207
+ "email_addresses",
1208
+ name="zz_email_add_id_fg",
1209
+ comment="di fk comment",
1210
+ ),
1211
+ ],
1212
+ (schema, "email_addresses"): [
1213
+ fk(["remote_user_id"], ["user_id"], "users")
1214
+ ],
1215
+ (schema, "comment_test"): [],
1216
+ (schema, "no_constraints"): [],
1217
+ (schema, "local_table"): [
1218
+ fk(
1219
+ ["remote_id"],
1220
+ ["id"],
1221
+ "remote_table_2",
1222
+ ref_schema=config.test_schema,
1223
+ )
1224
+ ],
1225
+ (schema, "remote_table"): [
1226
+ fk(["local_id"], ["id"], "local_table", ref_schema=None)
1227
+ ],
1228
+ (schema, "remote_table_2"): [],
1229
+ (schema, "noncol_idx_test_nopk"): [],
1230
+ (schema, "noncol_idx_test_pk"): [],
1231
+ (schema, self.temp_table_name()): [],
1232
+ }
1233
+ if not testing.requires.self_referential_foreign_keys.enabled:
1234
+ tables[(schema, "users")].clear()
1235
+ if not testing.requires.named_constraints.enabled:
1236
+ for vals in tables.values():
1237
+ for val in vals:
1238
+ if val["name"] is not mock.ANY:
1239
+ val["name"] = mock.ANY
1240
+
1241
+ res = self._resolve_kind(kind, tables, views, materialized)
1242
+ res = self._resolve_names(schema, scope, filter_names, res)
1243
+ return res
1244
+
1245
+ @property
1246
+ def _required_fk_keys(self):
1247
+ return {
1248
+ "name",
1249
+ "constrained_columns",
1250
+ "referred_schema",
1251
+ "referred_table",
1252
+ "referred_columns",
1253
+ }
1254
+
1255
+ def exp_indexes(
1256
+ self,
1257
+ schema=None,
1258
+ scope=ObjectScope.ANY,
1259
+ kind=ObjectKind.ANY,
1260
+ filter_names=None,
1261
+ ):
1262
+ def idx(
1263
+ *cols,
1264
+ name,
1265
+ unique=False,
1266
+ column_sorting=None,
1267
+ duplicates=False,
1268
+ fk=False,
1269
+ ):
1270
+ fk_req = testing.requires.foreign_keys_reflect_as_index
1271
+ dup_req = testing.requires.unique_constraints_reflect_as_index
1272
+ sorting_expression = (
1273
+ testing.requires.reflect_indexes_with_ascdesc_as_expression
1274
+ )
1275
+
1276
+ if (fk and not fk_req.enabled) or (
1277
+ duplicates and not dup_req.enabled
1278
+ ):
1279
+ return ()
1280
+ res = {
1281
+ "unique": unique,
1282
+ "column_names": list(cols),
1283
+ "name": name,
1284
+ "dialect_options": mock.ANY,
1285
+ "include_columns": [],
1286
+ }
1287
+ if column_sorting:
1288
+ res["column_sorting"] = column_sorting
1289
+ if sorting_expression.enabled:
1290
+ res["expressions"] = orig = res["column_names"]
1291
+ res["column_names"] = [
1292
+ None if c in column_sorting else c for c in orig
1293
+ ]
1294
+
1295
+ if duplicates:
1296
+ res["duplicates_constraint"] = name
1297
+ return [res]
1298
+
1299
+ materialized = {(schema, "dingalings_v"): []}
1300
+ views = {
1301
+ (schema, "email_addresses_v"): [],
1302
+ (schema, "users_v"): [],
1303
+ (schema, "user_tmp_v"): [],
1304
+ }
1305
+ self._resolve_views(views, materialized)
1306
+ if materialized:
1307
+ materialized[(schema, "dingalings_v")].extend(
1308
+ idx("data", name="mat_index")
1309
+ )
1310
+ tables = {
1311
+ (schema, "users"): [
1312
+ *idx("parent_user_id", name="user_id_fk", fk=True),
1313
+ *idx("user_id", "test2", "test1", name="users_all_idx"),
1314
+ *idx("test1", "test2", name="users_t_idx", unique=True),
1315
+ ],
1316
+ (schema, "dingalings"): [
1317
+ *idx("data", name=mock.ANY, unique=True, duplicates=True),
1318
+ *idx("id_user", name=mock.ANY, fk=True),
1319
+ *idx(
1320
+ "address_id",
1321
+ "dingaling_id",
1322
+ name="zz_dingalings_multiple",
1323
+ unique=True,
1324
+ duplicates=True,
1325
+ ),
1326
+ ],
1327
+ (schema, "email_addresses"): [
1328
+ *idx("email_address", name=mock.ANY),
1329
+ *idx("remote_user_id", name=mock.ANY, fk=True),
1330
+ ],
1331
+ (schema, "comment_test"): [],
1332
+ (schema, "no_constraints"): [],
1333
+ (schema, "local_table"): [
1334
+ *idx("remote_id", name=mock.ANY, fk=True)
1335
+ ],
1336
+ (schema, "remote_table"): [
1337
+ *idx("local_id", name=mock.ANY, fk=True)
1338
+ ],
1339
+ (schema, "remote_table_2"): [],
1340
+ (schema, "noncol_idx_test_nopk"): [
1341
+ *idx(
1342
+ "q",
1343
+ name="noncol_idx_nopk",
1344
+ column_sorting={"q": ("desc",)},
1345
+ )
1346
+ ],
1347
+ (schema, "noncol_idx_test_pk"): [
1348
+ *idx(
1349
+ "q", name="noncol_idx_pk", column_sorting={"q": ("desc",)}
1350
+ )
1351
+ ],
1352
+ (schema, self.temp_table_name()): [
1353
+ *idx("foo", name="user_tmp_ix"),
1354
+ *idx(
1355
+ "name",
1356
+ name=f"user_tmp_uq_{config.ident}",
1357
+ duplicates=True,
1358
+ unique=True,
1359
+ ),
1360
+ ],
1361
+ }
1362
+ if (
1363
+ not testing.requires.indexes_with_ascdesc.enabled
1364
+ or not testing.requires.reflect_indexes_with_ascdesc.enabled
1365
+ ):
1366
+ tables[(schema, "noncol_idx_test_nopk")].clear()
1367
+ tables[(schema, "noncol_idx_test_pk")].clear()
1368
+ res = self._resolve_kind(kind, tables, views, materialized)
1369
+ res = self._resolve_names(schema, scope, filter_names, res)
1370
+ return res
1371
+
1372
+ @property
1373
+ def _required_index_keys(self):
1374
+ return {"name", "column_names", "unique"}
1375
+
1376
+ def exp_ucs(
1377
+ self,
1378
+ schema=None,
1379
+ scope=ObjectScope.ANY,
1380
+ kind=ObjectKind.ANY,
1381
+ filter_names=None,
1382
+ all_=False,
1383
+ ):
1384
+ def uc(
1385
+ *cols, name, duplicates_index=None, is_index=False, comment=None
1386
+ ):
1387
+ req = testing.requires.unique_index_reflect_as_unique_constraints
1388
+ if is_index and not req.enabled:
1389
+ return ()
1390
+ res = {
1391
+ "column_names": list(cols),
1392
+ "name": name,
1393
+ "comment": comment,
1394
+ }
1395
+ if duplicates_index:
1396
+ res["duplicates_index"] = duplicates_index
1397
+ return [res]
1398
+
1399
+ materialized = {(schema, "dingalings_v"): []}
1400
+ views = {
1401
+ (schema, "email_addresses_v"): [],
1402
+ (schema, "users_v"): [],
1403
+ (schema, "user_tmp_v"): [],
1404
+ }
1405
+ self._resolve_views(views, materialized)
1406
+ tables = {
1407
+ (schema, "users"): [
1408
+ *uc(
1409
+ "test1",
1410
+ "test2",
1411
+ name="users_t_idx",
1412
+ duplicates_index="users_t_idx",
1413
+ is_index=True,
1414
+ )
1415
+ ],
1416
+ (schema, "dingalings"): [
1417
+ *uc("data", name=mock.ANY, duplicates_index=mock.ANY),
1418
+ *uc(
1419
+ "address_id",
1420
+ "dingaling_id",
1421
+ name="zz_dingalings_multiple",
1422
+ duplicates_index="zz_dingalings_multiple",
1423
+ comment="di unique comment",
1424
+ ),
1425
+ ],
1426
+ (schema, "email_addresses"): [],
1427
+ (schema, "comment_test"): [],
1428
+ (schema, "no_constraints"): [],
1429
+ (schema, "local_table"): [],
1430
+ (schema, "remote_table"): [],
1431
+ (schema, "remote_table_2"): [],
1432
+ (schema, "noncol_idx_test_nopk"): [],
1433
+ (schema, "noncol_idx_test_pk"): [],
1434
+ (schema, self.temp_table_name()): [
1435
+ *uc("name", name=f"user_tmp_uq_{config.ident}")
1436
+ ],
1437
+ }
1438
+ if all_:
1439
+ return {**materialized, **views, **tables}
1440
+ else:
1441
+ res = self._resolve_kind(kind, tables, views, materialized)
1442
+ res = self._resolve_names(schema, scope, filter_names, res)
1443
+ return res
1444
+
1445
+ @property
1446
+ def _required_unique_cst_keys(self):
1447
+ return {"name", "column_names"}
1448
+
1449
+ def exp_ccs(
1450
+ self,
1451
+ schema=None,
1452
+ scope=ObjectScope.ANY,
1453
+ kind=ObjectKind.ANY,
1454
+ filter_names=None,
1455
+ ):
1456
+ class tt(str):
1457
+ def __eq__(self, other):
1458
+ res = (
1459
+ other.lower()
1460
+ .replace("(", "")
1461
+ .replace(")", "")
1462
+ .replace("`", "")
1463
+ )
1464
+ return self in res
1465
+
1466
+ def cc(text, name, comment=None):
1467
+ return {"sqltext": tt(text), "name": name, "comment": comment}
1468
+
1469
+ # print({1: "test2 > (0)::double precision"} == {1: tt("test2 > 0")})
1470
+ # assert 0
1471
+ materialized = {(schema, "dingalings_v"): []}
1472
+ views = {
1473
+ (schema, "email_addresses_v"): [],
1474
+ (schema, "users_v"): [],
1475
+ (schema, "user_tmp_v"): [],
1476
+ }
1477
+ self._resolve_views(views, materialized)
1478
+ tables = {
1479
+ (schema, "users"): [
1480
+ cc("test2 <= 1000", mock.ANY),
1481
+ cc(
1482
+ "test2 > 0",
1483
+ "zz_test2_gt_zero",
1484
+ comment="users check constraint",
1485
+ ),
1486
+ ],
1487
+ (schema, "dingalings"): [
1488
+ cc(
1489
+ "address_id > 0 and address_id < 1000",
1490
+ name="address_id_gt_zero",
1491
+ ),
1492
+ ],
1493
+ (schema, "email_addresses"): [],
1494
+ (schema, "comment_test"): [],
1495
+ (schema, "no_constraints"): [],
1496
+ (schema, "local_table"): [],
1497
+ (schema, "remote_table"): [],
1498
+ (schema, "remote_table_2"): [],
1499
+ (schema, "noncol_idx_test_nopk"): [],
1500
+ (schema, "noncol_idx_test_pk"): [],
1501
+ (schema, self.temp_table_name()): [],
1502
+ }
1503
+ res = self._resolve_kind(kind, tables, views, materialized)
1504
+ res = self._resolve_names(schema, scope, filter_names, res)
1505
+ return res
1506
+
1507
+ @property
1508
+ def _required_cc_keys(self):
1509
+ return {"name", "sqltext"}
1510
+
1511
+ @testing.requires.schema_reflection
1512
+ def test_get_schema_names(self, connection):
1513
+ insp = inspect(connection)
1514
+
1515
+ is_true(testing.config.test_schema in insp.get_schema_names())
1516
+
1517
+ @testing.requires.schema_reflection
1518
+ def test_has_schema(self, connection):
1519
+ insp = inspect(connection)
1520
+
1521
+ is_true(insp.has_schema(testing.config.test_schema))
1522
+ is_false(insp.has_schema("sa_fake_schema_foo"))
1523
+
1524
+ @testing.requires.schema_reflection
1525
+ def test_get_schema_names_w_translate_map(self, connection):
1526
+ """test #7300"""
1527
+
1528
+ connection = connection.execution_options(
1529
+ schema_translate_map={
1530
+ "foo": "bar",
1531
+ BLANK_SCHEMA: testing.config.test_schema,
1532
+ }
1533
+ )
1534
+ insp = inspect(connection)
1535
+
1536
+ is_true(testing.config.test_schema in insp.get_schema_names())
1537
+
1538
+ @testing.requires.schema_reflection
1539
+ def test_has_schema_w_translate_map(self, connection):
1540
+ connection = connection.execution_options(
1541
+ schema_translate_map={
1542
+ "foo": "bar",
1543
+ BLANK_SCHEMA: testing.config.test_schema,
1544
+ }
1545
+ )
1546
+ insp = inspect(connection)
1547
+
1548
+ is_true(insp.has_schema(testing.config.test_schema))
1549
+ is_false(insp.has_schema("sa_fake_schema_foo"))
1550
+
1551
+ @testing.requires.schema_reflection
1552
+ @testing.requires.schema_create_delete
1553
+ def test_schema_cache(self, connection):
1554
+ insp = inspect(connection)
1555
+
1556
+ is_false("foo_bar" in insp.get_schema_names())
1557
+ is_false(insp.has_schema("foo_bar"))
1558
+ connection.execute(DDL("CREATE SCHEMA foo_bar"))
1559
+ try:
1560
+ is_false("foo_bar" in insp.get_schema_names())
1561
+ is_false(insp.has_schema("foo_bar"))
1562
+ insp.clear_cache()
1563
+ is_true("foo_bar" in insp.get_schema_names())
1564
+ is_true(insp.has_schema("foo_bar"))
1565
+ finally:
1566
+ connection.execute(DDL("DROP SCHEMA foo_bar"))
1567
+
1568
+ @testing.requires.schema_reflection
1569
+ def test_dialect_initialize(self):
1570
+ engine = engines.testing_engine()
1571
+ inspect(engine)
1572
+ assert hasattr(engine.dialect, "default_schema_name")
1573
+
1574
+ @testing.requires.schema_reflection
1575
+ def test_get_default_schema_name(self, connection):
1576
+ insp = inspect(connection)
1577
+ eq_(insp.default_schema_name, connection.dialect.default_schema_name)
1578
+
1579
+ @testing.combinations(
1580
+ None,
1581
+ ("foreign_key", testing.requires.foreign_key_constraint_reflection),
1582
+ argnames="order_by",
1583
+ )
1584
+ @testing.combinations(
1585
+ (True, testing.requires.schemas), False, argnames="use_schema"
1586
+ )
1587
+ def test_get_table_names(self, connection, order_by, use_schema):
1588
+ if use_schema:
1589
+ schema = config.test_schema
1590
+ else:
1591
+ schema = None
1592
+
1593
+ _ignore_tables = {
1594
+ "comment_test",
1595
+ "noncol_idx_test_pk",
1596
+ "noncol_idx_test_nopk",
1597
+ "local_table",
1598
+ "remote_table",
1599
+ "remote_table_2",
1600
+ "no_constraints",
1601
+ }
1602
+
1603
+ insp = inspect(connection)
1604
+
1605
+ if order_by:
1606
+ tables = [
1607
+ rec[0]
1608
+ for rec in insp.get_sorted_table_and_fkc_names(schema)
1609
+ if rec[0]
1610
+ ]
1611
+ else:
1612
+ tables = insp.get_table_names(schema)
1613
+ table_names = [t for t in tables if t not in _ignore_tables]
1614
+
1615
+ if order_by == "foreign_key":
1616
+ answer = ["users", "email_addresses", "dingalings"]
1617
+ eq_(table_names, answer)
1618
+ else:
1619
+ answer = ["dingalings", "email_addresses", "users"]
1620
+ eq_(sorted(table_names), answer)
1621
+
1622
+ @testing.combinations(
1623
+ (True, testing.requires.schemas), False, argnames="use_schema"
1624
+ )
1625
+ def test_get_view_names(self, connection, use_schema):
1626
+ insp = inspect(connection)
1627
+ if use_schema:
1628
+ schema = config.test_schema
1629
+ else:
1630
+ schema = None
1631
+ table_names = insp.get_view_names(schema)
1632
+ if testing.requires.materialized_views.enabled:
1633
+ eq_(sorted(table_names), ["email_addresses_v", "users_v"])
1634
+ eq_(insp.get_materialized_view_names(schema), ["dingalings_v"])
1635
+ else:
1636
+ answer = ["dingalings_v", "email_addresses_v", "users_v"]
1637
+ eq_(sorted(table_names), answer)
1638
+
1639
+ @testing.requires.temp_table_names
1640
+ def test_get_temp_table_names(self, connection):
1641
+ insp = inspect(connection)
1642
+ temp_table_names = insp.get_temp_table_names()
1643
+ eq_(sorted(temp_table_names), [f"user_tmp_{config.ident}"])
1644
+
1645
+ @testing.requires.view_reflection
1646
+ @testing.requires.temporary_views
1647
+ def test_get_temp_view_names(self, connection):
1648
+ insp = inspect(connection)
1649
+ temp_table_names = insp.get_temp_view_names()
1650
+ eq_(sorted(temp_table_names), ["user_tmp_v"])
1651
+
1652
+ @testing.requires.comment_reflection
1653
+ def test_get_comments(self, connection):
1654
+ self._test_get_comments(connection)
1655
+
1656
+ @testing.requires.comment_reflection
1657
+ @testing.requires.schemas
1658
+ def test_get_comments_with_schema(self, connection):
1659
+ self._test_get_comments(connection, testing.config.test_schema)
1660
+
1661
+ def _test_get_comments(self, connection, schema=None):
1662
+ insp = inspect(connection)
1663
+ exp = self.exp_comments(schema=schema)
1664
+ eq_(
1665
+ insp.get_table_comment("comment_test", schema=schema),
1666
+ exp[(schema, "comment_test")],
1667
+ )
1668
+
1669
+ eq_(
1670
+ insp.get_table_comment("users", schema=schema),
1671
+ exp[(schema, "users")],
1672
+ )
1673
+
1674
+ eq_(
1675
+ insp.get_table_comment("comment_test", schema=schema),
1676
+ exp[(schema, "comment_test")],
1677
+ )
1678
+
1679
+ no_cst = self.tables.no_constraints.name
1680
+ eq_(
1681
+ insp.get_table_comment(no_cst, schema=schema),
1682
+ exp[(schema, no_cst)],
1683
+ )
1684
+
1685
+ @testing.combinations(
1686
+ (False, False),
1687
+ (False, True, testing.requires.schemas),
1688
+ (True, False, testing.requires.view_reflection),
1689
+ (
1690
+ True,
1691
+ True,
1692
+ testing.requires.schemas + testing.requires.view_reflection,
1693
+ ),
1694
+ argnames="use_views,use_schema",
1695
+ )
1696
+ def test_get_columns(self, connection, use_views, use_schema):
1697
+ if use_schema:
1698
+ schema = config.test_schema
1699
+ else:
1700
+ schema = None
1701
+
1702
+ users, addresses = (self.tables.users, self.tables.email_addresses)
1703
+ if use_views:
1704
+ table_names = ["users_v", "email_addresses_v", "dingalings_v"]
1705
+ else:
1706
+ table_names = ["users", "email_addresses"]
1707
+
1708
+ insp = inspect(connection)
1709
+ for table_name, table in zip(table_names, (users, addresses)):
1710
+ schema_name = schema
1711
+ cols = insp.get_columns(table_name, schema=schema_name)
1712
+ is_true(len(cols) > 0, len(cols))
1713
+
1714
+ # should be in order
1715
+
1716
+ for i, col in enumerate(table.columns):
1717
+ eq_(col.name, cols[i]["name"])
1718
+ ctype = cols[i]["type"].__class__
1719
+ ctype_def = col.type
1720
+ if isinstance(ctype_def, sa.types.TypeEngine):
1721
+ ctype_def = ctype_def.__class__
1722
+
1723
+ # Oracle returns Date for DateTime.
1724
+
1725
+ if testing.against("oracle") and ctype_def in (
1726
+ sql_types.Date,
1727
+ sql_types.DateTime,
1728
+ ):
1729
+ ctype_def = sql_types.Date
1730
+
1731
+ # assert that the desired type and return type share
1732
+ # a base within one of the generic types.
1733
+
1734
+ is_true(
1735
+ len(
1736
+ set(ctype.__mro__)
1737
+ .intersection(ctype_def.__mro__)
1738
+ .intersection(
1739
+ [
1740
+ sql_types.Integer,
1741
+ sql_types.Numeric,
1742
+ sql_types.Float,
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
+ )