SQLAlchemy 2.1.0b2__cp313-cp313t-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. sqlalchemy/__init__.py +298 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +171 -0
  4. sqlalchemy/connectors/asyncio.py +476 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/dialects/__init__.py +62 -0
  7. sqlalchemy/dialects/_typing.py +30 -0
  8. sqlalchemy/dialects/mssql/__init__.py +89 -0
  9. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  10. sqlalchemy/dialects/mssql/base.py +4166 -0
  11. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  12. sqlalchemy/dialects/mssql/json.py +140 -0
  13. sqlalchemy/dialects/mssql/mssqlpython.py +220 -0
  14. sqlalchemy/dialects/mssql/provision.py +196 -0
  15. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  16. sqlalchemy/dialects/mssql/pyodbc.py +698 -0
  17. sqlalchemy/dialects/mysql/__init__.py +106 -0
  18. sqlalchemy/dialects/mysql/_mariadb_shim.py +312 -0
  19. sqlalchemy/dialects/mysql/aiomysql.py +226 -0
  20. sqlalchemy/dialects/mysql/asyncmy.py +214 -0
  21. sqlalchemy/dialects/mysql/base.py +3877 -0
  22. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  23. sqlalchemy/dialects/mysql/dml.py +279 -0
  24. sqlalchemy/dialects/mysql/enumerated.py +277 -0
  25. sqlalchemy/dialects/mysql/expression.py +146 -0
  26. sqlalchemy/dialects/mysql/json.py +92 -0
  27. sqlalchemy/dialects/mysql/mariadb.py +67 -0
  28. sqlalchemy/dialects/mysql/mariadbconnector.py +330 -0
  29. sqlalchemy/dialects/mysql/mysqlconnector.py +296 -0
  30. sqlalchemy/dialects/mysql/mysqldb.py +312 -0
  31. sqlalchemy/dialects/mysql/provision.py +153 -0
  32. sqlalchemy/dialects/mysql/pymysql.py +157 -0
  33. sqlalchemy/dialects/mysql/pyodbc.py +156 -0
  34. sqlalchemy/dialects/mysql/reflection.py +724 -0
  35. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  36. sqlalchemy/dialects/mysql/types.py +845 -0
  37. sqlalchemy/dialects/oracle/__init__.py +85 -0
  38. sqlalchemy/dialects/oracle/base.py +3977 -0
  39. sqlalchemy/dialects/oracle/cx_oracle.py +1601 -0
  40. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  41. sqlalchemy/dialects/oracle/json.py +158 -0
  42. sqlalchemy/dialects/oracle/oracledb.py +909 -0
  43. sqlalchemy/dialects/oracle/provision.py +288 -0
  44. sqlalchemy/dialects/oracle/types.py +367 -0
  45. sqlalchemy/dialects/oracle/vector.py +368 -0
  46. sqlalchemy/dialects/postgresql/__init__.py +171 -0
  47. sqlalchemy/dialects/postgresql/_psycopg_common.py +229 -0
  48. sqlalchemy/dialects/postgresql/array.py +534 -0
  49. sqlalchemy/dialects/postgresql/asyncpg.py +1323 -0
  50. sqlalchemy/dialects/postgresql/base.py +5789 -0
  51. sqlalchemy/dialects/postgresql/bitstring.py +327 -0
  52. sqlalchemy/dialects/postgresql/dml.py +360 -0
  53. sqlalchemy/dialects/postgresql/ext.py +593 -0
  54. sqlalchemy/dialects/postgresql/hstore.py +423 -0
  55. sqlalchemy/dialects/postgresql/json.py +408 -0
  56. sqlalchemy/dialects/postgresql/named_types.py +521 -0
  57. sqlalchemy/dialects/postgresql/operators.py +130 -0
  58. sqlalchemy/dialects/postgresql/pg8000.py +670 -0
  59. sqlalchemy/dialects/postgresql/pg_catalog.py +344 -0
  60. sqlalchemy/dialects/postgresql/provision.py +184 -0
  61. sqlalchemy/dialects/postgresql/psycopg.py +799 -0
  62. sqlalchemy/dialects/postgresql/psycopg2.py +860 -0
  63. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  64. sqlalchemy/dialects/postgresql/ranges.py +1002 -0
  65. sqlalchemy/dialects/postgresql/types.py +388 -0
  66. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  67. sqlalchemy/dialects/sqlite/aiosqlite.py +321 -0
  68. sqlalchemy/dialects/sqlite/base.py +3063 -0
  69. sqlalchemy/dialects/sqlite/dml.py +279 -0
  70. sqlalchemy/dialects/sqlite/json.py +100 -0
  71. sqlalchemy/dialects/sqlite/provision.py +229 -0
  72. sqlalchemy/dialects/sqlite/pysqlcipher.py +161 -0
  73. sqlalchemy/dialects/sqlite/pysqlite.py +754 -0
  74. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  75. sqlalchemy/engine/__init__.py +62 -0
  76. sqlalchemy/engine/_processors_cy.cp313t-win_arm64.pyd +0 -0
  77. sqlalchemy/engine/_processors_cy.py +92 -0
  78. sqlalchemy/engine/_result_cy.cp313t-win_arm64.pyd +0 -0
  79. sqlalchemy/engine/_result_cy.py +633 -0
  80. sqlalchemy/engine/_row_cy.cp313t-win_arm64.pyd +0 -0
  81. sqlalchemy/engine/_row_cy.py +232 -0
  82. sqlalchemy/engine/_util_cy.cp313t-win_arm64.pyd +0 -0
  83. sqlalchemy/engine/_util_cy.py +136 -0
  84. sqlalchemy/engine/base.py +3354 -0
  85. sqlalchemy/engine/characteristics.py +155 -0
  86. sqlalchemy/engine/create.py +877 -0
  87. sqlalchemy/engine/cursor.py +2421 -0
  88. sqlalchemy/engine/default.py +2402 -0
  89. sqlalchemy/engine/events.py +965 -0
  90. sqlalchemy/engine/interfaces.py +3495 -0
  91. sqlalchemy/engine/mock.py +134 -0
  92. sqlalchemy/engine/processors.py +82 -0
  93. sqlalchemy/engine/reflection.py +2100 -0
  94. sqlalchemy/engine/result.py +1966 -0
  95. sqlalchemy/engine/row.py +397 -0
  96. sqlalchemy/engine/strategies.py +16 -0
  97. sqlalchemy/engine/url.py +922 -0
  98. sqlalchemy/engine/util.py +156 -0
  99. sqlalchemy/event/__init__.py +26 -0
  100. sqlalchemy/event/api.py +220 -0
  101. sqlalchemy/event/attr.py +674 -0
  102. sqlalchemy/event/base.py +472 -0
  103. sqlalchemy/event/legacy.py +258 -0
  104. sqlalchemy/event/registry.py +390 -0
  105. sqlalchemy/events.py +17 -0
  106. sqlalchemy/exc.py +922 -0
  107. sqlalchemy/ext/__init__.py +11 -0
  108. sqlalchemy/ext/associationproxy.py +2072 -0
  109. sqlalchemy/ext/asyncio/__init__.py +29 -0
  110. sqlalchemy/ext/asyncio/base.py +281 -0
  111. sqlalchemy/ext/asyncio/engine.py +1487 -0
  112. sqlalchemy/ext/asyncio/exc.py +21 -0
  113. sqlalchemy/ext/asyncio/result.py +994 -0
  114. sqlalchemy/ext/asyncio/scoping.py +1679 -0
  115. sqlalchemy/ext/asyncio/session.py +2007 -0
  116. sqlalchemy/ext/automap.py +1701 -0
  117. sqlalchemy/ext/baked.py +559 -0
  118. sqlalchemy/ext/compiler.py +600 -0
  119. sqlalchemy/ext/declarative/__init__.py +65 -0
  120. sqlalchemy/ext/declarative/extensions.py +560 -0
  121. sqlalchemy/ext/horizontal_shard.py +481 -0
  122. sqlalchemy/ext/hybrid.py +1877 -0
  123. sqlalchemy/ext/indexable.py +364 -0
  124. sqlalchemy/ext/instrumentation.py +450 -0
  125. sqlalchemy/ext/mutable.py +1081 -0
  126. sqlalchemy/ext/orderinglist.py +439 -0
  127. sqlalchemy/ext/serializer.py +185 -0
  128. sqlalchemy/future/__init__.py +16 -0
  129. sqlalchemy/future/engine.py +15 -0
  130. sqlalchemy/inspection.py +174 -0
  131. sqlalchemy/log.py +283 -0
  132. sqlalchemy/orm/__init__.py +176 -0
  133. sqlalchemy/orm/_orm_constructors.py +2694 -0
  134. sqlalchemy/orm/_typing.py +179 -0
  135. sqlalchemy/orm/attributes.py +2868 -0
  136. sqlalchemy/orm/base.py +976 -0
  137. sqlalchemy/orm/bulk_persistence.py +2152 -0
  138. sqlalchemy/orm/clsregistry.py +582 -0
  139. sqlalchemy/orm/collections.py +1568 -0
  140. sqlalchemy/orm/context.py +3471 -0
  141. sqlalchemy/orm/decl_api.py +2280 -0
  142. sqlalchemy/orm/decl_base.py +2309 -0
  143. sqlalchemy/orm/dependency.py +1306 -0
  144. sqlalchemy/orm/descriptor_props.py +1183 -0
  145. sqlalchemy/orm/dynamic.py +307 -0
  146. sqlalchemy/orm/evaluator.py +379 -0
  147. sqlalchemy/orm/events.py +3386 -0
  148. sqlalchemy/orm/exc.py +237 -0
  149. sqlalchemy/orm/identity.py +302 -0
  150. sqlalchemy/orm/instrumentation.py +746 -0
  151. sqlalchemy/orm/interfaces.py +1589 -0
  152. sqlalchemy/orm/loading.py +1684 -0
  153. sqlalchemy/orm/mapped_collection.py +557 -0
  154. sqlalchemy/orm/mapper.py +4411 -0
  155. sqlalchemy/orm/path_registry.py +829 -0
  156. sqlalchemy/orm/persistence.py +1789 -0
  157. sqlalchemy/orm/properties.py +973 -0
  158. sqlalchemy/orm/query.py +3528 -0
  159. sqlalchemy/orm/relationships.py +3570 -0
  160. sqlalchemy/orm/scoping.py +2232 -0
  161. sqlalchemy/orm/session.py +5403 -0
  162. sqlalchemy/orm/state.py +1175 -0
  163. sqlalchemy/orm/state_changes.py +196 -0
  164. sqlalchemy/orm/strategies.py +3492 -0
  165. sqlalchemy/orm/strategy_options.py +2562 -0
  166. sqlalchemy/orm/sync.py +164 -0
  167. sqlalchemy/orm/unitofwork.py +798 -0
  168. sqlalchemy/orm/util.py +2438 -0
  169. sqlalchemy/orm/writeonly.py +694 -0
  170. sqlalchemy/pool/__init__.py +41 -0
  171. sqlalchemy/pool/base.py +1522 -0
  172. sqlalchemy/pool/events.py +375 -0
  173. sqlalchemy/pool/impl.py +582 -0
  174. sqlalchemy/py.typed +0 -0
  175. sqlalchemy/schema.py +74 -0
  176. sqlalchemy/sql/__init__.py +156 -0
  177. sqlalchemy/sql/_annotated_cols.py +397 -0
  178. sqlalchemy/sql/_dml_constructors.py +132 -0
  179. sqlalchemy/sql/_elements_constructors.py +2164 -0
  180. sqlalchemy/sql/_orm_types.py +20 -0
  181. sqlalchemy/sql/_selectable_constructors.py +840 -0
  182. sqlalchemy/sql/_typing.py +487 -0
  183. sqlalchemy/sql/_util_cy.cp313t-win_arm64.pyd +0 -0
  184. sqlalchemy/sql/_util_cy.py +127 -0
  185. sqlalchemy/sql/annotation.py +590 -0
  186. sqlalchemy/sql/base.py +2699 -0
  187. sqlalchemy/sql/cache_key.py +1066 -0
  188. sqlalchemy/sql/coercions.py +1373 -0
  189. sqlalchemy/sql/compiler.py +8327 -0
  190. sqlalchemy/sql/crud.py +1815 -0
  191. sqlalchemy/sql/ddl.py +1928 -0
  192. sqlalchemy/sql/default_comparator.py +654 -0
  193. sqlalchemy/sql/dml.py +1977 -0
  194. sqlalchemy/sql/elements.py +6033 -0
  195. sqlalchemy/sql/events.py +458 -0
  196. sqlalchemy/sql/expression.py +172 -0
  197. sqlalchemy/sql/functions.py +2305 -0
  198. sqlalchemy/sql/lambdas.py +1443 -0
  199. sqlalchemy/sql/naming.py +209 -0
  200. sqlalchemy/sql/operators.py +2897 -0
  201. sqlalchemy/sql/roles.py +332 -0
  202. sqlalchemy/sql/schema.py +6703 -0
  203. sqlalchemy/sql/selectable.py +7553 -0
  204. sqlalchemy/sql/sqltypes.py +4093 -0
  205. sqlalchemy/sql/traversals.py +1042 -0
  206. sqlalchemy/sql/type_api.py +2446 -0
  207. sqlalchemy/sql/util.py +1495 -0
  208. sqlalchemy/sql/visitors.py +1157 -0
  209. sqlalchemy/testing/__init__.py +96 -0
  210. sqlalchemy/testing/assertions.py +1007 -0
  211. sqlalchemy/testing/assertsql.py +519 -0
  212. sqlalchemy/testing/asyncio.py +128 -0
  213. sqlalchemy/testing/config.py +440 -0
  214. sqlalchemy/testing/engines.py +483 -0
  215. sqlalchemy/testing/entities.py +117 -0
  216. sqlalchemy/testing/exclusions.py +476 -0
  217. sqlalchemy/testing/fixtures/__init__.py +30 -0
  218. sqlalchemy/testing/fixtures/base.py +384 -0
  219. sqlalchemy/testing/fixtures/mypy.py +247 -0
  220. sqlalchemy/testing/fixtures/orm.py +227 -0
  221. sqlalchemy/testing/fixtures/sql.py +538 -0
  222. sqlalchemy/testing/pickleable.py +155 -0
  223. sqlalchemy/testing/plugin/__init__.py +6 -0
  224. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  225. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  226. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  227. sqlalchemy/testing/profiling.py +329 -0
  228. sqlalchemy/testing/provision.py +613 -0
  229. sqlalchemy/testing/requirements.py +1978 -0
  230. sqlalchemy/testing/schema.py +198 -0
  231. sqlalchemy/testing/suite/__init__.py +19 -0
  232. sqlalchemy/testing/suite/test_cte.py +237 -0
  233. sqlalchemy/testing/suite/test_ddl.py +420 -0
  234. sqlalchemy/testing/suite/test_dialect.py +776 -0
  235. sqlalchemy/testing/suite/test_insert.py +630 -0
  236. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  237. sqlalchemy/testing/suite/test_results.py +660 -0
  238. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  239. sqlalchemy/testing/suite/test_select.py +2112 -0
  240. sqlalchemy/testing/suite/test_sequence.py +317 -0
  241. sqlalchemy/testing/suite/test_table_via_select.py +686 -0
  242. sqlalchemy/testing/suite/test_types.py +2271 -0
  243. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  244. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  245. sqlalchemy/testing/util.py +535 -0
  246. sqlalchemy/testing/warnings.py +52 -0
  247. sqlalchemy/types.py +76 -0
  248. sqlalchemy/util/__init__.py +158 -0
  249. sqlalchemy/util/_collections.py +688 -0
  250. sqlalchemy/util/_collections_cy.cp313t-win_arm64.pyd +0 -0
  251. sqlalchemy/util/_collections_cy.pxd +8 -0
  252. sqlalchemy/util/_collections_cy.py +516 -0
  253. sqlalchemy/util/_has_cython.py +46 -0
  254. sqlalchemy/util/_immutabledict_cy.cp313t-win_arm64.pyd +0 -0
  255. sqlalchemy/util/_immutabledict_cy.py +240 -0
  256. sqlalchemy/util/compat.py +299 -0
  257. sqlalchemy/util/concurrency.py +322 -0
  258. sqlalchemy/util/cython.py +79 -0
  259. sqlalchemy/util/deprecations.py +401 -0
  260. sqlalchemy/util/langhelpers.py +2320 -0
  261. sqlalchemy/util/preloaded.py +152 -0
  262. sqlalchemy/util/queue.py +304 -0
  263. sqlalchemy/util/tool_support.py +201 -0
  264. sqlalchemy/util/topological.py +120 -0
  265. sqlalchemy/util/typing.py +711 -0
  266. sqlalchemy-2.1.0b2.dist-info/METADATA +269 -0
  267. sqlalchemy-2.1.0b2.dist-info/RECORD +270 -0
  268. sqlalchemy-2.1.0b2.dist-info/WHEEL +5 -0
  269. sqlalchemy-2.1.0b2.dist-info/licenses/LICENSE +19 -0
  270. sqlalchemy-2.1.0b2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,686 @@
