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