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,2271 @@
1
+ # testing/suite/test_types.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
+
10
+ import datetime
11
+ import decimal
12
+ import json
13
+ import re
14
+ import uuid
15
+
16
+ from .. import config
17
+ from .. import engines
18
+ from .. import fixtures
19
+ from .. import mock
20
+ from ..assertions import eq_
21
+ from ..assertions import is_
22
+ from ..assertions import ne_
23
+ from ..config import requirements
24
+ from ..schema import Column
25
+ from ..schema import Table
26
+ from ... import and_
27
+ from ... import ARRAY
28
+ from ... import BigInteger
29
+ from ... import bindparam
30
+ from ... import Boolean
31
+ from ... import case
32
+ from ... import cast
33
+ from ... import Date
34
+ from ... import DateTime
35
+ from ... import Enum
36
+ from ... import Float
37
+ from ... import Integer
38
+ from ... import Interval
39
+ from ... import JSON
40
+ from ... import literal
41
+ from ... import literal_column
42
+ from ... import MetaData
43
+ from ... import null
44
+ from ... import Numeric
45
+ from ... import select
46
+ from ... import String
47
+ from ... import testing
48
+ from ... import Text
49
+ from ... import Time
50
+ from ... import TIMESTAMP
51
+ from ... import type_coerce
52
+ from ... import TypeDecorator
53
+ from ... import Unicode
54
+ from ... import UnicodeText
55
+ from ... import UUID
56
+ from ... import Uuid
57
+ from ...orm import declarative_base
58
+ from ...orm import Session
59
+ from ...sql import sqltypes
60
+ from ...sql.sqltypes import LargeBinary
61
+ from ...sql.sqltypes import PickleType
62
+ from ...testing import Variation
63
+
64
+
65
+ class _LiteralRoundTripFixture:
66
+ supports_whereclause = True
67
+
68
+ @testing.fixture
69
+ def literal_round_trip(self, metadata, connection):
70
+ """test literal rendering"""
71
+
72
+ # for literal, we test the literal render in an INSERT
73
+ # into a typed column. we can then SELECT it back as its
74
+ # official type; ideally we'd be able to use CAST here
75
+ # but MySQL in particular can't CAST fully
76
+
77
+ def run(
78
+ type_,
79
+ input_,
80
+ output,
81
+ filter_=None,
82
+ compare=None,
83
+ support_whereclause=True,
84
+ ):
85
+ t = Table("t", metadata, Column("x", type_))
86
+ t.create(connection)
87
+
88
+ for value in input_:
89
+ ins = t.insert().values(
90
+ x=literal(value, type_, literal_execute=True)
91
+ )
92
+ connection.execute(ins)
93
+
94
+ ins = t.insert().values(
95
+ x=literal(None, type_, literal_execute=True)
96
+ )
97
+ connection.execute(ins)
98
+
99
+ if support_whereclause and self.supports_whereclause:
100
+ if compare:
101
+ stmt = t.select().where(
102
+ t.c.x
103
+ == literal(
104
+ compare,
105
+ type_,
106
+ literal_execute=True,
107
+ ),
108
+ t.c.x
109
+ == literal(
110
+ input_[0],
111
+ type_,
112
+ literal_execute=True,
113
+ ),
114
+ )
115
+ else:
116
+ stmt = t.select().where(
117
+ t.c.x
118
+ == literal(
119
+ compare if compare is not None else input_[0],
120
+ type_,
121
+ literal_execute=True,
122
+ )
123
+ )
124
+ else:
125
+ stmt = t.select().where(t.c.x.is_not(None))
126
+
127
+ rows = connection.execute(stmt).all()
128
+ assert rows, "No rows returned"
129
+ for row in rows:
130
+ value = row[0]
131
+ if filter_ is not None:
132
+ value = filter_(value)
133
+ assert value in output
134
+
135
+ stmt = t.select().where(t.c.x.is_(None))
136
+ rows = connection.execute(stmt).all()
137
+ eq_(rows, [(None,)])
138
+
139
+ return run
140
+
141
+
142
+ class _UnicodeFixture(_LiteralRoundTripFixture, fixtures.TestBase):
143
+ __requires__ = ("unicode_data",)
144
+
145
+ data = (
146
+ "Alors vous imaginez ma 🐍 surprise, au lever du jour, "
147
+ "quand une drôle de petite 🐍 voix m’a réveillé. Elle "
148
+ "disait: « S’il vous plaît… dessine-moi 🐍 un mouton! »"
149
+ )
150
+
151
+ @property
152
+ def supports_whereclause(self):
153
+ return config.requirements.expressions_against_unbounded_text.enabled
154
+
155
+ @classmethod
156
+ def define_tables(cls, metadata):
157
+ Table(
158
+ "unicode_table",
159
+ metadata,
160
+ Column(
161
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
162
+ ),
163
+ Column("unicode_data", cls.datatype),
164
+ )
165
+
166
+ def test_round_trip(self, connection):
167
+ unicode_table = self.tables.unicode_table
168
+
169
+ connection.execute(
170
+ unicode_table.insert(), {"id": 1, "unicode_data": self.data}
171
+ )
172
+
173
+ row = connection.execute(select(unicode_table.c.unicode_data)).first()
174
+
175
+ eq_(row, (self.data,))
176
+ assert isinstance(row[0], str)
177
+
178
+ def test_round_trip_executemany(self, connection):
179
+ unicode_table = self.tables.unicode_table
180
+
181
+ connection.execute(
182
+ unicode_table.insert(),
183
+ [{"id": i, "unicode_data": self.data} for i in range(1, 4)],
184
+ )
185
+
186
+ rows = connection.execute(
187
+ select(unicode_table.c.unicode_data)
188
+ ).fetchall()
189
+ eq_(rows, [(self.data,) for i in range(1, 4)])
190
+ for row in rows:
191
+ assert isinstance(row[0], str)
192
+
193
+ def _test_null_strings(self, connection):
194
+ unicode_table = self.tables.unicode_table
195
+
196
+ connection.execute(
197
+ unicode_table.insert(), {"id": 1, "unicode_data": None}
198
+ )
199
+ row = connection.execute(select(unicode_table.c.unicode_data)).first()
200
+ eq_(row, (None,))
201
+
202
+ def _test_empty_strings(self, connection):
203
+ unicode_table = self.tables.unicode_table
204
+
205
+ connection.execute(
206
+ unicode_table.insert(), {"id": 1, "unicode_data": ""}
207
+ )
208
+ row = connection.execute(select(unicode_table.c.unicode_data)).first()
209
+ eq_(row, ("",))
210
+
211
+ def test_literal(self, literal_round_trip):
212
+ literal_round_trip(self.datatype, [self.data], [self.data])
213
+
214
+ def test_literal_non_ascii(self, literal_round_trip):
215
+ literal_round_trip(self.datatype, ["réve🐍 illé"], ["réve🐍 illé"])
216
+
217
+
218
+ class UnicodeVarcharTest(_UnicodeFixture, fixtures.TablesTest):
219
+ __requires__ = ("unicode_data",)
220
+ __backend__ = True
221
+
222
+ datatype = Unicode(255)
223
+
224
+ @requirements.empty_strings_varchar
225
+ def test_empty_strings_varchar(self, connection):
226
+ self._test_empty_strings(connection)
227
+
228
+ def test_null_strings_varchar(self, connection):
229
+ self._test_null_strings(connection)
230
+
231
+
232
+ class UnicodeTextTest(_UnicodeFixture, fixtures.TablesTest):
233
+ __requires__ = "unicode_data", "text_type"
234
+ __backend__ = True
235
+
236
+ datatype = UnicodeText()
237
+
238
+ @requirements.empty_strings_text
239
+ def test_empty_strings_text(self, connection):
240
+ self._test_empty_strings(connection)
241
+
242
+ def test_null_strings_text(self, connection):
243
+ self._test_null_strings(connection)
244
+
245
+
246
+ class ArrayTest(_LiteralRoundTripFixture, fixtures.TablesTest):
247
+ """Add ARRAY test suite, #8138.
248
+
249
+ This only works on PostgreSQL right now.
250
+
251
+ """
252
+
253
+ __requires__ = ("array_type",)
254
+ __backend__ = True
255
+
256
+ @classmethod
257
+ def define_tables(cls, metadata):
258
+ Table(
259
+ "array_table",
260
+ metadata,
261
+ Column(
262
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
263
+ ),
264
+ Column("single_dim", ARRAY(Integer)),
265
+ Column("multi_dim", ARRAY(String, dimensions=2)),
266
+ )
267
+
268
+ def test_array_roundtrip(self, connection):
269
+ array_table = self.tables.array_table
270
+
271
+ connection.execute(
272
+ array_table.insert(),
273
+ {
274
+ "id": 1,
275
+ "single_dim": [1, 2, 3],
276
+ "multi_dim": [["one", "two"], ["thr'ee", "réve🐍 illé"]],
277
+ },
278
+ )
279
+ row = connection.execute(
280
+ select(array_table.c.single_dim, array_table.c.multi_dim)
281
+ ).first()
282
+ eq_(row, ([1, 2, 3], [["one", "two"], ["thr'ee", "réve🐍 illé"]]))
283
+
284
+ def test_literal_simple(self, literal_round_trip):
285
+ literal_round_trip(
286
+ ARRAY(Integer),
287
+ ([1, 2, 3],),
288
+ ([1, 2, 3],),
289
+ support_whereclause=False,
290
+ )
291
+
292
+ def test_literal_complex(self, literal_round_trip):
293
+ literal_round_trip(
294
+ ARRAY(String, dimensions=2),
295
+ ([["one", "two"], ["thr'ee", "réve🐍 illé"]],),
296
+ ([["one", "two"], ["thr'ee", "réve🐍 illé"]],),
297
+ support_whereclause=False,
298
+ )
299
+
300
+
301
+ class BinaryTest(_LiteralRoundTripFixture, fixtures.TablesTest):
302
+ __backend__ = True
303
+ __requires__ = ("binary_literals",)
304
+
305
+ @classmethod
306
+ def define_tables(cls, metadata):
307
+ Table(
308
+ "binary_table",
309
+ metadata,
310
+ Column(
311
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
312
+ ),
313
+ Column("binary_data", LargeBinary),
314
+ Column("pickle_data", PickleType),
315
+ )
316
+
317
+ @testing.combinations(b"this is binary", b"7\xe7\x9f", argnames="data")
318
+ def test_binary_roundtrip(self, connection, data):
319
+ binary_table = self.tables.binary_table
320
+
321
+ connection.execute(
322
+ binary_table.insert(), {"id": 1, "binary_data": data}
323
+ )
324
+ row = connection.execute(select(binary_table.c.binary_data)).first()
325
+ eq_(row, (data,))
326
+
327
+ def test_pickle_roundtrip(self, connection):
328
+ binary_table = self.tables.binary_table
329
+
330
+ connection.execute(
331
+ binary_table.insert(),
332
+ {"id": 1, "pickle_data": {"foo": [1, 2, 3], "bar": "bat"}},
333
+ )
334
+ row = connection.execute(select(binary_table.c.pickle_data)).first()
335
+ eq_(row, ({"foo": [1, 2, 3], "bar": "bat"},))
336
+
337
+
338
+ class TextTest(_LiteralRoundTripFixture, fixtures.TablesTest):
339
+ __requires__ = ("text_type",)
340
+ __backend__ = True
341
+
342
+ @property
343
+ def supports_whereclause(self):
344
+ return config.requirements.expressions_against_unbounded_text.enabled
345
+
346
+ @classmethod
347
+ def define_tables(cls, metadata):
348
+ Table(
349
+ "text_table",
350
+ metadata,
351
+ Column(
352
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
353
+ ),
354
+ Column("text_data", Text),
355
+ )
356
+
357
+ def test_text_roundtrip(self, connection):
358
+ text_table = self.tables.text_table
359
+
360
+ connection.execute(
361
+ text_table.insert(), {"id": 1, "text_data": "some text"}
362
+ )
363
+ row = connection.execute(select(text_table.c.text_data)).first()
364
+ eq_(row, ("some text",))
365
+
366
+ @testing.requires.empty_strings_text
367
+ def test_text_empty_strings(self, connection):
368
+ text_table = self.tables.text_table
369
+
370
+ connection.execute(text_table.insert(), {"id": 1, "text_data": ""})
371
+ row = connection.execute(select(text_table.c.text_data)).first()
372
+ eq_(row, ("",))
373
+
374
+ def test_text_null_strings(self, connection):
375
+ text_table = self.tables.text_table
376
+
377
+ connection.execute(text_table.insert(), {"id": 1, "text_data": None})
378
+ row = connection.execute(select(text_table.c.text_data)).first()
379
+ eq_(row, (None,))
380
+
381
+ def test_literal(self, literal_round_trip):
382
+ literal_round_trip(Text, ["some text"], ["some text"])
383
+
384
+ @requirements.unicode_data_no_special_types
385
+ def test_literal_non_ascii(self, literal_round_trip):
386
+ literal_round_trip(Text, ["réve🐍 illé"], ["réve🐍 illé"])
387
+
388
+ def test_literal_quoting(self, literal_round_trip):
389
+ data = """some 'text' hey "hi there" that's text"""
390
+ literal_round_trip(Text, [data], [data])
391
+
392
+ def test_literal_backslashes(self, literal_round_trip):
393
+ data = r"backslash one \ backslash two \\ end"
394
+ literal_round_trip(Text, [data], [data])
395
+
396
+ def test_literal_percentsigns(self, literal_round_trip):
397
+ data = r"percent % signs %% percent"
398
+ literal_round_trip(Text, [data], [data])
399
+
400
+
401
+ class StringTest(_LiteralRoundTripFixture, fixtures.TestBase):
402
+ __backend__ = True
403
+
404
+ @requirements.unbounded_varchar
405
+ def test_nolength_string(self):
406
+ metadata = MetaData()
407
+ foo = Table("foo", metadata, Column("one", String))
408
+
409
+ foo.create(config.db)
410
+ foo.drop(config.db)
411
+
412
+ def test_literal(self, literal_round_trip):
413
+ # note that in Python 3, this invokes the Unicode
414
+ # datatype for the literal part because all strings are unicode
415
+ literal_round_trip(String(40), ["some text"], ["some text"])
416
+
417
+ @requirements.unicode_data_no_special_types
418
+ def test_literal_non_ascii(self, literal_round_trip):
419
+ literal_round_trip(String(40), ["réve🐍 illé"], ["réve🐍 illé"])
420
+
421
+ @testing.combinations(
422
+ ("%B%", ["AB", "BC"]),
423
+ ("A%C", ["AC"]),
424
+ ("A%C%Z", []),
425
+ argnames="expr, expected",
426
+ )
427
+ def test_dont_truncate_rightside(
428
+ self, metadata, connection, expr, expected
429
+ ):
430
+ t = Table("t", metadata, Column("x", String(2)))
431
+ t.create(connection)
432
+
433
+ connection.execute(t.insert(), [{"x": "AB"}, {"x": "BC"}, {"x": "AC"}])
434
+
435
+ eq_(
436
+ connection.scalars(
437
+ select(t.c.x).where(t.c.x.like(expr)).order_by(t.c.x)
438
+ ).all(),
439
+ expected,
440
+ )
441
+
442
+ def test_literal_quoting(self, literal_round_trip):
443
+ data = """some 'text' hey "hi there" that's text"""
444
+ literal_round_trip(String(40), [data], [data])
445
+
446
+ def test_literal_backslashes(self, literal_round_trip):
447
+ data = r"backslash one \ backslash two \\ end"
448
+ literal_round_trip(String(40), [data], [data])
449
+
450
+ def test_concatenate_binary(self, connection):
451
+ """dialects with special string concatenation operators should
452
+ implement visit_concat_op_binary() and visit_concat_op_clauselist()
453
+ in their compiler.
454
+
455
+ .. versionchanged:: 2.0 visit_concat_op_clauselist() is also needed
456
+ for dialects to override the string concatenation operator.
457
+
458
+ """
459
+ eq_(connection.scalar(select(literal("a") + "b")), "ab")
460
+
461
+ def test_concatenate_clauselist(self, connection):
462
+ """dialects with special string concatenation operators should
463
+ implement visit_concat_op_binary() and visit_concat_op_clauselist()
464
+ in their compiler.
465
+
466
+ .. versionchanged:: 2.0 visit_concat_op_clauselist() is also needed
467
+ for dialects to override the string concatenation operator.
468
+
469
+ """
470
+ eq_(
471
+ connection.scalar(select(literal("a") + "b" + "c" + "d" + "e")),
472
+ "abcde",
473
+ )
474
+
475
+
476
+ class IntervalTest(_LiteralRoundTripFixture, fixtures.TestBase):
477
+ __requires__ = ("datetime_interval",)
478
+ __backend__ = True
479
+
480
+ datatype = Interval
481
+ data = datetime.timedelta(days=1, seconds=4)
482
+
483
+ def test_literal(self, literal_round_trip):
484
+ literal_round_trip(self.datatype, [self.data], [self.data])
485
+
486
+ def test_select_direct_literal_interval(self, connection):
487
+ row = connection.execute(select(literal(self.data))).first()
488
+ eq_(row, (self.data,))
489
+
490
+ def test_arithmetic_operation_literal_interval(self, connection):
491
+ now = datetime.datetime.now().replace(microsecond=0)
492
+ # Able to subtract
493
+ row = connection.execute(
494
+ select(literal(now) - literal(self.data))
495
+ ).scalar()
496
+ eq_(row, now - self.data)
497
+
498
+ # Able to Add
499
+ row = connection.execute(
500
+ select(literal(now) + literal(self.data))
501
+ ).scalar()
502
+ eq_(row, now + self.data)
503
+
504
+ @testing.fixture
505
+ def arithmetic_table_fixture(cls, metadata, connection):
506
+ class Decorated(TypeDecorator):
507
+ impl = cls.datatype
508
+ cache_ok = True
509
+
510
+ it = Table(
511
+ "interval_table",
512
+ metadata,
513
+ Column(
514
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
515
+ ),
516
+ Column("interval_data", cls.datatype),
517
+ Column("date_data", DateTime),
518
+ Column("decorated_interval_data", Decorated),
519
+ )
520
+ it.create(connection)
521
+ return it
522
+
523
+ def test_arithmetic_operation_table_interval_and_literal_interval(
524
+ self, connection, arithmetic_table_fixture
525
+ ):
526
+ interval_table = arithmetic_table_fixture
527
+ data = datetime.timedelta(days=2, seconds=5)
528
+ connection.execute(
529
+ interval_table.insert(), {"id": 1, "interval_data": data}
530
+ )
531
+ # Subtraction Operation
532
+ value = connection.execute(
533
+ select(interval_table.c.interval_data - literal(self.data))
534
+ ).scalar()
535
+ eq_(value, data - self.data)
536
+
537
+ # Addition Operation
538
+ value = connection.execute(
539
+ select(interval_table.c.interval_data + literal(self.data))
540
+ ).scalar()
541
+ eq_(value, data + self.data)
542
+
543
+ def test_arithmetic_operation_table_date_and_literal_interval(
544
+ self, connection, arithmetic_table_fixture
545
+ ):
546
+ interval_table = arithmetic_table_fixture
547
+ now = datetime.datetime.now().replace(microsecond=0)
548
+ connection.execute(
549
+ interval_table.insert(), {"id": 1, "date_data": now}
550
+ )
551
+ # Subtraction Operation
552
+ value = connection.execute(
553
+ select(interval_table.c.date_data - literal(self.data))
554
+ ).scalar()
555
+ eq_(value, (now - self.data))
556
+
557
+ # Addition Operation
558
+ value = connection.execute(
559
+ select(interval_table.c.date_data + literal(self.data))
560
+ ).scalar()
561
+ eq_(value, (now + self.data))
562
+
563
+
564
+ class PrecisionIntervalTest(IntervalTest):
565
+ __requires__ = ("datetime_interval",)
566
+ __backend__ = True
567
+
568
+ datatype = Interval(day_precision=9, second_precision=9)
569
+ data = datetime.timedelta(days=103, seconds=4)
570
+
571
+
572
+ class _DateFixture(_LiteralRoundTripFixture, fixtures.TestBase):
573
+ compare = None
574
+
575
+ @classmethod
576
+ def define_tables(cls, metadata):
577
+ class Decorated(TypeDecorator):
578
+ impl = cls.datatype
579
+ cache_ok = True
580
+
581
+ Table(
582
+ "date_table",
583
+ metadata,
584
+ Column(
585
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
586
+ ),
587
+ Column("date_data", cls.datatype),
588
+ Column("decorated_date_data", Decorated),
589
+ )
590
+
591
+ def test_round_trip(self, connection):
592
+ date_table = self.tables.date_table
593
+
594
+ connection.execute(
595
+ date_table.insert(), {"id": 1, "date_data": self.data}
596
+ )
597
+
598
+ row = connection.execute(select(date_table.c.date_data)).first()
599
+
600
+ compare = self.compare or self.data
601
+ eq_(row, (compare,))
602
+ assert isinstance(row[0], type(compare))
603
+
604
+ def test_round_trip_decorated(self, connection):
605
+ date_table = self.tables.date_table
606
+
607
+ connection.execute(
608
+ date_table.insert(), {"id": 1, "decorated_date_data": self.data}
609
+ )
610
+
611
+ row = connection.execute(
612
+ select(date_table.c.decorated_date_data)
613
+ ).first()
614
+
615
+ compare = self.compare or self.data
616
+ eq_(row, (compare,))
617
+ assert isinstance(row[0], type(compare))
618
+
619
+ def test_null(self, connection):
620
+ date_table = self.tables.date_table
621
+
622
+ connection.execute(date_table.insert(), {"id": 1, "date_data": None})
623
+
624
+ row = connection.execute(select(date_table.c.date_data)).first()
625
+ eq_(row, (None,))
626
+
627
+ @testing.requires.datetime_literals
628
+ def test_literal(self, literal_round_trip):
629
+ compare = self.compare or self.data
630
+
631
+ literal_round_trip(
632
+ self.datatype, [self.data], [compare], compare=compare
633
+ )
634
+
635
+ @testing.requires.standalone_null_binds_whereclause
636
+ def test_null_bound_comparison(self):
637
+ # this test is based on an Oracle issue observed in #4886.
638
+ # passing NULL for an expression that needs to be interpreted as
639
+ # a certain type, does the DBAPI have the info it needs to do this.
640
+ date_table = self.tables.date_table
641
+ with config.db.begin() as conn:
642
+ result = conn.execute(
643
+ date_table.insert(), {"id": 1, "date_data": self.data}
644
+ )
645
+ id_ = result.inserted_primary_key[0]
646
+ stmt = select(date_table.c.id).where(
647
+ case(
648
+ (
649
+ bindparam("foo", type_=self.datatype) != None,
650
+ bindparam("foo", type_=self.datatype),
651
+ ),
652
+ else_=date_table.c.date_data,
653
+ )
654
+ == date_table.c.date_data
655
+ )
656
+
657
+ row = conn.execute(stmt, {"foo": None}).first()
658
+ eq_(row[0], id_)
659
+
660
+
661
+ class DateTimeTest(_DateFixture, fixtures.TablesTest):
662
+ __requires__ = ("datetime",)
663
+ __backend__ = True
664
+ datatype = DateTime
665
+ data = datetime.datetime(2012, 10, 15, 12, 57, 18)
666
+
667
+ @testing.requires.datetime_implicit_bound
668
+ def test_select_direct(self, connection):
669
+ result = connection.scalar(select(literal(self.data)))
670
+ eq_(result, self.data)
671
+
672
+
673
+ class DateTimeTZTest(_DateFixture, fixtures.TablesTest):
674
+ __requires__ = ("datetime_timezone",)
675
+ __backend__ = True
676
+ datatype = DateTime(timezone=True)
677
+ data = datetime.datetime(
678
+ 2012, 10, 15, 12, 57, 18, tzinfo=datetime.timezone.utc
679
+ )
680
+
681
+ @testing.requires.datetime_implicit_bound
682
+ def test_select_direct(self, connection):
683
+ result = connection.scalar(select(literal(self.data)))
684
+ eq_(result, self.data)
685
+
686
+
687
+ class DateTimeMicrosecondsTest(_DateFixture, fixtures.TablesTest):
688
+ __requires__ = ("datetime_microseconds",)
689
+ __backend__ = True
690
+ datatype = DateTime
691
+ data = datetime.datetime(2012, 10, 15, 12, 57, 18, 39642)
692
+
693
+
694
+ class TimestampMicrosecondsTest(_DateFixture, fixtures.TablesTest):
695
+ __requires__ = ("timestamp_microseconds",)
696
+ __backend__ = True
697
+ datatype = TIMESTAMP
698
+ data = datetime.datetime(2012, 10, 15, 12, 57, 18, 396)
699
+
700
+ @testing.requires.timestamp_microseconds_implicit_bound
701
+ def test_select_direct(self, connection):
702
+ result = connection.scalar(select(literal(self.data)))
703
+ eq_(result, self.data)
704
+
705
+
706
+ class TimeTest(_DateFixture, fixtures.TablesTest):
707
+ __requires__ = ("time",)
708
+ __backend__ = True
709
+ datatype = Time
710
+ data = datetime.time(12, 57, 18)
711
+
712
+ @testing.requires.time_implicit_bound
713
+ def test_select_direct(self, connection):
714
+ result = connection.scalar(select(literal(self.data)))
715
+ eq_(result, self.data)
716
+
717
+
718
+ class TimeTZTest(_DateFixture, fixtures.TablesTest):
719
+ __requires__ = ("time_timezone",)
720
+ __backend__ = True
721
+ datatype = Time(timezone=True)
722
+ data = datetime.time(12, 57, 18, tzinfo=datetime.timezone.utc)
723
+
724
+ @testing.requires.time_implicit_bound
725
+ def test_select_direct(self, connection):
726
+ result = connection.scalar(select(literal(self.data)))
727
+ eq_(result, self.data)
728
+
729
+
730
+ class TimeMicrosecondsTest(_DateFixture, fixtures.TablesTest):
731
+ __requires__ = ("time_microseconds",)
732
+ __backend__ = True
733
+ datatype = Time
734
+ data = datetime.time(12, 57, 18, 396)
735
+
736
+ @testing.requires.time_implicit_bound
737
+ def test_select_direct(self, connection):
738
+ result = connection.scalar(select(literal(self.data)))
739
+ eq_(result, self.data)
740
+
741
+
742
+ class DateTest(_DateFixture, fixtures.TablesTest):
743
+ __requires__ = ("date",)
744
+ __backend__ = True
745
+ datatype = Date
746
+ data = datetime.date(2012, 10, 15)
747
+
748
+ @testing.requires.date_implicit_bound
749
+ def test_select_direct(self, connection):
750
+ result = connection.scalar(select(literal(self.data)))
751
+ eq_(result, self.data)
752
+
753
+
754
+ class DateTimeCoercedToDateTimeTest(_DateFixture, fixtures.TablesTest):
755
+ """this particular suite is testing that datetime parameters get
756
+ coerced to dates, which tends to be something DBAPIs do.
757
+
758
+ """
759
+
760
+ __requires__ = "date", "date_coerces_from_datetime"
761
+ __backend__ = True
762
+ datatype = Date
763
+ data = datetime.datetime(2012, 10, 15, 12, 57, 18)
764
+ compare = datetime.date(2012, 10, 15)
765
+
766
+ @testing.requires.datetime_implicit_bound
767
+ def test_select_direct(self, connection):
768
+ result = connection.scalar(select(literal(self.data)))
769
+ eq_(result, self.data)
770
+
771
+
772
+ class DateTimeHistoricTest(_DateFixture, fixtures.TablesTest):
773
+ __requires__ = ("datetime_historic",)
774
+ __backend__ = True
775
+ datatype = DateTime
776
+ data = datetime.datetime(1850, 11, 10, 11, 52, 35)
777
+
778
+ @testing.requires.date_implicit_bound
779
+ def test_select_direct(self, connection):
780
+ result = connection.scalar(select(literal(self.data)))
781
+ eq_(result, self.data)
782
+
783
+
784
+ class DateHistoricTest(_DateFixture, fixtures.TablesTest):
785
+ __requires__ = ("date_historic",)
786
+ __backend__ = True
787
+ datatype = Date
788
+ data = datetime.date(1727, 4, 1)
789
+
790
+ @testing.requires.date_implicit_bound
791
+ def test_select_direct(self, connection):
792
+ result = connection.scalar(select(literal(self.data)))
793
+ eq_(result, self.data)
794
+
795
+
796
+ class IntegerTest(_LiteralRoundTripFixture, fixtures.TestBase):
797
+ __backend__ = True
798
+
799
+ def test_literal(self, literal_round_trip):
800
+ literal_round_trip(Integer, [5], [5])
801
+
802
+ def _huge_ints():
803
+ return testing.combinations(
804
+ 2147483649, # 32 bits
805
+ 2147483648, # 32 bits
806
+ 2147483647, # 31 bits
807
+ 2147483646, # 31 bits
808
+ -2147483649, # 32 bits
809
+ -2147483648, # 32 interestingly, asyncpg accepts this one as int32
810
+ -2147483647, # 31
811
+ -2147483646, # 31
812
+ 0,
813
+ 1376537018368127,
814
+ -1376537018368127,
815
+ argnames="intvalue",
816
+ )
817
+
818
+ @_huge_ints()
819
+ def test_huge_int_auto_accommodation(self, connection, intvalue):
820
+ """test #7909"""
821
+
822
+ eq_(
823
+ connection.scalar(
824
+ select(intvalue).where(literal(intvalue) == intvalue)
825
+ ),
826
+ intvalue,
827
+ )
828
+
829
+ @_huge_ints()
830
+ def test_huge_int(self, integer_round_trip, intvalue):
831
+ integer_round_trip(BigInteger, intvalue)
832
+
833
+ @testing.fixture
834
+ def integer_round_trip(self, metadata, connection):
835
+ def run(datatype, data):
836
+ int_table = Table(
837
+ "integer_table",
838
+ metadata,
839
+ Column(
840
+ "id",
841
+ Integer,
842
+ primary_key=True,
843
+ test_needs_autoincrement=True,
844
+ ),
845
+ Column("integer_data", datatype),
846
+ )
847
+
848
+ metadata.create_all(config.db)
849
+
850
+ connection.execute(
851
+ int_table.insert(), {"id": 1, "integer_data": data}
852
+ )
853
+
854
+ row = connection.execute(select(int_table.c.integer_data)).first()
855
+
856
+ eq_(row, (data,))
857
+
858
+ assert isinstance(row[0], int)
859
+
860
+ return run
861
+
862
+
863
+ class CastTypeDecoratorTest(_LiteralRoundTripFixture, fixtures.TestBase):
864
+ __backend__ = True
865
+
866
+ @testing.fixture
867
+ def string_as_int(self):
868
+ class StringAsInt(TypeDecorator):
869
+ impl = String(50)
870
+ cache_ok = True
871
+
872
+ def column_expression(self, col):
873
+ return cast(col, Integer)
874
+
875
+ def bind_expression(self, col):
876
+ return cast(type_coerce(col, Integer), String(50))
877
+
878
+ return StringAsInt()
879
+
880
+ def test_special_type(self, metadata, connection, string_as_int):
881
+ type_ = string_as_int
882
+
883
+ t = Table("t", metadata, Column("x", type_))
884
+ t.create(connection)
885
+
886
+ connection.execute(t.insert(), [{"x": x} for x in [1, 2, 3]])
887
+
888
+ result = {row[0] for row in connection.execute(t.select())}
889
+ eq_(result, {1, 2, 3})
890
+
891
+ result = {
892
+ row[0] for row in connection.execute(t.select().where(t.c.x == 2))
893
+ }
894
+ eq_(result, {2})
895
+
896
+
897
+ class TrueDivTest(fixtures.TestBase):
898
+ __backend__ = True
899
+
900
+ @testing.combinations(
901
+ ("15", "10", 1.5),
902
+ ("-15", "10", -1.5),
903
+ argnames="left, right, expected",
904
+ )
905
+ def test_truediv_integer(self, connection, left, right, expected):
906
+ """test #4926"""
907
+
908
+ eq_(
909
+ connection.scalar(
910
+ select(
911
+ literal_column(left, type_=Integer())
912
+ / literal_column(right, type_=Integer())
913
+ )
914
+ ),
915
+ expected,
916
+ )
917
+
918
+ @testing.combinations(
919
+ ("15", "10", 1), ("-15", "5", -3), argnames="left, right, expected"
920
+ )
921
+ def test_floordiv_integer(self, connection, left, right, expected):
922
+ """test #4926"""
923
+
924
+ eq_(
925
+ connection.scalar(
926
+ select(
927
+ literal_column(left, type_=Integer())
928
+ // literal_column(right, type_=Integer())
929
+ )
930
+ ),
931
+ expected,
932
+ )
933
+
934
+ @testing.combinations(
935
+ ("5.52", "2.4", "2.3"), argnames="left, right, expected"
936
+ )
937
+ def test_truediv_numeric(self, connection, left, right, expected):
938
+ """test #4926"""
939
+
940
+ eq_(
941
+ connection.scalar(
942
+ select(
943
+ literal_column(left, type_=Numeric(10, 2))
944
+ / literal_column(right, type_=Numeric(10, 2))
945
+ )
946
+ ),
947
+ decimal.Decimal(expected),
948
+ )
949
+
950
+ @testing.combinations(
951
+ ("5.52", "2.4", 2.3), argnames="left, right, expected"
952
+ )
953
+ def test_truediv_float(self, connection, left, right, expected):
954
+ """test #4926"""
955
+
956
+ eq_(
957
+ connection.scalar(
958
+ select(
959
+ literal_column(left, type_=Float())
960
+ / literal_column(right, type_=Float())
961
+ )
962
+ ),
963
+ expected,
964
+ )
965
+
966
+ @testing.combinations(
967
+ ("5.52", "2.4", "2.0"), argnames="left, right, expected"
968
+ )
969
+ def test_floordiv_numeric(self, connection, left, right, expected):
970
+ """test #4926"""
971
+
972
+ eq_(
973
+ connection.scalar(
974
+ select(
975
+ literal_column(left, type_=Numeric())
976
+ // literal_column(right, type_=Numeric())
977
+ )
978
+ ),
979
+ decimal.Decimal(expected),
980
+ )
981
+
982
+ def test_truediv_integer_bound(self, connection):
983
+ """test #4926"""
984
+
985
+ eq_(
986
+ connection.scalar(select(literal(15) / literal(10))),
987
+ 1.5,
988
+ )
989
+
990
+ def test_floordiv_integer_bound(self, connection):
991
+ """test #4926"""
992
+
993
+ eq_(
994
+ connection.scalar(select(literal(15) // literal(10))),
995
+ 1,
996
+ )
997
+
998
+
999
+ class NumericTest(_LiteralRoundTripFixture, fixtures.TestBase):
1000
+ __backend__ = True
1001
+
1002
+ @testing.fixture
1003
+ def do_numeric_test(self, metadata, connection):
1004
+ def run(type_, input_, output, filter_=None, check_scale=False):
1005
+ t = Table("t", metadata, Column("x", type_))
1006
+ t.create(connection)
1007
+ connection.execute(t.insert(), [{"x": x} for x in input_])
1008
+
1009
+ result = {row[0] for row in connection.execute(t.select())}
1010
+ output = set(output)
1011
+ if filter_:
1012
+ result = {filter_(x) for x in result}
1013
+ output = {filter_(x) for x in output}
1014
+ eq_(result, output)
1015
+ if check_scale:
1016
+ eq_([str(x) for x in result], [str(x) for x in output])
1017
+
1018
+ connection.execute(t.delete())
1019
+
1020
+ # test that this is actually a number!
1021
+ # note we have tiny scale here as we have tests with very
1022
+ # small scale Numeric types. PostgreSQL will raise an error
1023
+ # if you use values outside the available scale.
1024
+ if type_.asdecimal:
1025
+ test_value = decimal.Decimal("2.9")
1026
+ add_value = decimal.Decimal("37.12")
1027
+ else:
1028
+ test_value = 2.9
1029
+ add_value = 37.12
1030
+
1031
+ connection.execute(t.insert(), {"x": test_value})
1032
+ assert_we_are_a_number = connection.scalar(
1033
+ select(type_coerce(t.c.x + add_value, type_))
1034
+ )
1035
+ eq_(
1036
+ round(assert_we_are_a_number, 3),
1037
+ round(test_value + add_value, 3),
1038
+ )
1039
+
1040
+ return run
1041
+
1042
+ def test_render_literal_numeric(self, literal_round_trip):
1043
+ literal_round_trip(
1044
+ Numeric(precision=8, scale=4),
1045
+ [15.7563, decimal.Decimal("15.7563")],
1046
+ [decimal.Decimal("15.7563")],
1047
+ )
1048
+
1049
+ def test_render_literal_numeric_asfloat(self, literal_round_trip):
1050
+ literal_round_trip(
1051
+ Numeric(precision=8, scale=4, asdecimal=False),
1052
+ [15.7563, decimal.Decimal("15.7563")],
1053
+ [15.7563],
1054
+ )
1055
+
1056
+ def test_render_literal_float(self, literal_round_trip):
1057
+ literal_round_trip(
1058
+ Float(),
1059
+ [15.7563, decimal.Decimal("15.7563")],
1060
+ [15.7563],
1061
+ filter_=lambda n: n is not None and round(n, 5) or None,
1062
+ support_whereclause=False,
1063
+ )
1064
+
1065
+ @testing.requires.precision_generic_float_type
1066
+ def test_float_custom_scale(self, do_numeric_test):
1067
+ do_numeric_test(
1068
+ Float(None, decimal_return_scale=7, asdecimal=True),
1069
+ [15.7563827, decimal.Decimal("15.7563827")],
1070
+ [decimal.Decimal("15.7563827")],
1071
+ check_scale=True,
1072
+ )
1073
+
1074
+ def test_numeric_as_decimal(self, do_numeric_test):
1075
+ do_numeric_test(
1076
+ Numeric(precision=8, scale=4),
1077
+ [15.7563, decimal.Decimal("15.7563")],
1078
+ [decimal.Decimal("15.7563")],
1079
+ )
1080
+
1081
+ def test_numeric_as_float(self, do_numeric_test):
1082
+ do_numeric_test(
1083
+ Numeric(precision=8, scale=4, asdecimal=False),
1084
+ [15.7563, decimal.Decimal("15.7563")],
1085
+ [15.7563],
1086
+ )
1087
+
1088
+ @testing.requires.infinity_floats
1089
+ def test_infinity_floats(self, do_numeric_test):
1090
+ """test for #977, #7283"""
1091
+
1092
+ do_numeric_test(
1093
+ Float(None),
1094
+ [float("inf")],
1095
+ [float("inf")],
1096
+ )
1097
+
1098
+ @testing.requires.fetch_null_from_numeric
1099
+ def test_numeric_null_as_decimal(self, do_numeric_test):
1100
+ do_numeric_test(Numeric(precision=8, scale=4), [None], [None])
1101
+
1102
+ @testing.requires.fetch_null_from_numeric
1103
+ def test_numeric_null_as_float(self, do_numeric_test):
1104
+ do_numeric_test(
1105
+ Numeric(precision=8, scale=4, asdecimal=False), [None], [None]
1106
+ )
1107
+
1108
+ @testing.requires.floats_to_four_decimals
1109
+ def test_float_as_decimal(self, do_numeric_test):
1110
+ do_numeric_test(
1111
+ Float(asdecimal=True),
1112
+ [15.756, decimal.Decimal("15.756"), None],
1113
+ [decimal.Decimal("15.756"), None],
1114
+ filter_=lambda n: n is not None and round(n, 4) or None,
1115
+ )
1116
+
1117
+ def test_float_as_float(self, do_numeric_test):
1118
+ do_numeric_test(
1119
+ Float(),
1120
+ [15.756, decimal.Decimal("15.756")],
1121
+ [15.756],
1122
+ filter_=lambda n: n is not None and round(n, 5) or None,
1123
+ )
1124
+
1125
+ @testing.requires.literal_float_coercion
1126
+ def test_float_coerce_round_trip(self, connection):
1127
+ expr = 15.7563
1128
+
1129
+ val = connection.scalar(select(literal(expr)))
1130
+ eq_(val, expr)
1131
+
1132
+ # this does not work in MySQL, see #4036, however we choose not
1133
+ # to render CAST unconditionally since this is kind of an edge case.
1134
+
1135
+ @testing.requires.implicit_decimal_binds
1136
+ def test_decimal_coerce_round_trip(self, connection):
1137
+ expr = decimal.Decimal("15.7563")
1138
+
1139
+ val = connection.scalar(select(literal(expr)))
1140
+ eq_(val, expr)
1141
+
1142
+ def test_decimal_coerce_round_trip_w_cast(self, connection):
1143
+ expr = decimal.Decimal("15.7563")
1144
+
1145
+ val = connection.scalar(select(cast(expr, Numeric(10, 4))))
1146
+ eq_(val, expr)
1147
+
1148
+ @testing.requires.precision_numerics_general
1149
+ def test_precision_decimal(self, do_numeric_test):
1150
+ numbers = {
1151
+ decimal.Decimal("54.234246451650"),
1152
+ decimal.Decimal("0.004354"),
1153
+ decimal.Decimal("900.0"),
1154
+ }
1155
+
1156
+ do_numeric_test(Numeric(precision=18, scale=12), numbers, numbers)
1157
+
1158
+ @testing.requires.precision_numerics_enotation_large
1159
+ def test_enotation_decimal(self, do_numeric_test):
1160
+ """test exceedingly small decimals.
1161
+
1162
+ Decimal reports values with E notation when the exponent
1163
+ is greater than 6.
1164
+
1165
+ """
1166
+
1167
+ numbers = {
1168
+ decimal.Decimal("1E-2"),
1169
+ decimal.Decimal("1E-3"),
1170
+ decimal.Decimal("1E-4"),
1171
+ decimal.Decimal("1E-5"),
1172
+ decimal.Decimal("1E-6"),
1173
+ decimal.Decimal("1E-7"),
1174
+ decimal.Decimal("1E-8"),
1175
+ decimal.Decimal("0.01000005940696"),
1176
+ decimal.Decimal("0.00000005940696"),
1177
+ decimal.Decimal("0.00000000000696"),
1178
+ decimal.Decimal("0.70000000000696"),
1179
+ decimal.Decimal("696E-12"),
1180
+ }
1181
+ do_numeric_test(Numeric(precision=18, scale=14), numbers, numbers)
1182
+
1183
+ @testing.requires.precision_numerics_enotation_large
1184
+ def test_enotation_decimal_large(self, do_numeric_test):
1185
+ """test exceedingly large decimals."""
1186
+
1187
+ numbers = {
1188
+ decimal.Decimal("4E+8"),
1189
+ decimal.Decimal("5748E+15"),
1190
+ decimal.Decimal("1.521E+15"),
1191
+ decimal.Decimal("00000000000000.1E+12"),
1192
+ }
1193
+ do_numeric_test(Numeric(precision=25, scale=2), numbers, numbers)
1194
+
1195
+ @testing.requires.precision_numerics_many_significant_digits
1196
+ def test_many_significant_digits(self, do_numeric_test):
1197
+ numbers = {
1198
+ decimal.Decimal("31943874831932418390.01"),
1199
+ decimal.Decimal("319438950232418390.273596"),
1200
+ decimal.Decimal("87673.594069654243"),
1201
+ }
1202
+ do_numeric_test(Numeric(precision=38, scale=12), numbers, numbers)
1203
+
1204
+ @testing.requires.precision_numerics_retains_significant_digits
1205
+ def test_numeric_no_decimal(self, do_numeric_test):
1206
+ numbers = {decimal.Decimal("1.000")}
1207
+ do_numeric_test(
1208
+ Numeric(precision=5, scale=3), numbers, numbers, check_scale=True
1209
+ )
1210
+
1211
+ @testing.combinations(sqltypes.Float, sqltypes.Double, argnames="cls_")
1212
+ @testing.requires.float_is_numeric
1213
+ def test_float_is_not_numeric(self, connection, cls_):
1214
+ target_type = cls_().dialect_impl(connection.dialect)
1215
+ numeric_type = sqltypes.Numeric().dialect_impl(connection.dialect)
1216
+
1217
+ ne_(target_type.__visit_name__, numeric_type.__visit_name__)
1218
+ ne_(target_type.__class__, numeric_type.__class__)
1219
+
1220
+
1221
+ class BooleanTest(_LiteralRoundTripFixture, fixtures.TablesTest):
1222
+ __backend__ = True
1223
+
1224
+ @classmethod
1225
+ def define_tables(cls, metadata):
1226
+ Table(
1227
+ "boolean_table",
1228
+ metadata,
1229
+ Column("id", Integer, primary_key=True, autoincrement=False),
1230
+ Column("value", Boolean),
1231
+ Column("unconstrained_value", Boolean(create_constraint=False)),
1232
+ )
1233
+
1234
+ def test_render_literal_bool(self, literal_round_trip):
1235
+ literal_round_trip(Boolean(), [True, False], [True, False])
1236
+
1237
+ def test_round_trip(self, connection):
1238
+ boolean_table = self.tables.boolean_table
1239
+
1240
+ connection.execute(
1241
+ boolean_table.insert(),
1242
+ {"id": 1, "value": True, "unconstrained_value": False},
1243
+ )
1244
+
1245
+ row = connection.execute(
1246
+ select(boolean_table.c.value, boolean_table.c.unconstrained_value)
1247
+ ).first()
1248
+
1249
+ eq_(row, (True, False))
1250
+ assert isinstance(row[0], bool)
1251
+
1252
+ @testing.requires.nullable_booleans
1253
+ def test_null(self, connection):
1254
+ boolean_table = self.tables.boolean_table
1255
+
1256
+ connection.execute(
1257
+ boolean_table.insert(),
1258
+ {"id": 1, "value": None, "unconstrained_value": None},
1259
+ )
1260
+
1261
+ row = connection.execute(
1262
+ select(boolean_table.c.value, boolean_table.c.unconstrained_value)
1263
+ ).first()
1264
+
1265
+ eq_(row, (None, None))
1266
+
1267
+ def test_whereclause(self):
1268
+ # testing "WHERE <column>" renders a compatible expression
1269
+ boolean_table = self.tables.boolean_table
1270
+
1271
+ with config.db.begin() as conn:
1272
+ conn.execute(
1273
+ boolean_table.insert(),
1274
+ [
1275
+ {"id": 1, "value": True, "unconstrained_value": True},
1276
+ {"id": 2, "value": False, "unconstrained_value": False},
1277
+ ],
1278
+ )
1279
+
1280
+ eq_(
1281
+ conn.scalar(
1282
+ select(boolean_table.c.id).where(boolean_table.c.value)
1283
+ ),
1284
+ 1,
1285
+ )
1286
+ eq_(
1287
+ conn.scalar(
1288
+ select(boolean_table.c.id).where(
1289
+ boolean_table.c.unconstrained_value
1290
+ )
1291
+ ),
1292
+ 1,
1293
+ )
1294
+ eq_(
1295
+ conn.scalar(
1296
+ select(boolean_table.c.id).where(~boolean_table.c.value)
1297
+ ),
1298
+ 2,
1299
+ )
1300
+ eq_(
1301
+ conn.scalar(
1302
+ select(boolean_table.c.id).where(
1303
+ ~boolean_table.c.unconstrained_value
1304
+ )
1305
+ ),
1306
+ 2,
1307
+ )
1308
+
1309
+
1310
+ class JSONTest(fixtures.TablesTest):
1311
+ __requires__ = ("json_type",)
1312
+ __backend__ = True
1313
+
1314
+ datatype = JSON
1315
+
1316
+ @classmethod
1317
+ def define_tables(cls, metadata):
1318
+ Table(
1319
+ "data_table",
1320
+ metadata,
1321
+ Column(
1322
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
1323
+ ),
1324
+ Column("name", String(30), nullable=False),
1325
+ Column("data", cls.datatype(), nullable=False),
1326
+ Column("nulldata", cls.datatype(none_as_null=True)),
1327
+ )
1328
+
1329
+ def test_round_trip_data1(self, connection):
1330
+ self._test_round_trip({"key1": "value1", "key2": "value2"}, connection)
1331
+
1332
+ @testing.combinations(
1333
+ ("unicode", True), ("ascii", False), argnames="unicode_", id_="ia"
1334
+ )
1335
+ @testing.combinations(100, 1999, 3000, 4000, 5000, 9000, argnames="length")
1336
+ def test_round_trip_pretty_large_data(self, connection, unicode_, length):
1337
+ if unicode_:
1338
+ data = "réve🐍illé" * ((length // 9) + 1)
1339
+ data = data[0 : (length // 2)]
1340
+ else:
1341
+ data = "abcdefg" * ((length // 7) + 1)
1342
+ data = data[0:length]
1343
+
1344
+ self._test_round_trip({"key1": data, "key2": data}, connection)
1345
+
1346
+ def _test_round_trip(self, data_element, connection):
1347
+ data_table = self.tables.data_table
1348
+
1349
+ connection.execute(
1350
+ data_table.insert(),
1351
+ {"id": 1, "name": "row1", "data": data_element},
1352
+ )
1353
+
1354
+ row = connection.execute(select(data_table.c.data)).first()
1355
+
1356
+ eq_(row, (data_element,))
1357
+
1358
+ def _index_fixtures(include_comparison):
1359
+ if include_comparison:
1360
+ # basically SQL Server and MariaDB can kind of do json
1361
+ # comparison, MySQL, PG and SQLite can't. not worth it.
1362
+ json_elements = []
1363
+ else:
1364
+ json_elements = [
1365
+ ("json", {"foo": "bar"}),
1366
+ ("json", ["one", "two", "three"]),
1367
+ (None, {"foo": "bar"}),
1368
+ (None, ["one", "two", "three"]),
1369
+ ]
1370
+
1371
+ elements = [
1372
+ ("boolean", True),
1373
+ ("boolean", False),
1374
+ ("boolean", None),
1375
+ ("string", "some string"),
1376
+ ("string", None),
1377
+ ("string", "réve illé"),
1378
+ (
1379
+ "string",
1380
+ "réve🐍 illé",
1381
+ testing.requires.json_index_supplementary_unicode_element,
1382
+ ),
1383
+ ("integer", 15),
1384
+ ("integer", 1),
1385
+ ("integer", 0),
1386
+ ("integer", None),
1387
+ ("float", 28.5),
1388
+ ("float", None),
1389
+ ("float", 1234567.89, testing.requires.literal_float_coercion),
1390
+ ("numeric", 1234567.89),
1391
+ # this one "works" because the float value you see here is
1392
+ # lost immediately to floating point stuff
1393
+ (
1394
+ "numeric",
1395
+ 99998969694839.983485848,
1396
+ ),
1397
+ ("numeric", 99939.983485848),
1398
+ ("_decimal", decimal.Decimal("1234567.89")),
1399
+ (
1400
+ "_decimal",
1401
+ decimal.Decimal("99998969694839.983485848"),
1402
+ # fails on SQLite and MySQL (non-mariadb)
1403
+ requirements.cast_precision_numerics_many_significant_digits,
1404
+ ),
1405
+ (
1406
+ "_decimal",
1407
+ decimal.Decimal("99939.983485848"),
1408
+ ),
1409
+ ] + json_elements
1410
+
1411
+ def decorate(fn):
1412
+ fn = testing.combinations(id_="sa", *elements)(fn)
1413
+
1414
+ return fn
1415
+
1416
+ return decorate
1417
+
1418
+ def _json_value_insert(self, connection, datatype, value, data_element):
1419
+ data_table = self.tables.data_table
1420
+ if datatype == "_decimal":
1421
+ # Python's builtin json serializer basically doesn't support
1422
+ # Decimal objects without implicit float conversion period.
1423
+ # users can otherwise use simplejson which supports
1424
+ # precision decimals
1425
+
1426
+ # https://bugs.python.org/issue16535
1427
+
1428
+ # inserting as strings to avoid a new fixture around the
1429
+ # dialect which would have idiosyncrasies for different
1430
+ # backends.
1431
+
1432
+ class DecimalEncoder(json.JSONEncoder):
1433
+ def default(self, o):
1434
+ if isinstance(o, decimal.Decimal):
1435
+ return str(o)
1436
+ return super().default(o)
1437
+
1438
+ json_data = json.dumps(data_element, cls=DecimalEncoder)
1439
+
1440
+ # take the quotes out. yup, there is *literally* no other
1441
+ # way to get Python's json.dumps() to put all the digits in
1442
+ # the string
1443
+ json_data = re.sub(r'"(%s)"' % str(value), str(value), json_data)
1444
+
1445
+ datatype = "numeric"
1446
+
1447
+ connection.execute(
1448
+ data_table.insert().values(
1449
+ name="row1",
1450
+ # to pass the string directly to every backend, including
1451
+ # PostgreSQL which needs the value to be CAST as JSON
1452
+ # both in the SQL as well as at the prepared statement
1453
+ # level for asyncpg, while at the same time MySQL
1454
+ # doesn't even support CAST for JSON, here we are
1455
+ # sending the string embedded in the SQL without using
1456
+ # a parameter.
1457
+ data=bindparam(None, json_data, literal_execute=True),
1458
+ nulldata=bindparam(None, json_data, literal_execute=True),
1459
+ ),
1460
+ )
1461
+ else:
1462
+ connection.execute(
1463
+ data_table.insert(),
1464
+ {
1465
+ "name": "row1",
1466
+ "data": data_element,
1467
+ "nulldata": data_element,
1468
+ },
1469
+ )
1470
+
1471
+ p_s = None
1472
+
1473
+ if datatype:
1474
+ if datatype == "numeric":
1475
+ a, b = str(value).split(".")
1476
+ s = len(b)
1477
+ p = len(a) + s
1478
+
1479
+ if isinstance(value, decimal.Decimal):
1480
+ compare_value = value
1481
+ else:
1482
+ compare_value = decimal.Decimal(str(value))
1483
+
1484
+ p_s = (p, s)
1485
+ else:
1486
+ compare_value = value
1487
+ else:
1488
+ compare_value = value
1489
+
1490
+ return datatype, compare_value, p_s
1491
+
1492
+ @testing.requires.legacy_unconditional_json_extract
1493
+ @_index_fixtures(False)
1494
+ def test_index_typed_access(self, datatype, value):
1495
+ data_table = self.tables.data_table
1496
+ data_element = {"key1": value}
1497
+
1498
+ with config.db.begin() as conn:
1499
+ datatype, compare_value, p_s = self._json_value_insert(
1500
+ conn, datatype, value, data_element
1501
+ )
1502
+
1503
+ expr = data_table.c.data["key1"]
1504
+ if datatype:
1505
+ if datatype == "numeric" and p_s:
1506
+ expr = expr.as_numeric(*p_s)
1507
+ else:
1508
+ expr = getattr(expr, "as_%s" % datatype)()
1509
+
1510
+ roundtrip = conn.scalar(select(expr))
1511
+ eq_(roundtrip, compare_value)
1512
+ is_(type(roundtrip), type(compare_value))
1513
+
1514
+ @testing.requires.legacy_unconditional_json_extract
1515
+ @_index_fixtures(True)
1516
+ def test_index_typed_comparison(self, datatype, value):
1517
+ data_table = self.tables.data_table
1518
+ data_element = {"key1": value}
1519
+
1520
+ with config.db.begin() as conn:
1521
+ datatype, compare_value, p_s = self._json_value_insert(
1522
+ conn, datatype, value, data_element
1523
+ )
1524
+
1525
+ expr = data_table.c.data["key1"]
1526
+ if datatype:
1527
+ if datatype == "numeric" and p_s:
1528
+ expr = expr.as_numeric(*p_s)
1529
+ else:
1530
+ expr = getattr(expr, "as_%s" % datatype)()
1531
+
1532
+ row = conn.execute(
1533
+ select(expr).where(expr == compare_value)
1534
+ ).first()
1535
+
1536
+ # make sure we get a row even if value is None
1537
+ eq_(row, (compare_value,))
1538
+
1539
+ @testing.requires.legacy_unconditional_json_extract
1540
+ @_index_fixtures(True)
1541
+ def test_path_typed_comparison(self, datatype, value):
1542
+ data_table = self.tables.data_table
1543
+ data_element = {"key1": {"subkey1": value}}
1544
+ with config.db.begin() as conn:
1545
+ datatype, compare_value, p_s = self._json_value_insert(
1546
+ conn, datatype, value, data_element
1547
+ )
1548
+
1549
+ expr = data_table.c.data[("key1", "subkey1")]
1550
+
1551
+ if datatype:
1552
+ if datatype == "numeric" and p_s:
1553
+ expr = expr.as_numeric(*p_s)
1554
+ else:
1555
+ expr = getattr(expr, "as_%s" % datatype)()
1556
+
1557
+ row = conn.execute(
1558
+ select(expr).where(expr == compare_value)
1559
+ ).first()
1560
+
1561
+ # make sure we get a row even if value is None
1562
+ eq_(row, (compare_value,))
1563
+
1564
+ @testing.combinations(
1565
+ (True,),
1566
+ (False,),
1567
+ (None,),
1568
+ (15,),
1569
+ (0,),
1570
+ (-1,),
1571
+ (-1.0,),
1572
+ (15.052,),
1573
+ ("a string",),
1574
+ ("réve illé",),
1575
+ ("réve🐍 illé",),
1576
+ )
1577
+ def test_single_element_round_trip(self, element):
1578
+ data_table = self.tables.data_table
1579
+ data_element = element
1580
+ with config.db.begin() as conn:
1581
+ conn.execute(
1582
+ data_table.insert(),
1583
+ {
1584
+ "name": "row1",
1585
+ "data": data_element,
1586
+ "nulldata": data_element,
1587
+ },
1588
+ )
1589
+
1590
+ row = conn.execute(
1591
+ select(data_table.c.data, data_table.c.nulldata)
1592
+ ).first()
1593
+
1594
+ if isinstance(data_element, float):
1595
+ c1, c2 = row
1596
+ eq_((float(c1), float(c2)), (data_element, data_element))
1597
+ else:
1598
+ eq_(row, (data_element, data_element))
1599
+
1600
+ def test_round_trip_custom_json(self):
1601
+ data_table = self.tables.data_table
1602
+ data_element = {"key1": "data1"}
1603
+
1604
+ js = mock.Mock(side_effect=json.dumps)
1605
+ jd = mock.Mock(side_effect=json.loads)
1606
+ engine = engines.testing_engine(
1607
+ options=dict(json_serializer=js, json_deserializer=jd)
1608
+ )
1609
+
1610
+ # support sqlite :memory: database...
1611
+ data_table.create(engine, checkfirst=True)
1612
+ with engine.begin() as conn:
1613
+ conn.execute(
1614
+ data_table.insert(), {"name": "row1", "data": data_element}
1615
+ )
1616
+ row = conn.execute(select(data_table.c.data)).first()
1617
+
1618
+ eq_(row, (data_element,))
1619
+ eq_(js.mock_calls, [mock.call(data_element)])
1620
+
1621
+ eq_(len(jd.mock_calls), 1)
1622
+ eq_(len(jd.mock_calls[0].args), 1)
1623
+
1624
+ # oracledb's json outputtypehandler receives the json
1625
+ # without spaces between the colons, so we have to normalize
1626
+ # for the compare
1627
+
1628
+ if testing.requires.json_deserializer_binary.enabled:
1629
+ json_str_given_to_adapter = jd.mock_calls[0].args[0].decode()
1630
+ else:
1631
+ json_str_given_to_adapter = jd.mock_calls[0].args[0]
1632
+
1633
+ eq_(
1634
+ json.dumps(json.loads(json_str_given_to_adapter)),
1635
+ json.dumps(data_element),
1636
+ )
1637
+
1638
+ @testing.combinations(
1639
+ ("parameters",),
1640
+ ("multiparameters",),
1641
+ ("values",),
1642
+ ("omit",),
1643
+ argnames="insert_type",
1644
+ )
1645
+ def test_round_trip_none_as_sql_null(self, connection, insert_type):
1646
+ col = self.tables.data_table.c["nulldata"]
1647
+
1648
+ conn = connection
1649
+
1650
+ if insert_type == "parameters":
1651
+ stmt, params = self.tables.data_table.insert(), {
1652
+ "name": "r1",
1653
+ "nulldata": None,
1654
+ "data": None,
1655
+ }
1656
+ elif insert_type == "multiparameters":
1657
+ stmt, params = self.tables.data_table.insert(), [
1658
+ {"name": "r1", "nulldata": None, "data": None}
1659
+ ]
1660
+ elif insert_type == "values":
1661
+ stmt, params = (
1662
+ self.tables.data_table.insert().values(
1663
+ name="r1",
1664
+ nulldata=None,
1665
+ data=None,
1666
+ ),
1667
+ {},
1668
+ )
1669
+ elif insert_type == "omit":
1670
+ stmt, params = (
1671
+ self.tables.data_table.insert(),
1672
+ {"name": "r1", "data": None},
1673
+ )
1674
+
1675
+ else:
1676
+ assert False
1677
+
1678
+ conn.execute(stmt, params)
1679
+
1680
+ eq_(
1681
+ conn.scalar(
1682
+ select(self.tables.data_table.c.name).where(col.is_(null()))
1683
+ ),
1684
+ "r1",
1685
+ )
1686
+
1687
+ eq_(conn.scalar(select(col)), None)
1688
+
1689
+ def test_round_trip_json_null_as_json_null(self, connection):
1690
+ col = self.tables.data_table.c["data"]
1691
+
1692
+ conn = connection
1693
+ conn.execute(
1694
+ self.tables.data_table.insert(),
1695
+ {"name": "r1", "data": JSON.NULL},
1696
+ )
1697
+
1698
+ eq_(
1699
+ conn.scalar(
1700
+ select(self.tables.data_table.c.name).where(
1701
+ cast(col, String) == "null"
1702
+ )
1703
+ ),
1704
+ "r1",
1705
+ )
1706
+
1707
+ eq_(conn.scalar(select(col)), None)
1708
+
1709
+ @testing.combinations(
1710
+ ("parameters",),
1711
+ ("multiparameters",),
1712
+ ("values",),
1713
+ argnames="insert_type",
1714
+ )
1715
+ def test_round_trip_none_as_json_null(self, connection, insert_type):
1716
+ col = self.tables.data_table.c["data"]
1717
+
1718
+ if insert_type == "parameters":
1719
+ stmt, params = self.tables.data_table.insert(), {
1720
+ "name": "r1",
1721
+ "data": None,
1722
+ }
1723
+ elif insert_type == "multiparameters":
1724
+ stmt, params = self.tables.data_table.insert(), [
1725
+ {"name": "r1", "data": None}
1726
+ ]
1727
+ elif insert_type == "values":
1728
+ stmt, params = (
1729
+ self.tables.data_table.insert().values(name="r1", data=None),
1730
+ {},
1731
+ )
1732
+ else:
1733
+ assert False
1734
+
1735
+ conn = connection
1736
+ conn.execute(stmt, params)
1737
+
1738
+ eq_(
1739
+ conn.scalar(
1740
+ select(self.tables.data_table.c.name).where(
1741
+ cast(col, String) == "null"
1742
+ )
1743
+ ),
1744
+ "r1",
1745
+ )
1746
+
1747
+ eq_(conn.scalar(select(col)), None)
1748
+
1749
+ def test_unicode_round_trip(self):
1750
+ # note we include Unicode supplementary characters as well
1751
+ with config.db.begin() as conn:
1752
+ conn.execute(
1753
+ self.tables.data_table.insert(),
1754
+ {
1755
+ "name": "r1",
1756
+ "data": {
1757
+ "réve🐍 illé": "réve🐍 illé",
1758
+ "data": {"k1": "drôl🐍e"},
1759
+ },
1760
+ },
1761
+ )
1762
+
1763
+ eq_(
1764
+ conn.scalar(select(self.tables.data_table.c.data)),
1765
+ {
1766
+ "réve🐍 illé": "réve🐍 illé",
1767
+ "data": {"k1": "drôl🐍e"},
1768
+ },
1769
+ )
1770
+
1771
+ def test_eval_none_flag_orm(self, connection):
1772
+ Base = declarative_base()
1773
+
1774
+ class Data(Base):
1775
+ __table__ = self.tables.data_table
1776
+
1777
+ with Session(connection) as s:
1778
+ d1 = Data(name="d1", data=None, nulldata=None)
1779
+ s.add(d1)
1780
+ s.commit()
1781
+
1782
+ s.bulk_insert_mappings(
1783
+ Data, [{"name": "d2", "data": None, "nulldata": None}]
1784
+ )
1785
+ eq_(
1786
+ s.query(
1787
+ cast(self.tables.data_table.c.data, String()),
1788
+ cast(self.tables.data_table.c.nulldata, String),
1789
+ )
1790
+ .filter(self.tables.data_table.c.name == "d1")
1791
+ .first(),
1792
+ ("null", None),
1793
+ )
1794
+ eq_(
1795
+ s.query(
1796
+ cast(self.tables.data_table.c.data, String()),
1797
+ cast(self.tables.data_table.c.nulldata, String),
1798
+ )
1799
+ .filter(self.tables.data_table.c.name == "d2")
1800
+ .first(),
1801
+ ("null", None),
1802
+ )
1803
+
1804
+ @testing.combinations(
1805
+ ("string",),
1806
+ ("integer",),
1807
+ ("float",),
1808
+ ("numeric",),
1809
+ ("boolean",),
1810
+ argnames="cross_cast",
1811
+ )
1812
+ @testing.combinations(
1813
+ ("boolean", True, {"string"}),
1814
+ ("boolean", False, {"string"}),
1815
+ ("boolean", None, {"all"}),
1816
+ ("string", "45", {"integer", "float", "numeric"}),
1817
+ ("string", "45.684", {"float", "numeric"}),
1818
+ ("string", "some string", {"string"}),
1819
+ ("string", None, {"all"}),
1820
+ ("string", "réve illé", {"string"}),
1821
+ ("string", "true", {"boolean"}),
1822
+ ("string", "false", {"boolean"}),
1823
+ ("integer", 15, {"string", "numeric", "float"}),
1824
+ ("integer", 1, {"all"}),
1825
+ ("integer", 0, {"all"}),
1826
+ ("integer", None, {"all"}),
1827
+ ("float", None, {"all"}),
1828
+ ("float", 1234567.89, {"string", "numeric"}),
1829
+ ("numeric", 1234567.89, {"string", "float"}),
1830
+ argnames="datatype, value, allowed_targets",
1831
+ )
1832
+ @testing.variation("json_access", ["getitem", "path"])
1833
+ def test_index_cross_casts(
1834
+ self,
1835
+ datatype,
1836
+ value,
1837
+ allowed_targets,
1838
+ cross_cast,
1839
+ json_access: Variation,
1840
+ connection,
1841
+ ):
1842
+ """cross cast tests set up for #11074"""
1843
+
1844
+ data_table = self.tables.data_table
1845
+ if json_access.getitem:
1846
+ data_element = {"key1": value}
1847
+ elif json_access.path:
1848
+ data_element = {"attr1": {"key1": value}}
1849
+ else:
1850
+ json_access.fail()
1851
+
1852
+ datatype, _, _ = self._json_value_insert(
1853
+ connection, datatype, value, data_element
1854
+ )
1855
+
1856
+ if json_access.getitem:
1857
+ expr = data_table.c.data["key1"]
1858
+ elif json_access.path:
1859
+ expr = data_table.c.data[("attr1", "key1")]
1860
+ else:
1861
+ json_access.fail()
1862
+
1863
+ if cross_cast == "numeric":
1864
+ expr = getattr(expr, "as_%s" % cross_cast)(10, 2)
1865
+ else:
1866
+ expr = getattr(expr, "as_%s" % cross_cast)()
1867
+
1868
+ if (
1869
+ cross_cast != datatype
1870
+ and "all" not in allowed_targets
1871
+ and cross_cast not in allowed_targets
1872
+ ):
1873
+ try:
1874
+ roundtrip = connection.scalar(select(expr))
1875
+ except Exception:
1876
+ # We can't predict in a backend-agnostic way what CASTS
1877
+ # will fail and which will proceed with a (possibly
1878
+ # useless) value. PostgreSQL CASTS fail in 100% of cases
1879
+ # that the types aren't compatible. SQL Server fails in
1880
+ # most, except for booleans because it uses ints for
1881
+ # booleans which are easier to cast. MySQL and SQLite do
1882
+ # not raise for CAST under any circumstances for the four
1883
+ # of string/int/float/boolean. one way to force a fail
1884
+ # would be to have backends inject a special version of
1885
+ # Float/Unicode/Integer/Boolean that enforces a python
1886
+ # check of the expected data value. However for now we let
1887
+ # the backends ensure the expected type is returned but we
1888
+ # don't try to validate the value itself for non-sensical
1889
+ # casts.
1890
+ return
1891
+ else:
1892
+ roundtrip = connection.scalar(select(expr))
1893
+
1894
+ if value is None:
1895
+ eq_(roundtrip, None)
1896
+ elif cross_cast == "string":
1897
+ assert isinstance(roundtrip, str)
1898
+ elif cross_cast == "integer":
1899
+ assert isinstance(roundtrip, int)
1900
+ elif cross_cast == "float":
1901
+ assert isinstance(roundtrip, float)
1902
+ elif cross_cast == "numeric":
1903
+ assert isinstance(roundtrip, decimal.Decimal)
1904
+ elif cross_cast == "boolean":
1905
+ assert isinstance(roundtrip, bool)
1906
+ else:
1907
+ assert False
1908
+
1909
+
1910
+ class JSONLegacyStringCastIndexTest(
1911
+ _LiteralRoundTripFixture, fixtures.TablesTest
1912
+ ):
1913
+ """test JSON index access with "cast to string", which we have documented
1914
+ for a long time as how to compare JSON values, but is ultimately not
1915
+ reliable in all cases. The "as_XYZ()" comparators should be used
1916
+ instead.
1917
+
1918
+ """
1919
+
1920
+ __requires__ = ("json_type", "legacy_unconditional_json_extract")
1921
+ __backend__ = True
1922
+
1923
+ datatype = JSON
1924
+
1925
+ data1 = {"key1": "value1", "key2": "value2"}
1926
+
1927
+ data2 = {
1928
+ "Key 'One'": "value1",
1929
+ "key two": "value2",
1930
+ "key three": "value ' three '",
1931
+ }
1932
+
1933
+ data3 = {
1934
+ "key1": [1, 2, 3],
1935
+ "key2": ["one", "two", "three"],
1936
+ "key3": [{"four": "five"}, {"six": "seven"}],
1937
+ }
1938
+
1939
+ data4 = ["one", "two", "three"]
1940
+
1941
+ data5 = {
1942
+ "nested": {
1943
+ "elem1": [{"a": "b", "c": "d"}, {"e": "f", "g": "h"}],
1944
+ "elem2": {"elem3": {"elem4": "elem5"}},
1945
+ }
1946
+ }
1947
+
1948
+ data6 = {"a": 5, "b": "some value", "c": {"foo": "bar"}}
1949
+
1950
+ @classmethod
1951
+ def define_tables(cls, metadata):
1952
+ Table(
1953
+ "data_table",
1954
+ metadata,
1955
+ Column(
1956
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
1957
+ ),
1958
+ Column("name", String(30), nullable=False),
1959
+ Column("data", cls.datatype),
1960
+ Column("nulldata", cls.datatype(none_as_null=True)),
1961
+ )
1962
+
1963
+ def _criteria_fixture(self):
1964
+ with config.db.begin() as conn:
1965
+ conn.execute(
1966
+ self.tables.data_table.insert(),
1967
+ [
1968
+ {"name": "r1", "data": self.data1},
1969
+ {"name": "r2", "data": self.data2},
1970
+ {"name": "r3", "data": self.data3},
1971
+ {"name": "r4", "data": self.data4},
1972
+ {"name": "r5", "data": self.data5},
1973
+ {"name": "r6", "data": self.data6},
1974
+ ],
1975
+ )
1976
+
1977
+ def _test_index_criteria(self, crit, expected, test_literal=True):
1978
+ self._criteria_fixture()
1979
+ with config.db.connect() as conn:
1980
+ stmt = select(self.tables.data_table.c.name).where(crit)
1981
+
1982
+ eq_(conn.scalar(stmt), expected)
1983
+
1984
+ if test_literal:
1985
+ literal_sql = str(
1986
+ stmt.compile(
1987
+ config.db, compile_kwargs={"literal_binds": True}
1988
+ )
1989
+ )
1990
+
1991
+ eq_(conn.exec_driver_sql(literal_sql).scalar(), expected)
1992
+
1993
+ def test_string_cast_crit_spaces_in_key(self):
1994
+ name = self.tables.data_table.c.name
1995
+ col = self.tables.data_table.c["data"]
1996
+
1997
+ # limit the rows here to avoid PG error
1998
+ # "cannot extract field from a non-object", which is
1999
+ # fixed in 9.4 but may exist in 9.3
2000
+ self._test_index_criteria(
2001
+ and_(
2002
+ name.in_(["r1", "r2", "r3"]),
2003
+ cast(col["key two"], String) == '"value2"',
2004
+ ),
2005
+ "r2",
2006
+ )
2007
+
2008
+ @config.requirements.json_array_indexes
2009
+ def test_string_cast_crit_simple_int(self):
2010
+ name = self.tables.data_table.c.name
2011
+ col = self.tables.data_table.c["data"]
2012
+
2013
+ # limit the rows here to avoid PG error
2014
+ # "cannot extract array element from a non-array", which is
2015
+ # fixed in 9.4 but may exist in 9.3
2016
+ self._test_index_criteria(
2017
+ and_(
2018
+ name == "r4",
2019
+ cast(col[1], String) == '"two"',
2020
+ ),
2021
+ "r4",
2022
+ )
2023
+
2024
+ def test_string_cast_crit_mixed_path(self):
2025
+ col = self.tables.data_table.c["data"]
2026
+ self._test_index_criteria(
2027
+ cast(col[("key3", 1, "six")], String) == '"seven"',
2028
+ "r3",
2029
+ )
2030
+
2031
+ def test_string_cast_crit_string_path(self):
2032
+ col = self.tables.data_table.c["data"]
2033
+ self._test_index_criteria(
2034
+ cast(col[("nested", "elem2", "elem3", "elem4")], String)
2035
+ == '"elem5"',
2036
+ "r5",
2037
+ )
2038
+
2039
+ def test_string_cast_crit_against_string_basic(self):
2040
+ name = self.tables.data_table.c.name
2041
+ col = self.tables.data_table.c["data"]
2042
+
2043
+ self._test_index_criteria(
2044
+ and_(
2045
+ name == "r6",
2046
+ cast(col["b"], String) == '"some value"',
2047
+ ),
2048
+ "r6",
2049
+ )
2050
+
2051
+
2052
+ class EnumTest(_LiteralRoundTripFixture, fixtures.TablesTest):
2053
+ __backend__ = True
2054
+
2055
+ enum_values = "a", "b", "a%", "b%percent", "réveillé"
2056
+
2057
+ datatype = Enum(*enum_values, name="myenum")
2058
+
2059
+ @classmethod
2060
+ def define_tables(cls, metadata):
2061
+ Table(
2062
+ "enum_table",
2063
+ metadata,
2064
+ Column("id", Integer, primary_key=True),
2065
+ Column("enum_data", cls.datatype),
2066
+ )
2067
+
2068
+ @testing.combinations(*enum_values, argnames="data")
2069
+ def test_round_trip(self, data, connection):
2070
+ connection.execute(
2071
+ self.tables.enum_table.insert(), {"id": 1, "enum_data": data}
2072
+ )
2073
+
2074
+ eq_(
2075
+ connection.scalar(
2076
+ select(self.tables.enum_table.c.enum_data).where(
2077
+ self.tables.enum_table.c.id == 1
2078
+ )
2079
+ ),
2080
+ data,
2081
+ )
2082
+
2083
+ def test_round_trip_executemany(self, connection):
2084
+ connection.execute(
2085
+ self.tables.enum_table.insert(),
2086
+ [
2087
+ {"id": 1, "enum_data": "b%percent"},
2088
+ {"id": 2, "enum_data": "réveillé"},
2089
+ {"id": 3, "enum_data": "b"},
2090
+ {"id": 4, "enum_data": "a%"},
2091
+ ],
2092
+ )
2093
+
2094
+ eq_(
2095
+ connection.scalars(
2096
+ select(self.tables.enum_table.c.enum_data).order_by(
2097
+ self.tables.enum_table.c.id
2098
+ )
2099
+ ).all(),
2100
+ ["b%percent", "réveillé", "b", "a%"],
2101
+ )
2102
+
2103
+ @testing.requires.insert_executemany_returning
2104
+ def test_round_trip_executemany_returning(self, connection):
2105
+ result = connection.execute(
2106
+ self.tables.enum_table.insert().returning(
2107
+ self.tables.enum_table.c.enum_data
2108
+ ),
2109
+ [
2110
+ {"id": 1, "enum_data": "b%percent"},
2111
+ {"id": 2, "enum_data": "réveillé"},
2112
+ {"id": 3, "enum_data": "b"},
2113
+ {"id": 4, "enum_data": "a%"},
2114
+ ],
2115
+ )
2116
+
2117
+ eq_(result.scalars().all(), ["b%percent", "réveillé", "b", "a%"])
2118
+
2119
+
2120
+ class UuidTest(_LiteralRoundTripFixture, fixtures.TablesTest):
2121
+ __backend__ = True
2122
+
2123
+ datatype = Uuid
2124
+
2125
+ @classmethod
2126
+ def define_tables(cls, metadata):
2127
+ Table(
2128
+ "uuid_table",
2129
+ metadata,
2130
+ Column(
2131
+ "id", Integer, primary_key=True, test_needs_autoincrement=True
2132
+ ),
2133
+ Column("uuid_data", cls.datatype),
2134
+ Column("uuid_text_data", cls.datatype(as_uuid=False)),
2135
+ Column("uuid_data_nonnative", Uuid(native_uuid=False)),
2136
+ Column(
2137
+ "uuid_text_data_nonnative",
2138
+ Uuid(as_uuid=False, native_uuid=False),
2139
+ ),
2140
+ )
2141
+
2142
+ def test_uuid_round_trip(self, connection):
2143
+ data = uuid.uuid4()
2144
+ uuid_table = self.tables.uuid_table
2145
+
2146
+ connection.execute(
2147
+ uuid_table.insert(),
2148
+ {"id": 1, "uuid_data": data, "uuid_data_nonnative": data},
2149
+ )
2150
+ row = connection.execute(
2151
+ select(
2152
+ uuid_table.c.uuid_data, uuid_table.c.uuid_data_nonnative
2153
+ ).where(
2154
+ uuid_table.c.uuid_data == data,
2155
+ uuid_table.c.uuid_data_nonnative == data,
2156
+ )
2157
+ ).first()
2158
+ eq_(row, (data, data))
2159
+
2160
+ def test_uuid_text_round_trip(self, connection):
2161
+ data = str(uuid.uuid4())
2162
+ uuid_table = self.tables.uuid_table
2163
+
2164
+ connection.execute(
2165
+ uuid_table.insert(),
2166
+ {
2167
+ "id": 1,
2168
+ "uuid_text_data": data,
2169
+ "uuid_text_data_nonnative": data,
2170
+ },
2171
+ )
2172
+ row = connection.execute(
2173
+ select(
2174
+ uuid_table.c.uuid_text_data,
2175
+ uuid_table.c.uuid_text_data_nonnative,
2176
+ ).where(
2177
+ uuid_table.c.uuid_text_data == data,
2178
+ uuid_table.c.uuid_text_data_nonnative == data,
2179
+ )
2180
+ ).first()
2181
+ eq_((row[0].lower(), row[1].lower()), (data, data))
2182
+
2183
+ def test_literal_uuid(self, literal_round_trip):
2184
+ data = uuid.uuid4()
2185
+ literal_round_trip(self.datatype, [data], [data])
2186
+
2187
+ def test_literal_text(self, literal_round_trip):
2188
+ data = str(uuid.uuid4())
2189
+ literal_round_trip(
2190
+ self.datatype(as_uuid=False),
2191
+ [data],
2192
+ [data],
2193
+ filter_=lambda x: x.lower(),
2194
+ )
2195
+
2196
+ def test_literal_nonnative_uuid(self, literal_round_trip):
2197
+ data = uuid.uuid4()
2198
+ literal_round_trip(Uuid(native_uuid=False), [data], [data])
2199
+
2200
+ def test_literal_nonnative_text(self, literal_round_trip):
2201
+ data = str(uuid.uuid4())
2202
+ literal_round_trip(
2203
+ Uuid(as_uuid=False, native_uuid=False),
2204
+ [data],
2205
+ [data],
2206
+ filter_=lambda x: x.lower(),
2207
+ )
2208
+
2209
+ @testing.requires.insert_returning
2210
+ def test_uuid_returning(self, connection):
2211
+ data = uuid.uuid4()
2212
+ str_data = str(data)
2213
+ uuid_table = self.tables.uuid_table
2214
+
2215
+ result = connection.execute(
2216
+ uuid_table.insert().returning(
2217
+ uuid_table.c.uuid_data,
2218
+ uuid_table.c.uuid_text_data,
2219
+ uuid_table.c.uuid_data_nonnative,
2220
+ uuid_table.c.uuid_text_data_nonnative,
2221
+ ),
2222
+ {
2223
+ "id": 1,
2224
+ "uuid_data": data,
2225
+ "uuid_text_data": str_data,
2226
+ "uuid_data_nonnative": data,
2227
+ "uuid_text_data_nonnative": str_data,
2228
+ },
2229
+ )
2230
+ row = result.first()
2231
+
2232
+ eq_(row, (data, str_data, data, str_data))
2233
+
2234
+
2235
+ class NativeUUIDTest(UuidTest):
2236
+ __requires__ = ("uuid_data_type",)
2237
+
2238
+ datatype = UUID
2239
+
2240
+
2241
+ __all__ = (
2242
+ "ArrayTest",
2243
+ "BinaryTest",
2244
+ "UnicodeVarcharTest",
2245
+ "UnicodeTextTest",
2246
+ "JSONTest",
2247
+ "JSONLegacyStringCastIndexTest",
2248
+ "DateTest",
2249
+ "DateTimeTest",
2250
+ "DateTimeTZTest",
2251
+ "TextTest",
2252
+ "NumericTest",
2253
+ "IntegerTest",
2254
+ "IntervalTest",
2255
+ "PrecisionIntervalTest",
2256
+ "CastTypeDecoratorTest",
2257
+ "DateTimeHistoricTest",
2258
+ "DateTimeCoercedToDateTimeTest",
2259
+ "TimeMicrosecondsTest",
2260
+ "TimestampMicrosecondsTest",
2261
+ "TimeTest",
2262
+ "TimeTZTest",
2263
+ "TrueDivTest",
2264
+ "DateTimeMicrosecondsTest",
2265
+ "DateHistoricTest",
2266
+ "StringTest",
2267
+ "BooleanTest",
2268
+ "EnumTest",
2269
+ "UuidTest",
2270
+ "NativeUUIDTest",
2271
+ )