1
+ # testing/suite/test_table_via_select.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
+ from .. import fixtures
10
+ from ..assertions import eq_
11
+ from ..provision import get_temp_table_name
12
+ from ... import bindparam
13
+ from ... import Column
14
+ from ... import func
15
+ from ... import inspect
16
+ from ... import Integer
17
+ from ... import literal
18
+ from ... import MetaData
19
+ from ... import select
20
+ from ... import String
21
+ from ... import testing
22
+ from ...schema import CreateTableAs
23
+ from ...schema import CreateView
24
+ from ...schema import DropTable
25
+ from ...schema import DropView
26
+ from ...schema import Table
27
+ from ...testing import config
28
+
29
+
30
+ class TableViaSelectTest(fixtures.TablesTest):
31
+ __sparse_driver_backend__ = True
32
+
33
+ @classmethod
34
+ def temp_table_name(cls):
35
+ return get_temp_table_name(
36
+ config, config.db, f"user_tmp_{config.ident}"
37
+ )
38
+
39
+ @classmethod
40
+ def temp_view_name(cls):
41
+ return get_temp_table_name(
42
+ config, config.db, f"user_tmp_view_{config.ident}"
43
+ )
44
+
45
+ @classmethod
46
+ def define_tables(cls, metadata):
47
+ Table(
48
+ "source_table",
49
+ metadata,
50
+ Column("id", Integer, primary_key=True, autoincrement=False),
51
+ Column("name", String(50)),
52
+ Column("value", Integer),
53
+ )
54
+ Table("a", metadata, Column("id", Integer))
55
+ Table("b", metadata, Column("id", Integer))
56
+
57
+ if testing.requires.schemas.enabled:
58
+ Table(
59
+ "source_table_s",
60
+ metadata,
61
+ Column("id", Integer, primary_key=True, autoincrement=False),
62
+ Column("name", String(50)),
63
+ Column("value", Integer),
64
+ schema=config.test_schema,
65
+ )
66
+
67
+ @classmethod
68
+ def insert_data(cls, connection):
69
+ table = cls.tables.source_table
70
+ connection.execute(
71
+ table.insert(),
72
+ [
73
+ {"id": 1, "name": "alice", "value": 100},
74
+ {"id": 2, "name": "bob", "value": 200},
75
+ {"id": 3, "name": "charlie", "value": 300},
76
+ ],
77
+ )
78
+
79
+ a = cls.tables.a
80
+ b = cls.tables.b
81
+
82
+ connection.execute(a.insert(), [{"id": v} for v in [1, 3]])
83
+ connection.execute(b.insert(), [{"id": v} for v in [2, 4]])
84
+
85
+ if testing.requires.schemas.enabled:
86
+ table = cls.tables[f"{config.test_schema}.source_table_s"]
87
+ connection.execute(
88
+ table.insert(),
89
+ [
90
+ {"id": 1, "name": "alice", "value": 100},
91
+ {"id": 2, "name": "bob", "value": 200},
92
+ {"id": 3, "name": "charlie", "value": 300},
93
+ ],
94
+ )
95
+
96
+ @testing.fixture(scope="function", autouse=True)
97
+ def drop_dest_table(self, connection):
98
+ for schema in None, config.test_schema:
99
+ for name in ("dest_table", self.temp_table_name()):
100
+ if inspect(connection).has_table(name, schema=schema):
101
+ connection.execute(
102
+ DropTable(Table(name, MetaData(), schema=schema))
103
+ )
104
+ for name in ("dest_view", self.temp_view_name()):
105
+ if inspect(connection).has_table(name, schema=schema):
106
+ connection.execute(
107
+ DropView(Table(name, MetaData(), schema=schema))
108
+ )
109
+ connection.commit()
110
+
111
+ @testing.combinations(
112
+ ("plain", False, False, False),
113
+ (
114
+ "use_temp",
115
+ False,
116
+ True,
117
+ False,
118
+ testing.requires.create_temp_table_as,
119
+ ),
120
+ ("use_schema", True, False, False, testing.requires.schemas),
121
+ ("plain", False, False, False),
122
+ ("use_temp", False, True, True, testing.requires.temporary_views),
123
+ ("use_schema", True, False, True, testing.requires.schemas),
124
+ argnames="use_schemas,use_temp,use_view",
125
+ id_="iaaa",
126
+ )
127
+ def test_without_metadata(
128
+ self, connection, use_temp, use_schemas, use_view
129
+ ):
130
+ source_table = self.tables.source_table
131
+
132
+ if not use_view:
133
+ tablename = self.temp_table_name() if use_temp else "dest_table"
134
+ stmt = CreateTableAs(
135
+ select(source_table.c.id, source_table.c.name).where(
136
+ source_table.c.value > 100
137
+ ),
138
+ tablename,
139
+ temporary=bool(use_temp),
140
+ schema=config.test_schema if use_schemas else None,
141
+ )
142
+ else:
143
+ if use_schemas:
144
+ source_table = self.tables[
145
+ f"{config.test_schema}.source_table_s"
146
+ ]
147
+
148
+ tablename = self.temp_view_name() if use_temp else "dest_view"
149
+ stmt = CreateView(
150
+ select(source_table.c.id, source_table.c.name).where(
151
+ source_table.c.value > 100
152
+ ),
153
+ tablename,
154
+ temporary=bool(use_temp),
155
+ schema=config.test_schema if use_schemas else None,
156
+ )
157
+
158
+ connection.execute(stmt)
159
+
160
+ # Verify we can SELECT from the generated table
161
+ dest = stmt.table
162
+ result = connection.execute(
163
+ select(dest.c.id, dest.c.name).order_by(dest.c.id)
164
+ ).fetchall()
165
+
166
+ eq_(result, [(2, "bob"), (3, "charlie")])
167
+
168
+ # Verify reflection works
169
+ insp = inspect(connection)
170
+ cols = insp.get_columns(
171
+ tablename,
172
+ schema=config.test_schema if use_schemas else None,
173
+ )
174
+
175
+ eq_(len(cols), 2)
176
+ eq_(cols[0]["name"], "id")
177
+ eq_(cols[1]["name"], "name")
178
+
179
+ # Verify type affinity
180
+ eq_(cols[0]["type"]._type_affinity, Integer)
181
+ eq_(cols[1]["type"]._type_affinity, String)
182
+
183
+ @testing.variation(
184
+ "table_type",
185
+ [
186
+ ("create_table_as", testing.requires.create_table_as),
187
+ ("select_into", testing.requires.create_table_as),
188
+ ("create_view", testing.requires.views),
189
+ ],
190
+ )
191
+ @testing.variation(
192
+ "use_temp",
193
+ [
194
+ False,
195
+ (
196
+ True,
197
+ testing.requires.create_temp_table_as
198
+ + testing.requires.temporary_views,
199
+ ),
200
+ ],
201
+ )
202
+ @testing.variation("use_drop_all", [True, False])
203
+ def test_with_metadata(
204
+ self,
205
+ connection,
206
+ metadata,
207
+ use_temp,
208
+ table_type,
209
+ use_drop_all,
210
+ ):
211
+ source_table = self.tables.source_table
212
+
213
+ select_stmt = select(
214
+ source_table.c.id,
215
+ source_table.c.name,
216
+ source_table.c.value,
217
+ ).where(source_table.c.value > 100)
218
+
219
+ match table_type:
220
+ case "create_table_as":
221
+ tablename = (
222
+ self.temp_table_name() if use_temp else "dest_table"
223
+ )
224
+ stmt = CreateTableAs(
225
+ select_stmt,
226
+ tablename,
227
+ metadata=metadata,
228
+ temporary=bool(use_temp),
229
+ )
230
+ case "select_into":
231
+ tablename = (
232
+ self.temp_table_name() if use_temp else "dest_table"
233
+ )
234
+ stmt = select_stmt.into(
235
+ tablename,
236
+ temporary=use_temp,
237
+ metadata=metadata,
238
+ )
239
+ case "create_view":
240
+ tablename = self.temp_view_name() if use_temp else "dest_view"
241
+ stmt = CreateView(
242
+ select_stmt,
243
+ tablename,
244
+ metadata=metadata,
245
+ temporary=bool(use_temp),
246
+ )
247
+ case _:
248
+ table_type.fail()
249
+
250
+ # these are metadata attached, create all
251
+ metadata.create_all(connection)
252
+
253
+ # Verify the generated table is a proper Table object
254
+ dest = stmt.table
255
+ assert isinstance(dest, Table)
256
+ assert dest.metadata is metadata
257
+ eq_(dest.name, tablename)
258
+
259
+ # SELECT from the generated table - should only have rows with
260
+ # value > 100 (bob and charlie)
261
+ result = connection.execute(
262
+ select(dest.c.id, dest.c.name).order_by(dest.c.id)
263
+ ).fetchall()
264
+
265
+ eq_(result, [(2, "bob"), (3, "charlie")])
266
+
267
+ # Drop the table using either metadata.drop_all() or dest.drop()
268
+ if use_drop_all:
269
+ metadata.drop_all(connection)
270
+ else:
271
+ dest.drop(connection)
272
+
273
+ # Verify it's gone
274
+ if use_temp:
275
+ if testing.requires.temp_table_names.enabled:
276
+ insp = inspect(connection)
277
+ assert tablename not in insp.get_temp_table_names()
278
+ else:
279
+ insp = inspect(connection)
280
+ if table_type.create_view:
281
+ assert tablename not in insp.get_view_names()
282
+ else:
283
+ assert tablename not in insp.get_table_names()
284
+
285
+ @testing.variation(
286
+ "table_type",
287
+ [
288
+ ("create_table_as", testing.requires.create_table_as),
289
+ ("create_view", testing.requires.views),
290
+ ],
291
+ )
292
+ def test_with_labels(self, connection, table_type):
293
+ source_table = self.tables.source_table
294
+
295
+ match table_type:
296
+ case "create_table_as":
297
+ tablename = "dest_table"
298
+ stmt = CreateTableAs(
299
+ select(
300
+ source_table.c.id.label("user_id"),
301
+ source_table.c.name.label("user_name"),
302
+ ),
303
+ tablename,
304
+ )
305
+ case "create_view":
306
+ tablename = "dest_view"
307
+ stmt = CreateView(
308
+ select(
309
+ source_table.c.id.label("user_id"),
310
+ source_table.c.name.label("user_name"),
311
+ ),
312
+ tablename,
313
+ )
314
+ case _:
315
+ table_type.fail()
316
+
317
+ connection.execute(stmt)
318
+
319
+ # Verify column names from labels
320
+ insp = inspect(connection)
321
+ cols = insp.get_columns(tablename)
322
+ eq_(len(cols), 2)
323
+ eq_(cols[0]["name"], "user_id")
324
+ eq_(cols[1]["name"], "user_name")
325
+
326
+ # Verify we can query using the labels
327
+ dest = stmt.table
328
+ result = connection.execute(
329
+ select(dest.c.user_id, dest.c.user_name).where(dest.c.user_id == 1)
330
+ ).fetchall()
331
+
332
+ eq_(result, [(1, "alice")])
333
+
334
+ @testing.requires.table_ddl_if_exists
335
+ @testing.requires.create_table_as
336
+ def test_create_table_as_if_not_exists(self, connection):
337
+ source_table = self.tables.source_table
338
+ tablename = "dest_table"
339
+
340
+ stmt = CreateTableAs(
341
+ select(source_table.c.id).select_from(source_table),
342
+ tablename,
343
+ if_not_exists=True,
344
+ )
345
+
346
+ insp = inspect(connection)
347
+ assert tablename not in insp.get_table_names()
348
+
349
+ connection.execute(stmt)
350
+
351
+ insp = inspect(connection)
352
+ assert tablename in insp.get_table_names()
353
+
354
+ # succeeds even though table exists
355
+ connection.execute(stmt)
356
+
357
+ @testing.requires.create_or_replace_view
358
+ def test_create_or_replace_view(self, connection):
359
+ source_table = self.tables.source_table
360
+ viewname = "dest_view"
361
+
362
+ # Create initial view that selects all rows
363
+ stmt = CreateView(
364
+ select(source_table.c.id).select_from(source_table),
365
+ viewname,
366
+ or_replace=True,
367
+ )
368
+
369
+ insp = inspect(connection)
370
+ assert viewname not in insp.get_view_names()
371
+
372
+ connection.execute(stmt)
373
+
374
+ insp = inspect(connection)
375
+ assert viewname in insp.get_view_names()
376
+
377
+ # Verify initial view returns all 3 rows
378
+ result = connection.execute(select(stmt.table)).fetchall()
379
+ eq_(len(result), 3)
380
+
381
+ # Replace view with filtered query (only id > 1)
382
+ stmt = CreateView(
383
+ select(source_table.c.id)
384
+ .select_from(source_table)
385
+ .where(source_table.c.id > 1),
386
+ viewname,
387
+ or_replace=True,
388
+ )
389
+ connection.execute(stmt)
390
+
391
+ # Verify view was replaced - should now return only 2 rows
392
+ insp = inspect(connection)
393
+ assert viewname in insp.get_view_names()
394
+
395
+ result = connection.execute(select(stmt.table)).fetchall()
396
+ eq_(len(result), 2)
397
+
398
+ @testing.requires.materialized_views
399
+ @testing.variation("use_metadata", [True, False])
400
+ def test_create_drop_materialized_view(self, connection, use_metadata):
401
+ source_table = self.tables.source_table
402
+ viewname = "dest_mat_view"
403
+
404
+ if use_metadata:
405
+ # Create with metadata
406
+ metadata = MetaData()
407
+ stmt = CreateView(
408
+ select(source_table.c.id, source_table.c.name).select_from(
409
+ source_table
410
+ ),
411
+ viewname,
412
+ materialized=True,
413
+ metadata=metadata,
414
+ )
415
+ else:
416
+ # Create without metadata
417
+ stmt = CreateView(
418
+ select(source_table.c.id, source_table.c.name).select_from(
419
+ source_table
420
+ ),
421
+ viewname,
422
+ materialized=True,
423
+ )
424
+
425
+ insp = inspect(connection)
426
+ assert viewname not in insp.get_materialized_view_names()
427
+
428
+ if use_metadata:
429
+ metadata.create_all(connection)
430
+ else:
431
+ connection.execute(stmt)
432
+
433
+ insp = inspect(connection)
434
+ assert viewname in insp.get_materialized_view_names()
435
+
436
+ # Verify materialized view returns data
437
+ dst_view = stmt.table
438
+ result = connection.execute(select(dst_view)).fetchall()
439
+ eq_(len(result), 3)
440
+ eq_(set(r[0] for r in result), {1, 2, 3})
441
+
442
+ # Drop materialized view
443
+ if use_metadata:
444
+ metadata.drop_all(connection)
445
+ else:
446
+ drop_stmt = DropView(dst_view, materialized=True)
447
+ connection.execute(drop_stmt)
448
+
449
+ insp = inspect(connection)
450
+ assert viewname not in insp.get_materialized_view_names()
451
+
452
+ @testing.variation(
453
+ "table_type",
454
+ [
455
+ ("create_table_as", testing.requires.create_table_as),
456
+ ("create_view", testing.requires.views),
457
+ ],
458
+ )
459
+ def test_literal_inlining_inside_select(self, connection, table_type):
460
+ src = self.tables.source_table
461
+ sel = select(
462
+ (src.c.id + 1).label("id2"),
463
+ literal("x").label("tag"),
464
+ ).select_from(src)
465
+
466
+ match table_type:
467
+ case "create_table_as":
468
+ tablename = "dest_table"
469
+ stmt = CreateTableAs(sel, tablename)
470
+ case "create_view":
471
+ tablename = "dest_view"
472
+ stmt = CreateView(sel, tablename)
473
+ case _:
474
+ table_type.fail()
475
+
476
+ connection.execute(stmt)
477
+
478
+ tbl = stmt.table
479
+ row = connection.execute(
480
+ select(func.count(), func.min(tbl.c.tag), func.max(tbl.c.tag))
481
+ ).first()
482
+ eq_(row, (3, "x", "x"))
483
+
484
+ @testing.variation(
485
+ "table_type",
486
+ [
487
+ ("create_table_as", testing.requires.create_table_as),
488
+ ("create_view", testing.requires.views),
489
+ ],
490
+ )
491
+ def test_with_bind_param_executes(self, connection, table_type):
492
+ src = self.tables.source_table
493
+
494
+ sel = (
495
+ select(src.c.id, src.c.name)
496
+ .select_from(src)
497
+ .where(src.c.name == bindparam("p", value="alice"))
498
+ )
499
+
500
+ match table_type:
501
+ case "create_table_as":
502
+ tablename = "dest_table"
503
+ stmt = CreateTableAs(sel, tablename)
504
+ case "create_view":
505
+ tablename = "dest_view"
506
+ stmt = CreateView(sel, tablename)
507
+ case _:
508
+ table_type.fail()
509
+
510
+ connection.execute(stmt)
511
+
512
+ tbl = stmt.table
513
+
514
+ row = connection.execute(
515
+ select(func.count(), func.min(tbl.c.name), func.max(tbl.c.name))
516
+ ).first()
517
+ eq_(row, (1, "alice", "alice"))
518
+
519
+ @testing.variation(
520
+ "table_type",
521
+ [
522
+ ("create_table_as", testing.requires.create_table_as),
523
+ ("create_view", testing.requires.views),
524
+ ],
525
+ )
526
+ def test_compound_select_smoke(self, connection, table_type):
527
+ a, b = self.tables("a", "b")
528
+
529
+ sel = select(a.c.id).union_all(select(b.c.id))
530
+
531
+ match table_type:
532
+ case "create_table_as":
533
+ tablename = "dest_table"
534
+ stmt = CreateTableAs(sel, tablename)
535
+ case "create_view":
536
+ tablename = "dest_view"
537
+ stmt = CreateView(sel, tablename)
538
+ case _:
539
+ table_type.fail()
540
+
541
+ connection.execute(stmt)
542
+
543
+ vals = (
544
+ connection.execute(
545
+ select(stmt.table.c.id).order_by(stmt.table.c.id)
546
+ )
547
+ .scalars()
548
+ .all()
549
+ )
550
+ eq_(vals, [1, 2, 3, 4])
551
+
552
+ @testing.requires.views
553
+ def test_view_dependencies_with_metadata(self, connection, metadata):
554
+ """Test that views with dependencies are created/dropped in correct
555
+ order.
556
+
557
+ This validates that when views are attached to metadata: - create_all()
558
+ creates base tables first, then dependent views in order - drop_all()
559
+ drops dependent views first, then base tables in reverse order
560
+ """
561
+ # Create three base tables
562
+ table1 = Table(
563
+ "base_table1",
564
+ metadata,
565
+ Column("id", Integer, primary_key=True, autoincrement=False),
566
+ Column("value", Integer),
567
+ )
568
+ table2 = Table(
569
+ "base_table2",
570
+ metadata,
571
+ Column("id", Integer, primary_key=True, autoincrement=False),
572
+ Column("amount", Integer),
573
+ )
574
+ table3 = Table(
575
+ "base_table3",
576
+ metadata,
577
+ Column("id", Integer, primary_key=True, autoincrement=False),
578
+ Column("total", Integer),
579
+ )
580
+
581
+ # First view depends on table1 and table2
582
+ view1_stmt = CreateView(
583
+ select(
584
+ table1.c.id,
585
+ table1.c.value,
586
+ table2.c.amount,
587
+ )
588
+ .select_from(table1.join(table2, table1.c.id == table2.c.id))
589
+ .where(table1.c.value > 0),
590
+ "view1",
591
+ metadata=metadata,
592
+ )
593
+
594
+ # Second view depends on table3 and view1
595
+ view2_stmt = CreateView(
596
+ select(
597
+ view1_stmt.table.c.id,
598
+ view1_stmt.table.c.value,
599
+ table3.c.total,
600
+ )
601
+ .select_from(
602
+ view1_stmt.table.join(
603
+ table3, view1_stmt.table.c.id == table3.c.id
604
+ )
605
+ )
606
+ .where(table3.c.total > 100),
607
+ "view2",
608
+ metadata=metadata,
609
+ )
610
+
611
+ # Verify metadata knows about all objects
612
+ eq_(
613
+ {"base_table1", "base_table2", "base_table3", "view1", "view2"},
614
+ set(metadata.tables),
615
+ )
616
+
617
+ # Create all in correct dependency order
618
+ metadata.create_all(connection)
619
+
620
+ # Verify all tables and views were created
621
+ insp = inspect(connection)
622
+ assert {"base_table1", "base_table2", "base_table3"}.issubset(
623
+ insp.get_table_names()
624
+ )
625
+ assert {"view1", "view2"}.issubset(insp.get_view_names())
626
+
627
+ # Insert test data
628
+ connection.execute(
629
+ table1.insert(),
630
+ [
631
+ {"id": 1, "value": 10},
632
+ {"id": 2, "value": 20},
633
+ {"id": 3, "value": 30},
634
+ ],
635
+ )
636
+ connection.execute(
637
+ table2.insert(),
638
+ [
639
+ {"id": 1, "amount": 100},
640
+ {"id": 2, "amount": 200},
641
+ {"id": 3, "amount": 300},
642
+ ],
643
+ )
644
+ connection.execute(
645
+ table3.insert(),
646
+ [
647
+ {"id": 1, "total": 50},
648
+ {"id": 2, "total": 150},
649
+ {"id": 3, "total": 250},
650
+ ],
651
+ )
652
+
653
+ # Query view1 to verify it works
654
+ view1_results = connection.execute(
655
+ select(view1_stmt.table).order_by(view1_stmt.table.c.id)
656
+ ).fetchall()
657
+ eq_(
658
+ view1_results,
659
+ [
660
+ (1, 10, 100),
661
+ (2, 20, 200),
662
+ (3, 30, 300),
663
+ ],
664
+ )
665
+
666
+ # Query view2 to verify it works (should filter total > 100)
667
+ view2_results = connection.execute(
668
+ select(view2_stmt.table).order_by(view2_stmt.table.c.id)
669
+ ).fetchall()
670
+ eq_(
671
+ view2_results,
672
+ [
673
+ (2, 20, 150),
674
+ (3, 30, 250),
675
+ ],
676
+ )
677
+
678
+ # Drop all in correct reverse dependency order
679
+ metadata.drop_all(connection)
680
+
681
+ # Verify all tables and views were dropped
682
+ insp = inspect(connection)
683
+ assert {"base_table1", "base_table2", "base_table3"}.isdisjoint(
684
+ insp.get_table_names()
685
+ )
686
+ assert {"view1", "view2"}.isdisjoint(insp.get_view_names())