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