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,2112 @@
1
+ # testing/suite/test_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
+ import collections.abc as collections_abc
10
+ from datetime import date
11
+ from datetime import timedelta
12
+ import itertools
13
+
14
+ from .. import AssertsCompiledSQL
15
+ from .. import AssertsExecutionResults
16
+ from .. import config
17
+ from .. import fixtures
18
+ from ..assertions import assert_raises
19
+ from ..assertions import eq_
20
+ from ..assertions import in_
21
+ from ..assertsql import CursorSQL
22
+ from ..schema import Column
23
+ from ..schema import Table
24
+ from ... import bindparam
25
+ from ... import case
26
+ from ... import column
27
+ from ... import Computed
28
+ from ... import Date
29
+ from ... import exists
30
+ from ... import false
31
+ from ... import Float
32
+ from ... import ForeignKey
33
+ from ... import FrameClause
34
+ from ... import FrameClauseType
35
+ from ... import func
36
+ from ... import Identity
37
+ from ... import Integer
38
+ from ... import literal
39
+ from ... import literal_column
40
+ from ... import null
41
+ from ... import select
42
+ from ... import String
43
+ from ... import table
44
+ from ... import testing
45
+ from ... import text
46
+ from ... import true
47
+ from ... import tuple_
48
+ from ... import TupleType
49
+ from ... import union
50
+ from ... import values
51
+ from ...exc import DatabaseError
52
+ from ...exc import ProgrammingError
53
+
54
+
55
+ class CollateTest(fixtures.TablesTest):
56
+ __sparse_driver_backend__ = True
57
+
58
+ @classmethod
59
+ def define_tables(cls, metadata):
60
+ Table(
61
+ "some_table",
62
+ metadata,
63
+ Column("id", Integer, primary_key=True),
64
+ Column("data", String(100)),
65
+ )
66
+
67
+ @classmethod
68
+ def insert_data(cls, connection):
69
+ connection.execute(
70
+ cls.tables.some_table.insert(),
71
+ [
72
+ {"id": 1, "data": "collate data1"},
73
+ {"id": 2, "data": "collate data2"},
74
+ ],
75
+ )
76
+
77
+ def _assert_result(self, select, result):
78
+ with config.db.connect() as conn:
79
+ eq_(conn.execute(select).fetchall(), result)
80
+
81
+ @testing.requires.order_by_collation
82
+ def test_collate_order_by(self):
83
+ collation = testing.requires.get_order_by_collation(testing.config)
84
+
85
+ self._assert_result(
86
+ select(self.tables.some_table).order_by(
87
+ self.tables.some_table.c.data.collate(collation).asc()
88
+ ),
89
+ [(1, "collate data1"), (2, "collate data2")],
90
+ )
91
+
92
+
93
+ class OrderByLabelTest(fixtures.TablesTest):
94
+ """Test the dialect sends appropriate ORDER BY expressions when
95
+ labels are used.
96
+
97
+ This essentially exercises the "supports_simple_order_by_label"
98
+ setting.
99
+
100
+ """
101
+
102
+ __sparse_driver_backend__ = True
103
+
104
+ @classmethod
105
+ def define_tables(cls, metadata):
106
+ Table(
107
+ "some_table",
108
+ metadata,
109
+ Column("id", Integer, primary_key=True),
110
+ Column("x", Integer),
111
+ Column("y", Integer),
112
+ Column("q", String(50)),
113
+ Column("p", String(50)),
114
+ )
115
+
116
+ @classmethod
117
+ def insert_data(cls, connection):
118
+ connection.execute(
119
+ cls.tables.some_table.insert(),
120
+ [
121
+ {"id": 1, "x": 1, "y": 2, "q": "q1", "p": "p3"},
122
+ {"id": 2, "x": 2, "y": 3, "q": "q2", "p": "p2"},
123
+ {"id": 3, "x": 3, "y": 4, "q": "q3", "p": "p1"},
124
+ ],
125
+ )
126
+
127
+ def _assert_result(self, select, result):
128
+ with config.db.connect() as conn:
129
+ eq_(conn.execute(select).fetchall(), result)
130
+
131
+ def test_plain(self):
132
+ table = self.tables.some_table
133
+ lx = table.c.x.label("lx")
134
+ self._assert_result(select(lx).order_by(lx), [(1,), (2,), (3,)])
135
+
136
+ def test_composed_int(self):
137
+ table = self.tables.some_table
138
+ lx = (table.c.x + table.c.y).label("lx")
139
+ self._assert_result(select(lx).order_by(lx), [(3,), (5,), (7,)])
140
+
141
+ def test_composed_multiple(self):
142
+ table = self.tables.some_table
143
+ lx = (table.c.x + table.c.y).label("lx")
144
+ ly = (func.lower(table.c.q) + table.c.p).label("ly")
145
+ self._assert_result(
146
+ select(lx, ly).order_by(lx, ly.desc()),
147
+ [(3, "q1p3"), (5, "q2p2"), (7, "q3p1")],
148
+ )
149
+
150
+ def test_plain_desc(self):
151
+ table = self.tables.some_table
152
+ lx = table.c.x.label("lx")
153
+ self._assert_result(select(lx).order_by(lx.desc()), [(3,), (2,), (1,)])
154
+
155
+ def test_composed_int_desc(self):
156
+ table = self.tables.some_table
157
+ lx = (table.c.x + table.c.y).label("lx")
158
+ self._assert_result(select(lx).order_by(lx.desc()), [(7,), (5,), (3,)])
159
+
160
+ @testing.requires.group_by_complex_expression
161
+ def test_group_by_composed(self):
162
+ table = self.tables.some_table
163
+ expr = (table.c.x + table.c.y).label("lx")
164
+ stmt = (
165
+ select(func.count(table.c.id), expr).group_by(expr).order_by(expr)
166
+ )
167
+ self._assert_result(stmt, [(1, 3), (1, 5), (1, 7)])
168
+
169
+
170
+ class ValuesExpressionTest(fixtures.TestBase):
171
+ __requires__ = ("table_value_constructor",)
172
+
173
+ __sparse_driver_backend__ = True
174
+
175
+ def test_tuples(self, connection):
176
+ value_expr = values(
177
+ column("id", Integer), column("name", String), name="my_values"
178
+ ).data([(1, "name1"), (2, "name2"), (3, "name3")])
179
+
180
+ eq_(
181
+ connection.execute(select(value_expr)).all(),
182
+ [(1, "name1"), (2, "name2"), (3, "name3")],
183
+ )
184
+
185
+
186
+ class FetchLimitOffsetTest(fixtures.TablesTest):
187
+ __backend__ = True
188
+
189
+ @classmethod
190
+ def define_tables(cls, metadata):
191
+ Table(
192
+ "some_table",
193
+ metadata,
194
+ Column("id", Integer, primary_key=True),
195
+ Column("x", Integer),
196
+ Column("y", Integer),
197
+ )
198
+
199
+ @classmethod
200
+ def insert_data(cls, connection):
201
+ connection.execute(
202
+ cls.tables.some_table.insert(),
203
+ [
204
+ {"id": 1, "x": 1, "y": 2},
205
+ {"id": 2, "x": 2, "y": 3},
206
+ {"id": 3, "x": 3, "y": 4},
207
+ {"id": 4, "x": 4, "y": 5},
208
+ {"id": 5, "x": 4, "y": 6},
209
+ ],
210
+ )
211
+
212
+ def _assert_result(
213
+ self, connection, select, result, params=None, set_=False
214
+ ):
215
+ if set_:
216
+ query_res = connection.execute(select, params).fetchall()
217
+ eq_(len(query_res), len(result))
218
+ eq_(set(query_res), set(result))
219
+
220
+ else:
221
+ eq_(connection.execute(select, params).fetchall(), result)
222
+
223
+ def _assert_result_str(self, select, result, params=None):
224
+ with config.db.connect() as conn:
225
+ eq_(conn.exec_driver_sql(select, params).fetchall(), result)
226
+
227
+ def test_simple_limit(self, connection):
228
+ table = self.tables.some_table
229
+ stmt = select(table).order_by(table.c.id)
230
+ self._assert_result(
231
+ connection,
232
+ stmt.limit(2),
233
+ [(1, 1, 2), (2, 2, 3)],
234
+ )
235
+ self._assert_result(
236
+ connection,
237
+ stmt.limit(3),
238
+ [(1, 1, 2), (2, 2, 3), (3, 3, 4)],
239
+ )
240
+
241
+ def test_limit_render_multiple_times(self, connection):
242
+ table = self.tables.some_table
243
+ stmt = (
244
+ select(table.c.id).order_by(table.c.id).limit(1).scalar_subquery()
245
+ )
246
+
247
+ u = union(select(stmt), select(stmt)).subquery().select()
248
+
249
+ self._assert_result(
250
+ connection,
251
+ u,
252
+ [
253
+ (1,),
254
+ ],
255
+ )
256
+
257
+ @testing.requires.fetch_first
258
+ def test_simple_fetch(self, connection):
259
+ table = self.tables.some_table
260
+ self._assert_result(
261
+ connection,
262
+ select(table).order_by(table.c.id).fetch(2),
263
+ [(1, 1, 2), (2, 2, 3)],
264
+ )
265
+ self._assert_result(
266
+ connection,
267
+ select(table).order_by(table.c.id).fetch(3),
268
+ [(1, 1, 2), (2, 2, 3), (3, 3, 4)],
269
+ )
270
+
271
+ @testing.requires.offset
272
+ def test_simple_offset(self, connection):
273
+ table = self.tables.some_table
274
+ self._assert_result(
275
+ connection,
276
+ select(table).order_by(table.c.id).offset(2),
277
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
278
+ )
279
+ self._assert_result(
280
+ connection,
281
+ select(table).order_by(table.c.id).offset(3),
282
+ [(4, 4, 5), (5, 4, 6)],
283
+ )
284
+
285
+ @testing.combinations(
286
+ ([(2, 0), (2, 1), (3, 2)]),
287
+ ([(2, 1), (2, 0), (3, 2)]),
288
+ ([(3, 1), (2, 1), (3, 1)]),
289
+ argnames="cases",
290
+ )
291
+ @testing.requires.offset
292
+ def test_simple_limit_offset(self, connection, cases):
293
+ table = self.tables.some_table
294
+ connection = connection.execution_options(compiled_cache={})
295
+
296
+ assert_data = [(1, 1, 2), (2, 2, 3), (3, 3, 4), (4, 4, 5), (5, 4, 6)]
297
+
298
+ for limit, offset in cases:
299
+ expected = assert_data[offset : offset + limit]
300
+ self._assert_result(
301
+ connection,
302
+ select(table).order_by(table.c.id).limit(limit).offset(offset),
303
+ expected,
304
+ )
305
+
306
+ @testing.requires.fetch_first
307
+ def test_simple_fetch_offset(self, connection):
308
+ table = self.tables.some_table
309
+ self._assert_result(
310
+ connection,
311
+ select(table).order_by(table.c.id).fetch(2).offset(1),
312
+ [(2, 2, 3), (3, 3, 4)],
313
+ )
314
+
315
+ self._assert_result(
316
+ connection,
317
+ select(table).order_by(table.c.id).fetch(3).offset(2),
318
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
319
+ )
320
+
321
+ @testing.requires.fetch_no_order_by
322
+ def test_fetch_offset_no_order(self, connection):
323
+ table = self.tables.some_table
324
+ self._assert_result(
325
+ connection,
326
+ select(table).fetch(10),
327
+ [(1, 1, 2), (2, 2, 3), (3, 3, 4), (4, 4, 5), (5, 4, 6)],
328
+ set_=True,
329
+ )
330
+
331
+ @testing.requires.offset
332
+ def test_simple_offset_zero(self, connection):
333
+ table = self.tables.some_table
334
+ self._assert_result(
335
+ connection,
336
+ select(table).order_by(table.c.id).offset(0),
337
+ [(1, 1, 2), (2, 2, 3), (3, 3, 4), (4, 4, 5), (5, 4, 6)],
338
+ )
339
+
340
+ self._assert_result(
341
+ connection,
342
+ select(table).order_by(table.c.id).offset(1),
343
+ [(2, 2, 3), (3, 3, 4), (4, 4, 5), (5, 4, 6)],
344
+ )
345
+
346
+ @testing.requires.offset
347
+ def test_limit_offset_nobinds(self):
348
+ """test that 'literal binds' mode works - no bound params."""
349
+
350
+ table = self.tables.some_table
351
+ stmt = select(table).order_by(table.c.id).limit(2).offset(1)
352
+ sql = stmt.compile(
353
+ dialect=config.db.dialect, compile_kwargs={"literal_binds": True}
354
+ )
355
+ sql = str(sql)
356
+
357
+ self._assert_result_str(sql, [(2, 2, 3), (3, 3, 4)])
358
+
359
+ @testing.requires.fetch_first
360
+ def test_fetch_offset_nobinds(self):
361
+ """test that 'literal binds' mode works - no bound params."""
362
+
363
+ table = self.tables.some_table
364
+ stmt = select(table).order_by(table.c.id).fetch(2).offset(1)
365
+ sql = stmt.compile(
366
+ dialect=config.db.dialect, compile_kwargs={"literal_binds": True}
367
+ )
368
+ sql = str(sql)
369
+
370
+ self._assert_result_str(sql, [(2, 2, 3), (3, 3, 4)])
371
+
372
+ @testing.requires.bound_limit_offset
373
+ def test_bound_limit(self, connection):
374
+ table = self.tables.some_table
375
+ self._assert_result(
376
+ connection,
377
+ select(table).order_by(table.c.id).limit(bindparam("l")),
378
+ [(1, 1, 2), (2, 2, 3)],
379
+ params={"l": 2},
380
+ )
381
+
382
+ self._assert_result(
383
+ connection,
384
+ select(table).order_by(table.c.id).limit(bindparam("l")),
385
+ [(1, 1, 2), (2, 2, 3), (3, 3, 4)],
386
+ params={"l": 3},
387
+ )
388
+
389
+ @testing.requires.bound_limit_offset
390
+ def test_bound_offset(self, connection):
391
+ table = self.tables.some_table
392
+ self._assert_result(
393
+ connection,
394
+ select(table).order_by(table.c.id).offset(bindparam("o")),
395
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
396
+ params={"o": 2},
397
+ )
398
+
399
+ self._assert_result(
400
+ connection,
401
+ select(table).order_by(table.c.id).offset(bindparam("o")),
402
+ [(2, 2, 3), (3, 3, 4), (4, 4, 5), (5, 4, 6)],
403
+ params={"o": 1},
404
+ )
405
+
406
+ @testing.requires.bound_limit_offset
407
+ def test_bound_limit_offset(self, connection):
408
+ table = self.tables.some_table
409
+ self._assert_result(
410
+ connection,
411
+ select(table)
412
+ .order_by(table.c.id)
413
+ .limit(bindparam("l"))
414
+ .offset(bindparam("o")),
415
+ [(2, 2, 3), (3, 3, 4)],
416
+ params={"l": 2, "o": 1},
417
+ )
418
+
419
+ self._assert_result(
420
+ connection,
421
+ select(table)
422
+ .order_by(table.c.id)
423
+ .limit(bindparam("l"))
424
+ .offset(bindparam("o")),
425
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
426
+ params={"l": 3, "o": 2},
427
+ )
428
+
429
+ @testing.requires.fetch_first
430
+ def test_bound_fetch_offset(self, connection):
431
+ table = self.tables.some_table
432
+ self._assert_result(
433
+ connection,
434
+ select(table)
435
+ .order_by(table.c.id)
436
+ .fetch(bindparam("f"))
437
+ .offset(bindparam("o")),
438
+ [(2, 2, 3), (3, 3, 4)],
439
+ params={"f": 2, "o": 1},
440
+ )
441
+
442
+ self._assert_result(
443
+ connection,
444
+ select(table)
445
+ .order_by(table.c.id)
446
+ .fetch(bindparam("f"))
447
+ .offset(bindparam("o")),
448
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
449
+ params={"f": 3, "o": 2},
450
+ )
451
+
452
+ @testing.requires.sql_expression_limit_offset
453
+ def test_expr_offset(self, connection):
454
+ table = self.tables.some_table
455
+ self._assert_result(
456
+ connection,
457
+ select(table)
458
+ .order_by(table.c.id)
459
+ .offset(literal_column("1") + literal_column("2")),
460
+ [(4, 4, 5), (5, 4, 6)],
461
+ )
462
+
463
+ @testing.requires.sql_expression_limit_offset
464
+ def test_expr_limit(self, connection):
465
+ table = self.tables.some_table
466
+ self._assert_result(
467
+ connection,
468
+ select(table)
469
+ .order_by(table.c.id)
470
+ .limit(literal_column("1") + literal_column("2")),
471
+ [(1, 1, 2), (2, 2, 3), (3, 3, 4)],
472
+ )
473
+
474
+ @testing.requires.sql_expression_limit_offset
475
+ def test_expr_limit_offset(self, connection):
476
+ table = self.tables.some_table
477
+ self._assert_result(
478
+ connection,
479
+ select(table)
480
+ .order_by(table.c.id)
481
+ .limit(literal_column("1") + literal_column("1"))
482
+ .offset(literal_column("1") + literal_column("1")),
483
+ [(3, 3, 4), (4, 4, 5)],
484
+ )
485
+
486
+ @testing.requires.fetch_first
487
+ @testing.requires.fetch_expression
488
+ def test_expr_fetch_offset(self, connection):
489
+ table = self.tables.some_table
490
+ self._assert_result(
491
+ connection,
492
+ select(table)
493
+ .order_by(table.c.id)
494
+ .fetch(literal_column("1") + literal_column("1"))
495
+ .offset(literal_column("1") + literal_column("1")),
496
+ [(3, 3, 4), (4, 4, 5)],
497
+ )
498
+
499
+ @testing.requires.sql_expression_limit_offset
500
+ def test_simple_limit_expr_offset(self, connection):
501
+ table = self.tables.some_table
502
+ self._assert_result(
503
+ connection,
504
+ select(table)
505
+ .order_by(table.c.id)
506
+ .limit(2)
507
+ .offset(literal_column("1") + literal_column("1")),
508
+ [(3, 3, 4), (4, 4, 5)],
509
+ )
510
+
511
+ self._assert_result(
512
+ connection,
513
+ select(table)
514
+ .order_by(table.c.id)
515
+ .limit(3)
516
+ .offset(literal_column("1") + literal_column("1")),
517
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
518
+ )
519
+
520
+ @testing.requires.sql_expression_limit_offset
521
+ def test_expr_limit_simple_offset(self, connection):
522
+ table = self.tables.some_table
523
+ self._assert_result(
524
+ connection,
525
+ select(table)
526
+ .order_by(table.c.id)
527
+ .limit(literal_column("1") + literal_column("1"))
528
+ .offset(2),
529
+ [(3, 3, 4), (4, 4, 5)],
530
+ )
531
+
532
+ self._assert_result(
533
+ connection,
534
+ select(table)
535
+ .order_by(table.c.id)
536
+ .limit(literal_column("1") + literal_column("1"))
537
+ .offset(1),
538
+ [(2, 2, 3), (3, 3, 4)],
539
+ )
540
+
541
+ @testing.requires.fetch_ties
542
+ def test_simple_fetch_ties(self, connection):
543
+ table = self.tables.some_table
544
+ self._assert_result(
545
+ connection,
546
+ select(table).order_by(table.c.x.desc()).fetch(1, with_ties=True),
547
+ [(4, 4, 5), (5, 4, 6)],
548
+ set_=True,
549
+ )
550
+
551
+ self._assert_result(
552
+ connection,
553
+ select(table).order_by(table.c.x.desc()).fetch(3, with_ties=True),
554
+ [(3, 3, 4), (4, 4, 5), (5, 4, 6)],
555
+ set_=True,
556
+ )
557
+
558
+ @testing.requires.fetch_ties
559
+ @testing.requires.fetch_offset_with_options
560
+ def test_fetch_offset_ties(self, connection):
561
+ table = self.tables.some_table
562
+ fa = connection.execute(
563
+ select(table)
564
+ .order_by(table.c.x)
565
+ .fetch(2, with_ties=True)
566
+ .offset(2)
567
+ ).fetchall()
568
+ eq_(fa[0], (3, 3, 4))
569
+ eq_(set(fa), {(3, 3, 4), (4, 4, 5), (5, 4, 6)})
570
+
571
+ @testing.requires.fetch_ties
572
+ @testing.requires.fetch_offset_with_options
573
+ def test_fetch_offset_ties_exact_number(self, connection):
574
+ table = self.tables.some_table
575
+ self._assert_result(
576
+ connection,
577
+ select(table)
578
+ .order_by(table.c.x)
579
+ .fetch(2, with_ties=True)
580
+ .offset(1),
581
+ [(2, 2, 3), (3, 3, 4)],
582
+ )
583
+
584
+ self._assert_result(
585
+ connection,
586
+ select(table)
587
+ .order_by(table.c.x)
588
+ .fetch(3, with_ties=True)
589
+ .offset(3),
590
+ [(4, 4, 5), (5, 4, 6)],
591
+ )
592
+
593
+ @testing.requires.fetch_percent
594
+ def test_simple_fetch_percent(self, connection):
595
+ table = self.tables.some_table
596
+ self._assert_result(
597
+ connection,
598
+ select(table).order_by(table.c.id).fetch(20, percent=True),
599
+ [(1, 1, 2)],
600
+ )
601
+
602
+ @testing.requires.fetch_percent
603
+ @testing.requires.fetch_offset_with_options
604
+ def test_fetch_offset_percent(self, connection):
605
+ table = self.tables.some_table
606
+ self._assert_result(
607
+ connection,
608
+ select(table)
609
+ .order_by(table.c.id)
610
+ .fetch(40, percent=True)
611
+ .offset(1),
612
+ [(2, 2, 3), (3, 3, 4)],
613
+ )
614
+
615
+ @testing.requires.fetch_ties
616
+ @testing.requires.fetch_percent
617
+ def test_simple_fetch_percent_ties(self, connection):
618
+ table = self.tables.some_table
619
+ self._assert_result(
620
+ connection,
621
+ select(table)
622
+ .order_by(table.c.x.desc())
623
+ .fetch(20, percent=True, with_ties=True),
624
+ [(4, 4, 5), (5, 4, 6)],
625
+ set_=True,
626
+ )
627
+
628
+ @testing.requires.fetch_ties
629
+ @testing.requires.fetch_percent
630
+ @testing.requires.fetch_offset_with_options
631
+ def test_fetch_offset_percent_ties(self, connection):
632
+ table = self.tables.some_table
633
+ fa = connection.execute(
634
+ select(table)
635
+ .order_by(table.c.x)
636
+ .fetch(40, percent=True, with_ties=True)
637
+ .offset(2)
638
+ ).fetchall()
639
+ eq_(fa[0], (3, 3, 4))
640
+ eq_(set(fa), {(3, 3, 4), (4, 4, 5), (5, 4, 6)})
641
+
642
+
643
+ class SameNamedSchemaTableTest(fixtures.TablesTest):
644
+ """tests for #7471"""
645
+
646
+ __sparse_driver_backend__ = True
647
+
648
+ __requires__ = ("schemas",)
649
+
650
+ @classmethod
651
+ def define_tables(cls, metadata):
652
+ Table(
653
+ "some_table",
654
+ metadata,
655
+ Column("id", Integer, primary_key=True),
656
+ schema=config.test_schema,
657
+ )
658
+ Table(
659
+ "some_table",
660
+ metadata,
661
+ Column("id", Integer, primary_key=True),
662
+ Column(
663
+ "some_table_id",
664
+ Integer,
665
+ # ForeignKey("%s.some_table.id" % config.test_schema),
666
+ nullable=False,
667
+ ),
668
+ )
669
+
670
+ @classmethod
671
+ def insert_data(cls, connection):
672
+ some_table, some_table_schema = cls.tables(
673
+ "some_table", "%s.some_table" % config.test_schema
674
+ )
675
+ connection.execute(some_table_schema.insert(), {"id": 1})
676
+ connection.execute(some_table.insert(), {"id": 1, "some_table_id": 1})
677
+
678
+ def test_simple_join_both_tables(self, connection):
679
+ some_table, some_table_schema = self.tables(
680
+ "some_table", "%s.some_table" % config.test_schema
681
+ )
682
+
683
+ eq_(
684
+ connection.execute(
685
+ select(some_table, some_table_schema).join_from(
686
+ some_table,
687
+ some_table_schema,
688
+ some_table.c.some_table_id == some_table_schema.c.id,
689
+ )
690
+ ).first(),
691
+ (1, 1, 1),
692
+ )
693
+
694
+ def test_simple_join_whereclause_only(self, connection):
695
+ some_table, some_table_schema = self.tables(
696
+ "some_table", "%s.some_table" % config.test_schema
697
+ )
698
+
699
+ eq_(
700
+ connection.execute(
701
+ select(some_table)
702
+ .join_from(
703
+ some_table,
704
+ some_table_schema,
705
+ some_table.c.some_table_id == some_table_schema.c.id,
706
+ )
707
+ .where(some_table.c.id == 1)
708
+ ).first(),
709
+ (1, 1),
710
+ )
711
+
712
+ def test_subquery(self, connection):
713
+ some_table, some_table_schema = self.tables(
714
+ "some_table", "%s.some_table" % config.test_schema
715
+ )
716
+
717
+ subq = (
718
+ select(some_table)
719
+ .join_from(
720
+ some_table,
721
+ some_table_schema,
722
+ some_table.c.some_table_id == some_table_schema.c.id,
723
+ )
724
+ .where(some_table.c.id == 1)
725
+ .subquery()
726
+ )
727
+
728
+ eq_(
729
+ connection.execute(
730
+ select(some_table, subq.c.id)
731
+ .join_from(
732
+ some_table,
733
+ subq,
734
+ some_table.c.some_table_id == subq.c.id,
735
+ )
736
+ .where(some_table.c.id == 1)
737
+ ).first(),
738
+ (1, 1, 1),
739
+ )
740
+
741
+
742
+ class JoinTest(fixtures.TablesTest):
743
+ __sparse_driver_backend__ = True
744
+
745
+ def _assert_result(self, select, result, params=None):
746
+ with config.db.connect() as conn:
747
+ eq_(conn.execute(select, params).fetchall(), result)
748
+
749
+ @classmethod
750
+ def define_tables(cls, metadata):
751
+ Table("a", metadata, Column("id", Integer, primary_key=True))
752
+ Table(
753
+ "b",
754
+ metadata,
755
+ Column("id", Integer, primary_key=True),
756
+ Column("a_id", ForeignKey("a.id"), nullable=False),
757
+ )
758
+
759
+ @classmethod
760
+ def insert_data(cls, connection):
761
+ connection.execute(
762
+ cls.tables.a.insert(),
763
+ [{"id": 1}, {"id": 2}, {"id": 3}, {"id": 4}, {"id": 5}],
764
+ )
765
+
766
+ connection.execute(
767
+ cls.tables.b.insert(),
768
+ [
769
+ {"id": 1, "a_id": 1},
770
+ {"id": 2, "a_id": 1},
771
+ {"id": 4, "a_id": 2},
772
+ {"id": 5, "a_id": 3},
773
+ ],
774
+ )
775
+
776
+ def test_inner_join_fk(self):
777
+ a, b = self.tables("a", "b")
778
+
779
+ stmt = select(a, b).select_from(a.join(b)).order_by(a.c.id, b.c.id)
780
+
781
+ self._assert_result(stmt, [(1, 1, 1), (1, 2, 1), (2, 4, 2), (3, 5, 3)])
782
+
783
+ def test_inner_join_true(self):
784
+ a, b = self.tables("a", "b")
785
+
786
+ stmt = (
787
+ select(a, b)
788
+ .select_from(a.join(b, true()))
789
+ .order_by(a.c.id, b.c.id)
790
+ )
791
+
792
+ self._assert_result(
793
+ stmt,
794
+ [
795
+ (a, b, c)
796
+ for (a,), (b, c) in itertools.product(
797
+ [(1,), (2,), (3,), (4,), (5,)],
798
+ [(1, 1), (2, 1), (4, 2), (5, 3)],
799
+ )
800
+ ],
801
+ )
802
+
803
+ def test_inner_join_false(self):
804
+ a, b = self.tables("a", "b")
805
+
806
+ stmt = (
807
+ select(a, b)
808
+ .select_from(a.join(b, false()))
809
+ .order_by(a.c.id, b.c.id)
810
+ )
811
+
812
+ self._assert_result(stmt, [])
813
+
814
+ def test_outer_join_false(self):
815
+ a, b = self.tables("a", "b")
816
+
817
+ stmt = (
818
+ select(a, b)
819
+ .select_from(a.outerjoin(b, false()))
820
+ .order_by(a.c.id, b.c.id)
821
+ )
822
+
823
+ self._assert_result(
824
+ stmt,
825
+ [
826
+ (1, None, None),
827
+ (2, None, None),
828
+ (3, None, None),
829
+ (4, None, None),
830
+ (5, None, None),
831
+ ],
832
+ )
833
+
834
+ def test_outer_join_fk(self):
835
+ a, b = self.tables("a", "b")
836
+
837
+ stmt = select(a, b).select_from(a.join(b)).order_by(a.c.id, b.c.id)
838
+
839
+ self._assert_result(stmt, [(1, 1, 1), (1, 2, 1), (2, 4, 2), (3, 5, 3)])
840
+
841
+
842
+ class CompoundSelectTest(fixtures.TablesTest):
843
+ __sparse_driver_backend__ = True
844
+
845
+ @classmethod
846
+ def define_tables(cls, metadata):
847
+ Table(
848
+ "some_table",
849
+ metadata,
850
+ Column("id", Integer, primary_key=True),
851
+ Column("x", Integer),
852
+ Column("y", Integer),
853
+ )
854
+
855
+ @classmethod
856
+ def insert_data(cls, connection):
857
+ connection.execute(
858
+ cls.tables.some_table.insert(),
859
+ [
860
+ {"id": 1, "x": 1, "y": 2},
861
+ {"id": 2, "x": 2, "y": 3},
862
+ {"id": 3, "x": 3, "y": 4},
863
+ {"id": 4, "x": 4, "y": 5},
864
+ ],
865
+ )
866
+
867
+ def _assert_result(self, select, result, params=None):
868
+ with config.db.connect() as conn:
869
+ eq_(conn.execute(select, params).fetchall(), result)
870
+
871
+ def test_plain_union(self):
872
+ table = self.tables.some_table
873
+ s1 = select(table).where(table.c.id == 2)
874
+ s2 = select(table).where(table.c.id == 3)
875
+
876
+ u1 = union(s1, s2)
877
+ self._assert_result(
878
+ u1.order_by(u1.selected_columns.id), [(2, 2, 3), (3, 3, 4)]
879
+ )
880
+
881
+ def test_select_from_plain_union(self):
882
+ table = self.tables.some_table
883
+ s1 = select(table).where(table.c.id == 2)
884
+ s2 = select(table).where(table.c.id == 3)
885
+
886
+ u1 = union(s1, s2).alias().select()
887
+ self._assert_result(
888
+ u1.order_by(u1.selected_columns.id), [(2, 2, 3), (3, 3, 4)]
889
+ )
890
+
891
+ @testing.requires.order_by_col_from_union
892
+ @testing.requires.parens_in_union_contained_select_w_limit_offset
893
+ def test_limit_offset_selectable_in_unions(self):
894
+ table = self.tables.some_table
895
+ s1 = select(table).where(table.c.id == 2).limit(1).order_by(table.c.id)
896
+ s2 = select(table).where(table.c.id == 3).limit(1).order_by(table.c.id)
897
+
898
+ u1 = union(s1, s2).limit(2)
899
+ self._assert_result(
900
+ u1.order_by(u1.selected_columns.id), [(2, 2, 3), (3, 3, 4)]
901
+ )
902
+
903
+ @testing.requires.parens_in_union_contained_select_wo_limit_offset
904
+ def test_order_by_selectable_in_unions(self):
905
+ table = self.tables.some_table
906
+ s1 = select(table).where(table.c.id == 2).order_by(table.c.id)
907
+ s2 = select(table).where(table.c.id == 3).order_by(table.c.id)
908
+
909
+ u1 = union(s1, s2).limit(2)
910
+ self._assert_result(
911
+ u1.order_by(u1.selected_columns.id), [(2, 2, 3), (3, 3, 4)]
912
+ )
913
+
914
+ def test_distinct_selectable_in_unions(self):
915
+ table = self.tables.some_table
916
+ s1 = select(table).where(table.c.id == 2).distinct()
917
+ s2 = select(table).where(table.c.id == 3).distinct()
918
+
919
+ u1 = union(s1, s2).limit(2)
920
+ self._assert_result(
921
+ u1.order_by(u1.selected_columns.id), [(2, 2, 3), (3, 3, 4)]
922
+ )
923
+
924
+ @testing.requires.parens_in_union_contained_select_w_limit_offset
925
+ def test_limit_offset_in_unions_from_alias(self):
926
+ table = self.tables.some_table
927
+ s1 = select(table).where(table.c.id == 2).limit(1).order_by(table.c.id)
928
+ s2 = select(table).where(table.c.id == 3).limit(1).order_by(table.c.id)
929
+
930
+ # this necessarily has double parens
931
+ u1 = union(s1, s2).alias()
932
+ self._assert_result(
933
+ u1.select().limit(2).order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)]
934
+ )
935
+
936
+ def test_limit_offset_aliased_selectable_in_unions(self):
937
+ table = self.tables.some_table
938
+ s1 = (
939
+ select(table)
940
+ .where(table.c.id == 2)
941
+ .limit(1)
942
+ .order_by(table.c.id)
943
+ .alias()
944
+ .select()
945
+ )
946
+ s2 = (
947
+ select(table)
948
+ .where(table.c.id == 3)
949
+ .limit(1)
950
+ .order_by(table.c.id)
951
+ .alias()
952
+ .select()
953
+ )
954
+
955
+ u1 = union(s1, s2).limit(2)
956
+ self._assert_result(
957
+ u1.order_by(u1.selected_columns.id), [(2, 2, 3), (3, 3, 4)]
958
+ )
959
+
960
+
961
+ class PostCompileParamsTest(
962
+ AssertsExecutionResults, AssertsCompiledSQL, fixtures.TablesTest
963
+ ):
964
+ __backend__ = True
965
+
966
+ __requires__ = ("standard_cursor_sql",)
967
+
968
+ @classmethod
969
+ def define_tables(cls, metadata):
970
+ Table(
971
+ "some_table",
972
+ metadata,
973
+ Column("id", Integer, primary_key=True),
974
+ Column("x", Integer),
975
+ Column("y", Integer),
976
+ Column("z", String(50)),
977
+ )
978
+
979
+ @classmethod
980
+ def insert_data(cls, connection):
981
+ connection.execute(
982
+ cls.tables.some_table.insert(),
983
+ [
984
+ {"id": 1, "x": 1, "y": 2, "z": "z1"},
985
+ {"id": 2, "x": 2, "y": 3, "z": "z2"},
986
+ {"id": 3, "x": 3, "y": 4, "z": "z3"},
987
+ {"id": 4, "x": 4, "y": 5, "z": "z4"},
988
+ ],
989
+ )
990
+
991
+ def test_compile(self):
992
+ table = self.tables.some_table
993
+
994
+ stmt = select(table.c.id).where(
995
+ table.c.x == bindparam("q", literal_execute=True)
996
+ )
997
+
998
+ self.assert_compile(
999
+ stmt,
1000
+ "SELECT some_table.id FROM some_table "
1001
+ "WHERE some_table.x = __[POSTCOMPILE_q]",
1002
+ {},
1003
+ )
1004
+
1005
+ def test_compile_literal_binds(self):
1006
+ table = self.tables.some_table
1007
+
1008
+ stmt = select(table.c.id).where(
1009
+ table.c.x == bindparam("q", 10, literal_execute=True)
1010
+ )
1011
+
1012
+ self.assert_compile(
1013
+ stmt,
1014
+ "SELECT some_table.id FROM some_table WHERE some_table.x = 10",
1015
+ {},
1016
+ literal_binds=True,
1017
+ )
1018
+
1019
+ def test_execute(self):
1020
+ table = self.tables.some_table
1021
+
1022
+ stmt = select(table.c.id).where(
1023
+ table.c.x == bindparam("q", literal_execute=True)
1024
+ )
1025
+
1026
+ with self.sql_execution_asserter() as asserter:
1027
+ with config.db.connect() as conn:
1028
+ conn.execute(stmt, dict(q=10))
1029
+
1030
+ asserter.assert_(
1031
+ CursorSQL(
1032
+ "SELECT some_table.id \nFROM some_table "
1033
+ "\nWHERE some_table.x = 10",
1034
+ () if config.db.dialect.positional else {},
1035
+ )
1036
+ )
1037
+
1038
+ def test_execute_expanding_plus_literal_execute(self):
1039
+ table = self.tables.some_table
1040
+
1041
+ stmt = select(table.c.id).where(
1042
+ table.c.x.in_(bindparam("q", expanding=True, literal_execute=True))
1043
+ )
1044
+
1045
+ with self.sql_execution_asserter() as asserter:
1046
+ with config.db.connect() as conn:
1047
+ conn.execute(stmt, dict(q=[5, 6, 7]))
1048
+
1049
+ asserter.assert_(
1050
+ CursorSQL(
1051
+ "SELECT some_table.id \nFROM some_table "
1052
+ "\nWHERE some_table.x IN (5, 6, 7)",
1053
+ () if config.db.dialect.positional else {},
1054
+ )
1055
+ )
1056
+
1057
+ @testing.requires.tuple_in
1058
+ def test_execute_tuple_expanding_plus_literal_execute(self):
1059
+ table = self.tables.some_table
1060
+
1061
+ stmt = select(table.c.id).where(
1062
+ tuple_(table.c.x, table.c.y).in_(
1063
+ bindparam("q", expanding=True, literal_execute=True)
1064
+ )
1065
+ )
1066
+
1067
+ with self.sql_execution_asserter() as asserter:
1068
+ with config.db.connect() as conn:
1069
+ conn.execute(stmt, dict(q=[(5, 10), (12, 18)]))
1070
+
1071
+ asserter.assert_(
1072
+ CursorSQL(
1073
+ "SELECT some_table.id \nFROM some_table "
1074
+ "\nWHERE (some_table.x, some_table.y) "
1075
+ "IN (%s(5, 10), (12, 18))"
1076
+ % ("VALUES " if config.db.dialect.tuple_in_values else ""),
1077
+ () if config.db.dialect.positional else {},
1078
+ )
1079
+ )
1080
+
1081
+ @testing.requires.tuple_in
1082
+ def test_execute_tuple_expanding_plus_literal_heterogeneous_execute(self):
1083
+ table = self.tables.some_table
1084
+
1085
+ stmt = select(table.c.id).where(
1086
+ tuple_(table.c.x, table.c.z).in_(
1087
+ bindparam("q", expanding=True, literal_execute=True)
1088
+ )
1089
+ )
1090
+
1091
+ with self.sql_execution_asserter() as asserter:
1092
+ with config.db.connect() as conn:
1093
+ conn.execute(stmt, dict(q=[(5, "z1"), (12, "z3")]))
1094
+
1095
+ asserter.assert_(
1096
+ CursorSQL(
1097
+ "SELECT some_table.id \nFROM some_table "
1098
+ "\nWHERE (some_table.x, some_table.z) "
1099
+ "IN (%s(5, 'z1'), (12, 'z3'))"
1100
+ % ("VALUES " if config.db.dialect.tuple_in_values else ""),
1101
+ () if config.db.dialect.positional else {},
1102
+ )
1103
+ )
1104
+
1105
+
1106
+ class ExpandingBoundInTest(fixtures.TablesTest):
1107
+ __backend__ = True
1108
+
1109
+ @classmethod
1110
+ def define_tables(cls, metadata):
1111
+ Table(
1112
+ "some_table",
1113
+ metadata,
1114
+ Column("id", Integer, primary_key=True),
1115
+ Column("x", Integer),
1116
+ Column("y", Integer),
1117
+ Column("z", String(50)),
1118
+ )
1119
+
1120
+ @classmethod
1121
+ def insert_data(cls, connection):
1122
+ connection.execute(
1123
+ cls.tables.some_table.insert(),
1124
+ [
1125
+ {"id": 1, "x": 1, "y": 2, "z": "z1"},
1126
+ {"id": 2, "x": 2, "y": 3, "z": "z2"},
1127
+ {"id": 3, "x": 3, "y": 4, "z": "z3"},
1128
+ {"id": 4, "x": 4, "y": 5, "z": "z4"},
1129
+ ],
1130
+ )
1131
+
1132
+ def _assert_result(self, select, result, params=None):
1133
+ with config.db.connect() as conn:
1134
+ eq_(conn.execute(select, params).fetchall(), result)
1135
+
1136
+ def test_multiple_empty_sets_bindparam(self):
1137
+ # test that any anonymous aliasing used by the dialect
1138
+ # is fine with duplicates
1139
+ table = self.tables.some_table
1140
+ stmt = (
1141
+ select(table.c.id)
1142
+ .where(table.c.x.in_(bindparam("q")))
1143
+ .where(table.c.y.in_(bindparam("p")))
1144
+ .order_by(table.c.id)
1145
+ )
1146
+ self._assert_result(stmt, [], params={"q": [], "p": []})
1147
+
1148
+ def test_multiple_empty_sets_direct(self):
1149
+ # test that any anonymous aliasing used by the dialect
1150
+ # is fine with duplicates
1151
+ table = self.tables.some_table
1152
+ stmt = (
1153
+ select(table.c.id)
1154
+ .where(table.c.x.in_([]))
1155
+ .where(table.c.y.in_([]))
1156
+ .order_by(table.c.id)
1157
+ )
1158
+ self._assert_result(stmt, [])
1159
+
1160
+ @testing.requires.tuple_in_w_empty
1161
+ def test_empty_heterogeneous_tuples_bindparam(self):
1162
+ table = self.tables.some_table
1163
+ stmt = (
1164
+ select(table.c.id)
1165
+ .where(tuple_(table.c.x, table.c.z).in_(bindparam("q")))
1166
+ .order_by(table.c.id)
1167
+ )
1168
+ self._assert_result(stmt, [], params={"q": []})
1169
+
1170
+ @testing.requires.tuple_in_w_empty
1171
+ def test_empty_heterogeneous_tuples_direct(self):
1172
+ table = self.tables.some_table
1173
+
1174
+ def go(val, expected):
1175
+ stmt = (
1176
+ select(table.c.id)
1177
+ .where(tuple_(table.c.x, table.c.z).in_(val))
1178
+ .order_by(table.c.id)
1179
+ )
1180
+ self._assert_result(stmt, expected)
1181
+
1182
+ go([], [])
1183
+ go([(2, "z2"), (3, "z3"), (4, "z4")], [(2,), (3,), (4,)])
1184
+ go([], [])
1185
+
1186
+ @testing.requires.tuple_in_w_empty
1187
+ def test_empty_homogeneous_tuples_bindparam(self):
1188
+ table = self.tables.some_table
1189
+ stmt = (
1190
+ select(table.c.id)
1191
+ .where(tuple_(table.c.x, table.c.y).in_(bindparam("q")))
1192
+ .order_by(table.c.id)
1193
+ )
1194
+ self._assert_result(stmt, [], params={"q": []})
1195
+
1196
+ @testing.requires.tuple_in_w_empty
1197
+ def test_empty_homogeneous_tuples_direct(self):
1198
+ table = self.tables.some_table
1199
+
1200
+ def go(val, expected):
1201
+ stmt = (
1202
+ select(table.c.id)
1203
+ .where(tuple_(table.c.x, table.c.y).in_(val))
1204
+ .order_by(table.c.id)
1205
+ )
1206
+ self._assert_result(stmt, expected)
1207
+
1208
+ go([], [])
1209
+ go([(1, 2), (2, 3), (3, 4)], [(1,), (2,), (3,)])
1210
+ go([], [])
1211
+
1212
+ def test_bound_in_scalar_bindparam(self):
1213
+ table = self.tables.some_table
1214
+ stmt = (
1215
+ select(table.c.id)
1216
+ .where(table.c.x.in_(bindparam("q")))
1217
+ .order_by(table.c.id)
1218
+ )
1219
+ self._assert_result(stmt, [(2,), (3,), (4,)], params={"q": [2, 3, 4]})
1220
+
1221
+ def test_bound_in_scalar_direct(self):
1222
+ table = self.tables.some_table
1223
+ stmt = (
1224
+ select(table.c.id)
1225
+ .where(table.c.x.in_([2, 3, 4]))
1226
+ .order_by(table.c.id)
1227
+ )
1228
+ self._assert_result(stmt, [(2,), (3,), (4,)])
1229
+
1230
+ def test_nonempty_in_plus_empty_notin(self):
1231
+ table = self.tables.some_table
1232
+ stmt = (
1233
+ select(table.c.id)
1234
+ .where(table.c.x.in_([2, 3]))
1235
+ .where(table.c.id.not_in([]))
1236
+ .order_by(table.c.id)
1237
+ )
1238
+ self._assert_result(stmt, [(2,), (3,)])
1239
+
1240
+ def test_empty_in_plus_notempty_notin(self):
1241
+ table = self.tables.some_table
1242
+ stmt = (
1243
+ select(table.c.id)
1244
+ .where(table.c.x.in_([]))
1245
+ .where(table.c.id.not_in([2, 3]))
1246
+ .order_by(table.c.id)
1247
+ )
1248
+ self._assert_result(stmt, [])
1249
+
1250
+ def test_typed_str_in(self):
1251
+ """test related to #7292.
1252
+
1253
+ as a type is given to the bound param, there is no ambiguity
1254
+ to the type of element.
1255
+
1256
+ """
1257
+
1258
+ stmt = text(
1259
+ "select id FROM some_table WHERE z IN :q ORDER BY id"
1260
+ ).bindparams(bindparam("q", type_=String, expanding=True))
1261
+ self._assert_result(
1262
+ stmt,
1263
+ [(2,), (3,), (4,)],
1264
+ params={"q": ["z2", "z3", "z4"]},
1265
+ )
1266
+
1267
+ def test_untyped_str_in(self):
1268
+ """test related to #7292.
1269
+
1270
+ for untyped expression, we look at the types of elements.
1271
+ Test for Sequence to detect tuple in. but not strings or bytes!
1272
+ as always....
1273
+
1274
+ """
1275
+
1276
+ stmt = text(
1277
+ "select id FROM some_table WHERE z IN :q ORDER BY id"
1278
+ ).bindparams(bindparam("q", expanding=True))
1279
+ self._assert_result(
1280
+ stmt,
1281
+ [(2,), (3,), (4,)],
1282
+ params={"q": ["z2", "z3", "z4"]},
1283
+ )
1284
+
1285
+ @testing.requires.tuple_in
1286
+ def test_bound_in_two_tuple_bindparam(self):
1287
+ table = self.tables.some_table
1288
+ stmt = (
1289
+ select(table.c.id)
1290
+ .where(tuple_(table.c.x, table.c.y).in_(bindparam("q")))
1291
+ .order_by(table.c.id)
1292
+ )
1293
+ self._assert_result(
1294
+ stmt, [(2,), (3,), (4,)], params={"q": [(2, 3), (3, 4), (4, 5)]}
1295
+ )
1296
+
1297
+ @testing.requires.tuple_in
1298
+ def test_bound_in_two_tuple_direct(self):
1299
+ table = self.tables.some_table
1300
+ stmt = (
1301
+ select(table.c.id)
1302
+ .where(tuple_(table.c.x, table.c.y).in_([(2, 3), (3, 4), (4, 5)]))
1303
+ .order_by(table.c.id)
1304
+ )
1305
+ self._assert_result(stmt, [(2,), (3,), (4,)])
1306
+
1307
+ @testing.requires.tuple_in
1308
+ def test_bound_in_heterogeneous_two_tuple_bindparam(self):
1309
+ table = self.tables.some_table
1310
+ stmt = (
1311
+ select(table.c.id)
1312
+ .where(tuple_(table.c.x, table.c.z).in_(bindparam("q")))
1313
+ .order_by(table.c.id)
1314
+ )
1315
+ self._assert_result(
1316
+ stmt,
1317
+ [(2,), (3,), (4,)],
1318
+ params={"q": [(2, "z2"), (3, "z3"), (4, "z4")]},
1319
+ )
1320
+
1321
+ @testing.requires.tuple_in
1322
+ def test_bound_in_heterogeneous_two_tuple_direct(self):
1323
+ table = self.tables.some_table
1324
+ stmt = (
1325
+ select(table.c.id)
1326
+ .where(
1327
+ tuple_(table.c.x, table.c.z).in_(
1328
+ [(2, "z2"), (3, "z3"), (4, "z4")]
1329
+ )
1330
+ )
1331
+ .order_by(table.c.id)
1332
+ )
1333
+ self._assert_result(
1334
+ stmt,
1335
+ [(2,), (3,), (4,)],
1336
+ )
1337
+
1338
+ @testing.requires.tuple_in
1339
+ def test_bound_in_heterogeneous_two_tuple_text_bindparam(self):
1340
+ # note this becomes ARRAY if we dont use expanding
1341
+ # explicitly right now
1342
+ stmt = text(
1343
+ "select id FROM some_table WHERE (x, z) IN :q ORDER BY id"
1344
+ ).bindparams(bindparam("q", expanding=True))
1345
+ self._assert_result(
1346
+ stmt,
1347
+ [(2,), (3,), (4,)],
1348
+ params={"q": [(2, "z2"), (3, "z3"), (4, "z4")]},
1349
+ )
1350
+
1351
+ @testing.requires.tuple_in
1352
+ def test_bound_in_heterogeneous_two_tuple_typed_bindparam_non_tuple(self):
1353
+ class LikeATuple(collections_abc.Sequence):
1354
+ def __init__(self, *data):
1355
+ self._data = data
1356
+
1357
+ def __iter__(self):
1358
+ return iter(self._data)
1359
+
1360
+ def __getitem__(self, idx):
1361
+ return self._data[idx]
1362
+
1363
+ def __len__(self):
1364
+ return len(self._data)
1365
+
1366
+ stmt = text(
1367
+ "select id FROM some_table WHERE (x, z) IN :q ORDER BY id"
1368
+ ).bindparams(
1369
+ bindparam(
1370
+ "q", type_=TupleType(Integer(), String()), expanding=True
1371
+ )
1372
+ )
1373
+ self._assert_result(
1374
+ stmt,
1375
+ [(2,), (3,), (4,)],
1376
+ params={
1377
+ "q": [
1378
+ LikeATuple(2, "z2"),
1379
+ LikeATuple(3, "z3"),
1380
+ LikeATuple(4, "z4"),
1381
+ ]
1382
+ },
1383
+ )
1384
+
1385
+ @testing.requires.tuple_in
1386
+ def test_bound_in_heterogeneous_two_tuple_text_bindparam_non_tuple(self):
1387
+ # note this becomes ARRAY if we dont use expanding
1388
+ # explicitly right now
1389
+
1390
+ class LikeATuple(collections_abc.Sequence):
1391
+ def __init__(self, *data):
1392
+ self._data = data
1393
+
1394
+ def __iter__(self):
1395
+ return iter(self._data)
1396
+
1397
+ def __getitem__(self, idx):
1398
+ return self._data[idx]
1399
+
1400
+ def __len__(self):
1401
+ return len(self._data)
1402
+
1403
+ stmt = text(
1404
+ "select id FROM some_table WHERE (x, z) IN :q ORDER BY id"
1405
+ ).bindparams(bindparam("q", expanding=True))
1406
+ self._assert_result(
1407
+ stmt,
1408
+ [(2,), (3,), (4,)],
1409
+ params={
1410
+ "q": [
1411
+ LikeATuple(2, "z2"),
1412
+ LikeATuple(3, "z3"),
1413
+ LikeATuple(4, "z4"),
1414
+ ]
1415
+ },
1416
+ )
1417
+
1418
+ def test_empty_set_against_integer_bindparam(self):
1419
+ table = self.tables.some_table
1420
+ stmt = (
1421
+ select(table.c.id)
1422
+ .where(table.c.x.in_(bindparam("q")))
1423
+ .order_by(table.c.id)
1424
+ )
1425
+ self._assert_result(stmt, [], params={"q": []})
1426
+
1427
+ def test_empty_set_against_integer_direct(self):
1428
+ table = self.tables.some_table
1429
+ stmt = select(table.c.id).where(table.c.x.in_([])).order_by(table.c.id)
1430
+ self._assert_result(stmt, [])
1431
+
1432
+ def test_empty_set_against_integer_negation_bindparam(self):
1433
+ table = self.tables.some_table
1434
+ stmt = (
1435
+ select(table.c.id)
1436
+ .where(table.c.x.not_in(bindparam("q")))
1437
+ .order_by(table.c.id)
1438
+ )
1439
+ self._assert_result(stmt, [(1,), (2,), (3,), (4,)], params={"q": []})
1440
+
1441
+ def test_empty_set_against_integer_negation_direct(self):
1442
+ table = self.tables.some_table
1443
+ stmt = (
1444
+ select(table.c.id).where(table.c.x.not_in([])).order_by(table.c.id)
1445
+ )
1446
+ self._assert_result(stmt, [(1,), (2,), (3,), (4,)])
1447
+
1448
+ def test_empty_set_against_string_bindparam(self):
1449
+ table = self.tables.some_table
1450
+ stmt = (
1451
+ select(table.c.id)
1452
+ .where(table.c.z.in_(bindparam("q")))
1453
+ .order_by(table.c.id)
1454
+ )
1455
+ self._assert_result(stmt, [], params={"q": []})
1456
+
1457
+ def test_empty_set_against_string_direct(self):
1458
+ table = self.tables.some_table
1459
+ stmt = select(table.c.id).where(table.c.z.in_([])).order_by(table.c.id)
1460
+ self._assert_result(stmt, [])
1461
+
1462
+ def test_empty_set_against_string_negation_bindparam(self):
1463
+ table = self.tables.some_table
1464
+ stmt = (
1465
+ select(table.c.id)
1466
+ .where(table.c.z.not_in(bindparam("q")))
1467
+ .order_by(table.c.id)
1468
+ )
1469
+ self._assert_result(stmt, [(1,), (2,), (3,), (4,)], params={"q": []})
1470
+
1471
+ def test_empty_set_against_string_negation_direct(self):
1472
+ table = self.tables.some_table
1473
+ stmt = (
1474
+ select(table.c.id).where(table.c.z.not_in([])).order_by(table.c.id)
1475
+ )
1476
+ self._assert_result(stmt, [(1,), (2,), (3,), (4,)])
1477
+
1478
+ def test_null_in_empty_set_is_false_bindparam(self, connection):
1479
+ stmt = select(
1480
+ case(
1481
+ (
1482
+ null().in_(bindparam("foo", value=())),
1483
+ true(),
1484
+ ),
1485
+ else_=false(),
1486
+ )
1487
+ )
1488
+ in_(connection.execute(stmt).fetchone()[0], (False, 0))
1489
+
1490
+ def test_null_in_empty_set_is_false_direct(self, connection):
1491
+ stmt = select(
1492
+ case(
1493
+ (
1494
+ null().in_([]),
1495
+ true(),
1496
+ ),
1497
+ else_=false(),
1498
+ )
1499
+ )
1500
+ in_(connection.execute(stmt).fetchone()[0], (False, 0))
1501
+
1502
+
1503
+ class LikeFunctionsTest(fixtures.TablesTest):
1504
+ __sparse_driver_backend__ = True
1505
+
1506
+ run_inserts = "once"
1507
+ run_deletes = None
1508
+
1509
+ @classmethod
1510
+ def define_tables(cls, metadata):
1511
+ Table(
1512
+ "some_table",
1513
+ metadata,
1514
+ Column("id", Integer, primary_key=True),
1515
+ Column("data", String(50)),
1516
+ )
1517
+
1518
+ @classmethod
1519
+ def insert_data(cls, connection):
1520
+ connection.execute(
1521
+ cls.tables.some_table.insert(),
1522
+ [
1523
+ {"id": 1, "data": "abcdefg"},
1524
+ {"id": 2, "data": "ab/cdefg"},
1525
+ {"id": 3, "data": "ab%cdefg"},
1526
+ {"id": 4, "data": "ab_cdefg"},
1527
+ {"id": 5, "data": "abcde/fg"},
1528
+ {"id": 6, "data": "abcde%fg"},
1529
+ {"id": 7, "data": "ab#cdefg"},
1530
+ {"id": 8, "data": "ab9cdefg"},
1531
+ {"id": 9, "data": "abcde#fg"},
1532
+ {"id": 10, "data": "abcd9fg"},
1533
+ {"id": 11, "data": None},
1534
+ ],
1535
+ )
1536
+
1537
+ def _test(self, expr, expected):
1538
+ some_table = self.tables.some_table
1539
+
1540
+ with config.db.connect() as conn:
1541
+ rows = {
1542
+ value
1543
+ for value, in conn.execute(select(some_table.c.id).where(expr))
1544
+ }
1545
+
1546
+ eq_(rows, expected)
1547
+
1548
+ def test_startswith_unescaped(self):
1549
+ col = self.tables.some_table.c.data
1550
+ self._test(col.startswith("ab%c"), {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
1551
+
1552
+ @testing.requires.like_escapes
1553
+ def test_startswith_autoescape(self):
1554
+ col = self.tables.some_table.c.data
1555
+ self._test(col.startswith("ab%c", autoescape=True), {3})
1556
+
1557
+ def test_startswith_sqlexpr(self):
1558
+ col = self.tables.some_table.c.data
1559
+ self._test(
1560
+ col.startswith(literal_column("'ab%c'")),
1561
+ {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
1562
+ )
1563
+
1564
+ @testing.requires.like_escapes
1565
+ def test_startswith_escape(self):
1566
+ col = self.tables.some_table.c.data
1567
+ self._test(col.startswith("ab##c", escape="#"), {7})
1568
+
1569
+ @testing.requires.like_escapes
1570
+ def test_startswith_autoescape_escape(self):
1571
+ col = self.tables.some_table.c.data
1572
+ self._test(col.startswith("ab%c", autoescape=True, escape="#"), {3})
1573
+ self._test(col.startswith("ab#c", autoescape=True, escape="#"), {7})
1574
+
1575
+ def test_endswith_unescaped(self):
1576
+ col = self.tables.some_table.c.data
1577
+ self._test(col.endswith("e%fg"), {1, 2, 3, 4, 5, 6, 7, 8, 9})
1578
+
1579
+ def test_endswith_sqlexpr(self):
1580
+ col = self.tables.some_table.c.data
1581
+ self._test(
1582
+ col.endswith(literal_column("'e%fg'")), {1, 2, 3, 4, 5, 6, 7, 8, 9}
1583
+ )
1584
+
1585
+ @testing.requires.like_escapes
1586
+ def test_endswith_autoescape(self):
1587
+ col = self.tables.some_table.c.data
1588
+ self._test(col.endswith("e%fg", autoescape=True), {6})
1589
+
1590
+ @testing.requires.like_escapes
1591
+ def test_endswith_escape(self):
1592
+ col = self.tables.some_table.c.data
1593
+ self._test(col.endswith("e##fg", escape="#"), {9})
1594
+
1595
+ @testing.requires.like_escapes
1596
+ def test_endswith_autoescape_escape(self):
1597
+ col = self.tables.some_table.c.data
1598
+ self._test(col.endswith("e%fg", autoescape=True, escape="#"), {6})
1599
+ self._test(col.endswith("e#fg", autoescape=True, escape="#"), {9})
1600
+
1601
+ def test_contains_unescaped(self):
1602
+ col = self.tables.some_table.c.data
1603
+ self._test(col.contains("b%cde"), {1, 2, 3, 4, 5, 6, 7, 8, 9})
1604
+
1605
+ @testing.requires.like_escapes
1606
+ def test_contains_autoescape(self):
1607
+ col = self.tables.some_table.c.data
1608
+ self._test(col.contains("b%cde", autoescape=True), {3})
1609
+
1610
+ @testing.requires.like_escapes
1611
+ def test_contains_escape(self):
1612
+ col = self.tables.some_table.c.data
1613
+ self._test(col.contains("b##cde", escape="#"), {7})
1614
+
1615
+ @testing.requires.like_escapes
1616
+ def test_contains_autoescape_escape(self):
1617
+ col = self.tables.some_table.c.data
1618
+ self._test(col.contains("b%cd", autoescape=True, escape="#"), {3})
1619
+ self._test(col.contains("b#cd", autoescape=True, escape="#"), {7})
1620
+
1621
+ @testing.requires.regexp_match
1622
+ def test_not_regexp_match(self):
1623
+ col = self.tables.some_table.c.data
1624
+ self._test(~col.regexp_match("a.cde"), {2, 3, 4, 7, 8, 10})
1625
+
1626
+ @testing.requires.regexp_replace
1627
+ def test_regexp_replace(self):
1628
+ col = self.tables.some_table.c.data
1629
+ self._test(
1630
+ col.regexp_replace("a.cde", "FOO").contains("FOO"), {1, 5, 6, 9}
1631
+ )
1632
+
1633
+ @testing.requires.regexp_match
1634
+ @testing.combinations(
1635
+ ("a.cde", {1, 5, 6, 9}),
1636
+ ("abc", {1, 5, 6, 9, 10}),
1637
+ ("^abc", {1, 5, 6, 9, 10}),
1638
+ ("9cde", {8}),
1639
+ ("^a", set(range(1, 11))),
1640
+ ("(b|c)", set(range(1, 11))),
1641
+ ("^(b|c)", set()),
1642
+ )
1643
+ def test_regexp_match(self, text, expected):
1644
+ col = self.tables.some_table.c.data
1645
+ self._test(col.regexp_match(text), expected)
1646
+
1647
+
1648
+ class ComputedColumnTest(fixtures.TablesTest):
1649
+ __sparse_driver_backend__ = True
1650
+ __requires__ = ("computed_columns",)
1651
+
1652
+ @classmethod
1653
+ def define_tables(cls, metadata):
1654
+ Table(
1655
+ "square",
1656
+ metadata,
1657
+ Column("id", Integer, primary_key=True),
1658
+ Column("side", Integer),
1659
+ Column("area", Integer, Computed("side * side")),
1660
+ Column("perimeter", Integer, Computed("4 * side")),
1661
+ )
1662
+
1663
+ @classmethod
1664
+ def insert_data(cls, connection):
1665
+ connection.execute(
1666
+ cls.tables.square.insert(),
1667
+ [{"id": 1, "side": 10}, {"id": 10, "side": 42}],
1668
+ )
1669
+
1670
+ def test_select_all(self):
1671
+ with config.db.connect() as conn:
1672
+ res = conn.execute(
1673
+ select(text("*"))
1674
+ .select_from(self.tables.square)
1675
+ .order_by(self.tables.square.c.id)
1676
+ ).fetchall()
1677
+ eq_(res, [(1, 10, 100, 40), (10, 42, 1764, 168)])
1678
+
1679
+ def test_select_columns(self):
1680
+ with config.db.connect() as conn:
1681
+ res = conn.execute(
1682
+ select(
1683
+ self.tables.square.c.area, self.tables.square.c.perimeter
1684
+ )
1685
+ .select_from(self.tables.square)
1686
+ .order_by(self.tables.square.c.id)
1687
+ ).fetchall()
1688
+ eq_(res, [(100, 40), (1764, 168)])
1689
+
1690
+
1691
+ class IdentityColumnTest(fixtures.TablesTest):
1692
+ __backend__ = True
1693
+ __requires__ = ("identity_columns",)
1694
+ run_inserts = "once"
1695
+ run_deletes = "once"
1696
+
1697
+ @classmethod
1698
+ def define_tables(cls, metadata):
1699
+ Table(
1700
+ "tbl_a",
1701
+ metadata,
1702
+ Column(
1703
+ "id",
1704
+ Integer,
1705
+ Identity(
1706
+ always=True, start=42, nominvalue=True, nomaxvalue=True
1707
+ ),
1708
+ primary_key=True,
1709
+ ),
1710
+ Column("desc", String(100)),
1711
+ )
1712
+ Table(
1713
+ "tbl_b",
1714
+ metadata,
1715
+ Column(
1716
+ "id",
1717
+ Integer,
1718
+ Identity(increment=-5, start=0, minvalue=-1000, maxvalue=0),
1719
+ primary_key=True,
1720
+ ),
1721
+ Column("desc", String(100)),
1722
+ )
1723
+
1724
+ @classmethod
1725
+ def insert_data(cls, connection):
1726
+ connection.execute(
1727
+ cls.tables.tbl_a.insert(),
1728
+ [{"desc": "a"}, {"desc": "b"}],
1729
+ )
1730
+ connection.execute(
1731
+ cls.tables.tbl_b.insert(),
1732
+ [{"desc": "a"}, {"desc": "b"}],
1733
+ )
1734
+ connection.execute(
1735
+ cls.tables.tbl_b.insert(),
1736
+ [{"id": 42, "desc": "c"}],
1737
+ )
1738
+
1739
+ def test_select_all(self, connection):
1740
+ res = connection.execute(
1741
+ select(text("*"))
1742
+ .select_from(self.tables.tbl_a)
1743
+ .order_by(self.tables.tbl_a.c.id)
1744
+ ).fetchall()
1745
+ eq_(res, [(42, "a"), (43, "b")])
1746
+
1747
+ res = connection.execute(
1748
+ select(text("*"))
1749
+ .select_from(self.tables.tbl_b)
1750
+ .order_by(self.tables.tbl_b.c.id)
1751
+ ).fetchall()
1752
+ eq_(res, [(-5, "b"), (0, "a"), (42, "c")])
1753
+
1754
+ def test_select_columns(self, connection):
1755
+ res = connection.execute(
1756
+ select(self.tables.tbl_a.c.id).order_by(self.tables.tbl_a.c.id)
1757
+ ).fetchall()
1758
+ eq_(res, [(42,), (43,)])
1759
+
1760
+ @testing.requires.identity_columns_standard
1761
+ def test_insert_always_error(self, connection):
1762
+ def fn():
1763
+ connection.execute(
1764
+ self.tables.tbl_a.insert(),
1765
+ [{"id": 200, "desc": "a"}],
1766
+ )
1767
+
1768
+ assert_raises((DatabaseError, ProgrammingError), fn)
1769
+
1770
+
1771
+ class IdentityAutoincrementTest(fixtures.TablesTest):
1772
+ __backend__ = True
1773
+ __requires__ = ("autoincrement_without_sequence",)
1774
+
1775
+ @classmethod
1776
+ def define_tables(cls, metadata):
1777
+ Table(
1778
+ "tbl",
1779
+ metadata,
1780
+ Column(
1781
+ "id",
1782
+ Integer,
1783
+ Identity(),
1784
+ primary_key=True,
1785
+ autoincrement=True,
1786
+ ),
1787
+ Column("desc", String(100)),
1788
+ )
1789
+
1790
+ def test_autoincrement_with_identity(self, connection):
1791
+ connection.execute(self.tables.tbl.insert(), {"desc": "row"})
1792
+ res = connection.execute(self.tables.tbl.select()).first()
1793
+ eq_(res, (1, "row"))
1794
+
1795
+
1796
+ class ExistsTest(fixtures.TablesTest):
1797
+ __sparse_driver_backend__ = True
1798
+
1799
+ @classmethod
1800
+ def define_tables(cls, metadata):
1801
+ Table(
1802
+ "stuff",
1803
+ metadata,
1804
+ Column("id", Integer, primary_key=True),
1805
+ Column("data", String(50)),
1806
+ )
1807
+
1808
+ @classmethod
1809
+ def insert_data(cls, connection):
1810
+ connection.execute(
1811
+ cls.tables.stuff.insert(),
1812
+ [
1813
+ {"id": 1, "data": "some data"},
1814
+ {"id": 2, "data": "some data"},
1815
+ {"id": 3, "data": "some data"},
1816
+ {"id": 4, "data": "some other data"},
1817
+ ],
1818
+ )
1819
+
1820
+ def test_select_exists(self, connection):
1821
+ stuff = self.tables.stuff
1822
+ eq_(
1823
+ connection.execute(
1824
+ select(literal(1)).where(
1825
+ exists().where(stuff.c.data == "some data")
1826
+ )
1827
+ ).fetchall(),
1828
+ [(1,)],
1829
+ )
1830
+
1831
+ def test_select_exists_false(self, connection):
1832
+ stuff = self.tables.stuff
1833
+ eq_(
1834
+ connection.execute(
1835
+ select(literal(1)).where(
1836
+ exists().where(stuff.c.data == "no data")
1837
+ )
1838
+ ).fetchall(),
1839
+ [],
1840
+ )
1841
+
1842
+
1843
+ class DistinctOnTest(AssertsCompiledSQL, fixtures.TablesTest):
1844
+ __sparse_driver_backend__ = True
1845
+
1846
+ @testing.fails_if(testing.requires.supports_distinct_on)
1847
+ def test_distinct_on(self):
1848
+ with testing.expect_deprecated(
1849
+ "Passing expression to ``distinct`` to generate "
1850
+ ):
1851
+ stm = select("*").distinct(column("q")).select_from(table("foo"))
1852
+ with testing.expect_deprecated(
1853
+ "DISTINCT ON is currently supported only by the PostgreSQL "
1854
+ ):
1855
+ self.assert_compile(stm, "SELECT DISTINCT * FROM foo")
1856
+
1857
+
1858
+ class IsOrIsNotDistinctFromTest(fixtures.TablesTest):
1859
+ __sparse_driver_backend__ = True
1860
+ __requires__ = ("supports_is_distinct_from",)
1861
+
1862
+ @classmethod
1863
+ def define_tables(cls, metadata):
1864
+ Table(
1865
+ "is_distinct_test",
1866
+ metadata,
1867
+ Column("id", Integer, primary_key=True),
1868
+ Column("col_a", Integer, nullable=True),
1869
+ Column("col_b", Integer, nullable=True),
1870
+ )
1871
+
1872
+ @testing.combinations(
1873
+ ("both_int_different", 0, 1, 1),
1874
+ ("both_int_same", 1, 1, 0),
1875
+ ("one_null_first", None, 1, 1),
1876
+ ("one_null_second", 0, None, 1),
1877
+ ("both_null", None, None, 0),
1878
+ id_="iaaa",
1879
+ argnames="col_a_value, col_b_value, expected_row_count_for_is",
1880
+ )
1881
+ def test_is_or_is_not_distinct_from(
1882
+ self, col_a_value, col_b_value, expected_row_count_for_is, connection
1883
+ ):
1884
+ tbl = self.tables.is_distinct_test
1885
+
1886
+ connection.execute(
1887
+ tbl.insert(),
1888
+ [{"id": 1, "col_a": col_a_value, "col_b": col_b_value}],
1889
+ )
1890
+
1891
+ result = connection.execute(
1892
+ tbl.select().where(tbl.c.col_a.is_distinct_from(tbl.c.col_b))
1893
+ ).fetchall()
1894
+ eq_(
1895
+ len(result),
1896
+ expected_row_count_for_is,
1897
+ )
1898
+
1899
+ expected_row_count_for_is_not = (
1900
+ 1 if expected_row_count_for_is == 0 else 0
1901
+ )
1902
+ result = connection.execute(
1903
+ tbl.select().where(tbl.c.col_a.is_not_distinct_from(tbl.c.col_b))
1904
+ ).fetchall()
1905
+ eq_(
1906
+ len(result),
1907
+ expected_row_count_for_is_not,
1908
+ )
1909
+
1910
+
1911
+ class WindowFunctionTest(fixtures.TablesTest):
1912
+ __requires__ = ("window_functions",)
1913
+
1914
+ __sparse_driver_backend__ = True
1915
+
1916
+ @classmethod
1917
+ def define_tables(cls, metadata):
1918
+ Table(
1919
+ "some_table",
1920
+ metadata,
1921
+ Column("id", Integer, primary_key=True),
1922
+ Column("col1", Integer),
1923
+ Column("col2", Integer),
1924
+ Column("col3", Float),
1925
+ )
1926
+
1927
+ @classmethod
1928
+ def insert_data(cls, connection):
1929
+ def row_factory(i):
1930
+ return {
1931
+ "id": i,
1932
+ "col1": i,
1933
+ "col2": i * 5,
1934
+ "col3": i + 0.5,
1935
+ }
1936
+
1937
+ connection.execute(
1938
+ cls.tables.some_table.insert(),
1939
+ [row_factory(i) for i in range(1, 50)],
1940
+ )
1941
+
1942
+ def test_window(self, connection):
1943
+ some_table = self.tables.some_table
1944
+ rows = connection.execute(
1945
+ select(
1946
+ func.max(some_table.c.col2).over(
1947
+ order_by=[some_table.c.col1.desc()]
1948
+ )
1949
+ ).where(some_table.c.col1 < 20)
1950
+ ).all()
1951
+
1952
+ eq_(rows, [(95,) for i in range(19)])
1953
+
1954
+ @testing.requires.window_range
1955
+ def test_window_range(self, connection):
1956
+ some_table = self.tables.some_table
1957
+ rows = connection.execute(
1958
+ select(
1959
+ func.max(some_table.c.col1).over(
1960
+ partition_by=[some_table.c.col2],
1961
+ order_by=[some_table.c.col2.asc()],
1962
+ range_=(0, 1),
1963
+ )
1964
+ ).where(some_table.c.col1 < 20)
1965
+ ).all()
1966
+
1967
+ eq_(rows, [(i,) for i in range(1, 20)])
1968
+
1969
+ @testing.requires.window_range_numeric
1970
+ def test_window_range_numeric(self, connection):
1971
+ some_table = self.tables.some_table
1972
+ rows = connection.execute(
1973
+ select(
1974
+ func.max(some_table.c.col3).over(
1975
+ partition_by=[some_table.c.col3],
1976
+ order_by=[some_table.c.col3.asc()],
1977
+ range_=FrameClause(
1978
+ 1.25,
1979
+ 1.25,
1980
+ FrameClauseType.PRECEDING,
1981
+ FrameClauseType.FOLLOWING,
1982
+ ),
1983
+ )
1984
+ ).where(some_table.c.col1 < 20)
1985
+ ).all()
1986
+
1987
+ eq_(rows, [(i + 0.5,) for i in range(1, 20)])
1988
+
1989
+ @testing.requires.window_range_non_numeric
1990
+ def test_window_range_dates(self, connection, metadata):
1991
+ t = Table(
1992
+ "range_string",
1993
+ metadata,
1994
+ Column("value", Integer),
1995
+ Column("oder", Date),
1996
+ )
1997
+ t.create(connection)
1998
+ connection.execute(
1999
+ t.insert(),
2000
+ [
2001
+ {"value": 1, "oder": date(2025, 10, 1)},
2002
+ {"value": 2, "oder": date(2025, 10, 2)},
2003
+ {"value": 3, "oder": date(2025, 10, 10)},
2004
+ {"value": 4, "oder": date(2025, 10, 13)},
2005
+ {"value": 5, "oder": date(2025, 10, 16)},
2006
+ ],
2007
+ )
2008
+ rows = connection.execute(
2009
+ select(
2010
+ func.sum(t.c.value).over(
2011
+ order_by=t.c.oder,
2012
+ range_=FrameClause(
2013
+ timedelta(days=7),
2014
+ None,
2015
+ FrameClauseType.PRECEDING,
2016
+ FrameClauseType.CURRENT,
2017
+ ),
2018
+ )
2019
+ ).order_by(t.c.oder)
2020
+ ).all()
2021
+
2022
+ eq_(rows, [(1,), (3,), (3,), (7,), (12,)])
2023
+
2024
+ def test_window_rows_between_w_caching(self, connection):
2025
+ some_table = self.tables.some_table
2026
+
2027
+ # this tests that dialects such as SQL Server which require literal
2028
+ # rendering of ROWS BETWEEN and RANGE BETWEEN numerical values make
2029
+ # use of literal_execute, for post-cache rendering of integer values,
2030
+ # and not literal_binds which would include the integer values in the
2031
+ # cached string (caching overall fixed in #11515)
2032
+ for i in range(3):
2033
+ for rows, expected in [
2034
+ (
2035
+ (5, 20),
2036
+ list(range(105, 245, 5)) + ([245] * 16) + [None] * 5,
2037
+ ),
2038
+ (
2039
+ (20, 30),
2040
+ list(range(155, 245, 5)) + ([245] * 11) + [None] * 20,
2041
+ ),
2042
+ ]:
2043
+ result_rows = connection.execute(
2044
+ select(
2045
+ func.max(some_table.c.col2).over(
2046
+ order_by=[some_table.c.col1],
2047
+ rows=rows,
2048
+ )
2049
+ )
2050
+ ).all()
2051
+
2052
+ eq_(result_rows, [(i,) for i in expected])
2053
+
2054
+
2055
+ class BitwiseTest(fixtures.TablesTest):
2056
+ __backend__ = True
2057
+ run_inserts = run_deletes = "once"
2058
+
2059
+ inserted_data = [{"a": i, "b": i + 1} for i in range(10)]
2060
+
2061
+ @classmethod
2062
+ def define_tables(cls, metadata):
2063
+ Table("bitwise", metadata, Column("a", Integer), Column("b", Integer))
2064
+
2065
+ @classmethod
2066
+ def insert_data(cls, connection):
2067
+ connection.execute(cls.tables.bitwise.insert(), cls.inserted_data)
2068
+
2069
+ @testing.combinations(
2070
+ (
2071
+ lambda a: a.bitwise_xor(5),
2072
+ [i for i in range(10) if i != 5],
2073
+ testing.requires.supports_bitwise_xor,
2074
+ ),
2075
+ (
2076
+ lambda a: a.bitwise_or(1),
2077
+ list(range(10)),
2078
+ testing.requires.supports_bitwise_or,
2079
+ ),
2080
+ (
2081
+ lambda a: a.bitwise_and(4),
2082
+ list(range(4, 8)),
2083
+ testing.requires.supports_bitwise_and,
2084
+ ),
2085
+ (
2086
+ lambda a: (a - 2).bitwise_not(),
2087
+ [0],
2088
+ testing.requires.supports_bitwise_not,
2089
+ ),
2090
+ (
2091
+ lambda a: a.bitwise_lshift(1),
2092
+ list(range(1, 10)),
2093
+ testing.requires.supports_bitwise_shift,
2094
+ ),
2095
+ (
2096
+ lambda a: a.bitwise_rshift(2),
2097
+ list(range(4, 10)),
2098
+ testing.requires.supports_bitwise_shift,
2099
+ ),
2100
+ argnames="case, expected",
2101
+ )
2102
+ def test_bitwise(self, case, expected, connection):
2103
+ tbl = self.tables.bitwise
2104
+
2105
+ a = tbl.c.a
2106
+
2107
+ op = testing.resolve_lambda(case, a=a)
2108
+
2109
+ stmt = select(tbl).where(op > 0).order_by(a)
2110
+
2111
+ res = connection.execute(stmt).mappings().all()
2112
+ eq_(res, [self.inserted_data[i] for i in expected])