SQLAlchemy 2.0.47__cp313-cp313t-win32.whl

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