ydb-sqlalchemy 0.0.1b4__py2.py3-none-any.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.
- test/__init__.py +0 -0
- test/conftest.py +11 -0
- test/test_core.py +734 -0
- test/test_inspect.py +34 -0
- test/test_suite.py +553 -0
- test_dbapi/__init__.py +0 -0
- test_dbapi/test_dbapi.py +196 -0
- ydb_sqlalchemy/__init__.py +3 -0
- ydb_sqlalchemy/_version.py +1 -0
- ydb_sqlalchemy/dbapi/__init__.py +43 -0
- ydb_sqlalchemy/dbapi/connection.py +180 -0
- ydb_sqlalchemy/dbapi/constants.py +218 -0
- ydb_sqlalchemy/dbapi/cursor.py +304 -0
- ydb_sqlalchemy/dbapi/errors.py +103 -0
- ydb_sqlalchemy/sqlalchemy/__init__.py +754 -0
- ydb_sqlalchemy/sqlalchemy/dml.py +12 -0
- ydb_sqlalchemy/sqlalchemy/json.py +34 -0
- ydb_sqlalchemy/sqlalchemy/requirements.py +105 -0
- ydb_sqlalchemy/sqlalchemy/test_sqlalchemy.py +27 -0
- ydb_sqlalchemy/sqlalchemy/types.py +67 -0
- ydb_sqlalchemy-0.0.1b4.dist-info/LICENSE +201 -0
- ydb_sqlalchemy-0.0.1b4.dist-info/METADATA +89 -0
- ydb_sqlalchemy-0.0.1b4.dist-info/RECORD +26 -0
- ydb_sqlalchemy-0.0.1b4.dist-info/WHEEL +6 -0
- ydb_sqlalchemy-0.0.1b4.dist-info/entry_points.txt +6 -0
- ydb_sqlalchemy-0.0.1b4.dist-info/top_level.txt +3 -0
test/test_inspect.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sqlalchemy as sa
|
|
2
|
+
from sqlalchemy import Column, Integer, Numeric, Table, Unicode
|
|
3
|
+
from sqlalchemy.testing.fixtures import TablesTest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestInspection(TablesTest):
|
|
7
|
+
@classmethod
|
|
8
|
+
def define_tables(cls, metadata):
|
|
9
|
+
Table(
|
|
10
|
+
"test",
|
|
11
|
+
metadata,
|
|
12
|
+
Column("id", Integer, primary_key=True, nullable=False),
|
|
13
|
+
Column("value", Unicode),
|
|
14
|
+
Column("num", Numeric(22, 9)),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def test_get_columns(self, connection):
|
|
18
|
+
inspect = sa.inspect(connection)
|
|
19
|
+
|
|
20
|
+
columns = inspect.get_columns("test")
|
|
21
|
+
for c in columns:
|
|
22
|
+
c["type"] = type(c["type"])
|
|
23
|
+
|
|
24
|
+
assert columns == [
|
|
25
|
+
{"name": "id", "type": sa.INTEGER, "nullable": False, "default": None},
|
|
26
|
+
{"name": "value", "type": sa.TEXT, "nullable": True, "default": None},
|
|
27
|
+
{"name": "num", "type": sa.DECIMAL, "nullable": True, "default": None},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
def test_has_table(self, connection):
|
|
31
|
+
inspect = sa.inspect(connection)
|
|
32
|
+
|
|
33
|
+
assert inspect.has_table("test")
|
|
34
|
+
assert not inspect.has_table("foo")
|
test/test_suite.py
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import sqlalchemy as sa
|
|
5
|
+
import sqlalchemy.testing.suite.test_types
|
|
6
|
+
from sqlalchemy.testing import is_false, is_true
|
|
7
|
+
from sqlalchemy.testing.suite import * # noqa: F401, F403
|
|
8
|
+
from sqlalchemy.testing.suite import (
|
|
9
|
+
Column,
|
|
10
|
+
Integer,
|
|
11
|
+
MetaData,
|
|
12
|
+
String,
|
|
13
|
+
Table,
|
|
14
|
+
column,
|
|
15
|
+
config,
|
|
16
|
+
eq_,
|
|
17
|
+
exists,
|
|
18
|
+
fixtures,
|
|
19
|
+
func,
|
|
20
|
+
inspect,
|
|
21
|
+
literal_column,
|
|
22
|
+
provide_metadata,
|
|
23
|
+
requirements,
|
|
24
|
+
select,
|
|
25
|
+
testing,
|
|
26
|
+
union,
|
|
27
|
+
)
|
|
28
|
+
from sqlalchemy.testing.suite.test_ddl import (
|
|
29
|
+
LongNameBlowoutTest as _LongNameBlowoutTest,
|
|
30
|
+
)
|
|
31
|
+
from sqlalchemy.testing.suite.test_dialect import (
|
|
32
|
+
DifficultParametersTest as _DifficultParametersTest,
|
|
33
|
+
)
|
|
34
|
+
from sqlalchemy.testing.suite.test_dialect import EscapingTest as _EscapingTest
|
|
35
|
+
from sqlalchemy.testing.suite.test_insert import (
|
|
36
|
+
InsertBehaviorTest as _InsertBehaviorTest,
|
|
37
|
+
)
|
|
38
|
+
from sqlalchemy.testing.suite.test_reflection import (
|
|
39
|
+
ComponentReflectionTest as _ComponentReflectionTest,
|
|
40
|
+
)
|
|
41
|
+
from sqlalchemy.testing.suite.test_reflection import (
|
|
42
|
+
ComponentReflectionTestExtra as _ComponentReflectionTestExtra,
|
|
43
|
+
)
|
|
44
|
+
from sqlalchemy.testing.suite.test_reflection import (
|
|
45
|
+
CompositeKeyReflectionTest as _CompositeKeyReflectionTest,
|
|
46
|
+
)
|
|
47
|
+
from sqlalchemy.testing.suite.test_reflection import HasIndexTest as _HasIndexTest
|
|
48
|
+
from sqlalchemy.testing.suite.test_reflection import HasTableTest as _HasTableTest
|
|
49
|
+
from sqlalchemy.testing.suite.test_reflection import (
|
|
50
|
+
QuotedNameArgumentTest as _QuotedNameArgumentTest,
|
|
51
|
+
)
|
|
52
|
+
from sqlalchemy.testing.suite.test_results import RowFetchTest as _RowFetchTest
|
|
53
|
+
from sqlalchemy.testing.suite.test_select import ExistsTest as _ExistsTest
|
|
54
|
+
from sqlalchemy.testing.suite.test_select import (
|
|
55
|
+
FetchLimitOffsetTest as _FetchLimitOffsetTest,
|
|
56
|
+
)
|
|
57
|
+
from sqlalchemy.testing.suite.test_select import JoinTest as _JoinTest
|
|
58
|
+
from sqlalchemy.testing.suite.test_select import LikeFunctionsTest as _LikeFunctionsTest
|
|
59
|
+
from sqlalchemy.testing.suite.test_select import OrderByLabelTest as _OrderByLabelTest
|
|
60
|
+
from sqlalchemy.testing.suite.test_types import BinaryTest as _BinaryTest
|
|
61
|
+
from sqlalchemy.testing.suite.test_types import DateTest as _DateTest
|
|
62
|
+
from sqlalchemy.testing.suite.test_types import (
|
|
63
|
+
DateTimeCoercedToDateTimeTest as _DateTimeCoercedToDateTimeTest,
|
|
64
|
+
)
|
|
65
|
+
from sqlalchemy.testing.suite.test_types import (
|
|
66
|
+
DateTimeMicrosecondsTest as _DateTimeMicrosecondsTest,
|
|
67
|
+
)
|
|
68
|
+
from sqlalchemy.testing.suite.test_types import DateTimeTest as _DateTimeTest
|
|
69
|
+
from sqlalchemy.testing.suite.test_types import IntegerTest as _IntegerTest
|
|
70
|
+
from sqlalchemy.testing.suite.test_types import JSONTest as _JSONTest
|
|
71
|
+
from sqlalchemy.testing.suite.test_types import NativeUUIDTest as _NativeUUIDTest
|
|
72
|
+
from sqlalchemy.testing.suite.test_types import NumericTest as _NumericTest
|
|
73
|
+
from sqlalchemy.testing.suite.test_types import StringTest as _StringTest
|
|
74
|
+
from sqlalchemy.testing.suite.test_types import (
|
|
75
|
+
TimeMicrosecondsTest as _TimeMicrosecondsTest,
|
|
76
|
+
)
|
|
77
|
+
from sqlalchemy.testing.suite.test_types import (
|
|
78
|
+
TimestampMicrosecondsTest as _TimestampMicrosecondsTest,
|
|
79
|
+
)
|
|
80
|
+
from sqlalchemy.testing.suite.test_types import TimeTest as _TimeTest
|
|
81
|
+
from sqlalchemy.testing.suite.test_types import TrueDivTest as _TrueDivTest
|
|
82
|
+
|
|
83
|
+
from ydb_sqlalchemy.sqlalchemy import types as ydb_sa_types
|
|
84
|
+
|
|
85
|
+
test_types_suite = sqlalchemy.testing.suite.test_types
|
|
86
|
+
col_creator = test_types_suite.Column
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def column_getter(*args, **kwargs):
|
|
90
|
+
col = col_creator(*args, **kwargs)
|
|
91
|
+
if col.name == "x":
|
|
92
|
+
col.primary_key = True
|
|
93
|
+
return col
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
test_types_suite.Column = column_getter
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ComponentReflectionTest(_ComponentReflectionTest):
|
|
100
|
+
def _check_list(self, result, exp, req_keys=None, msg=None):
|
|
101
|
+
try:
|
|
102
|
+
return super()._check_list(result, exp, req_keys, msg)
|
|
103
|
+
except AssertionError as err:
|
|
104
|
+
err_info = err.args[0]
|
|
105
|
+
if "nullable" in err_info:
|
|
106
|
+
return "We changed nullable in define_reflected_tables method so won't check it."
|
|
107
|
+
if "constrained_columns" in err_info and "contains one more item: 'data'" in err_info:
|
|
108
|
+
return "We changed primary_keys in define_reflected_tables method so this will fail"
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def define_reflected_tables(cls, metadata, schema):
|
|
113
|
+
Table(
|
|
114
|
+
"users",
|
|
115
|
+
metadata,
|
|
116
|
+
Column("user_id", sa.INT, primary_key=True),
|
|
117
|
+
Column("test1", sa.CHAR(5)),
|
|
118
|
+
Column("test2", sa.Float()),
|
|
119
|
+
Column("parent_user_id", sa.Integer),
|
|
120
|
+
schema=schema,
|
|
121
|
+
test_needs_fk=True,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
Table(
|
|
125
|
+
"dingalings",
|
|
126
|
+
metadata,
|
|
127
|
+
Column("dingaling_id", sa.Integer, primary_key=True),
|
|
128
|
+
Column("address_id", sa.Integer),
|
|
129
|
+
Column("id_user", sa.Integer),
|
|
130
|
+
Column("data", sa.String(30)),
|
|
131
|
+
schema=schema,
|
|
132
|
+
test_needs_fk=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
Table(
|
|
136
|
+
"email_addresses",
|
|
137
|
+
metadata,
|
|
138
|
+
Column("address_id", sa.Integer, primary_key=True),
|
|
139
|
+
Column("remote_user_id", sa.Integer),
|
|
140
|
+
Column("email_address", sa.String(20)),
|
|
141
|
+
schema=schema,
|
|
142
|
+
test_needs_fk=True,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
Table(
|
|
146
|
+
"comment_test",
|
|
147
|
+
metadata,
|
|
148
|
+
Column("id", sa.Integer, primary_key=True, comment="id comment"),
|
|
149
|
+
Column("data", sa.String(20), comment="data % comment"),
|
|
150
|
+
Column("d2", sa.String(20), comment=r"""Comment types type speedily ' " \ '' Fun!"""),
|
|
151
|
+
schema=schema,
|
|
152
|
+
comment=r"""the test % ' " \ table comment""",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
Table(
|
|
156
|
+
"no_constraints",
|
|
157
|
+
metadata,
|
|
158
|
+
Column("data", sa.String(20), primary_key=True, nullable=True),
|
|
159
|
+
schema=schema,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@pytest.mark.skip("views unsupported")
|
|
163
|
+
def test_get_view_names(self, connection, use_schema):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
def test_metadata(self, connection, **kwargs):
|
|
167
|
+
m = MetaData()
|
|
168
|
+
m.reflect(connection, resolve_fks=False)
|
|
169
|
+
|
|
170
|
+
insp = inspect(connection)
|
|
171
|
+
tables = insp.get_table_names()
|
|
172
|
+
eq_(sorted(m.tables), sorted(tables))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class CompositeKeyReflectionTest(_CompositeKeyReflectionTest):
|
|
176
|
+
@classmethod
|
|
177
|
+
def define_tables(cls, metadata):
|
|
178
|
+
Table(
|
|
179
|
+
"tb1",
|
|
180
|
+
metadata,
|
|
181
|
+
Column("id", Integer),
|
|
182
|
+
Column("attr", Integer),
|
|
183
|
+
Column("name", sa.VARCHAR(20)),
|
|
184
|
+
# named pk unsupported
|
|
185
|
+
sa.PrimaryKeyConstraint("name", "id", "attr"),
|
|
186
|
+
schema=None,
|
|
187
|
+
test_needs_fk=True,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ComponentReflectionTestExtra(_ComponentReflectionTestExtra):
|
|
192
|
+
def _type_round_trip(self, connection, metadata, *types):
|
|
193
|
+
t = Table(
|
|
194
|
+
"t",
|
|
195
|
+
metadata,
|
|
196
|
+
# table without pk unsupported
|
|
197
|
+
*[Column("t%d" % i, type_, primary_key=True) for i, type_ in enumerate(types)],
|
|
198
|
+
)
|
|
199
|
+
t.create(connection)
|
|
200
|
+
return [c["type"] for c in inspect(connection).get_columns("t")]
|
|
201
|
+
|
|
202
|
+
@pytest.mark.skip("YDB: Only Decimal(22,9) is supported for table columns")
|
|
203
|
+
def test_numeric_reflection(self):
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
@pytest.mark.skip("TODO: varchar with length unsupported")
|
|
207
|
+
def test_varchar_reflection(self):
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
@testing.requires.table_reflection
|
|
211
|
+
def test_nullable_reflection(self, connection, metadata):
|
|
212
|
+
t = Table(
|
|
213
|
+
"t",
|
|
214
|
+
metadata,
|
|
215
|
+
# table without pk unsupported
|
|
216
|
+
Column("a", Integer, nullable=True, primary_key=True),
|
|
217
|
+
Column("b", Integer, nullable=False, primary_key=True),
|
|
218
|
+
)
|
|
219
|
+
t.create(connection)
|
|
220
|
+
eq_(
|
|
221
|
+
{col["name"]: col["nullable"] for col in inspect(connection).get_columns("t")},
|
|
222
|
+
{"a": True, "b": False},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class HasTableTest(_HasTableTest):
|
|
227
|
+
@classmethod
|
|
228
|
+
def define_tables(cls, metadata):
|
|
229
|
+
Table(
|
|
230
|
+
"test_table",
|
|
231
|
+
metadata,
|
|
232
|
+
Column("id", Integer, primary_key=True),
|
|
233
|
+
Column("data", String(50)),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def test_has_table_cache(self, metadata):
|
|
237
|
+
insp = inspect(config.db)
|
|
238
|
+
is_true(insp.has_table("test_table"))
|
|
239
|
+
# table without pk unsupported
|
|
240
|
+
nt = Table("new_table", metadata, Column("col", Integer, primary_key=True))
|
|
241
|
+
is_false(insp.has_table("new_table"))
|
|
242
|
+
nt.create(config.db)
|
|
243
|
+
try:
|
|
244
|
+
is_false(insp.has_table("new_table"))
|
|
245
|
+
insp.clear_cache()
|
|
246
|
+
is_true(insp.has_table("new_table"))
|
|
247
|
+
finally:
|
|
248
|
+
nt.drop(config.db)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@pytest.mark.skip("CREATE INDEX syntax unsupported")
|
|
252
|
+
class HasIndexTest(_HasIndexTest):
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@pytest.mark.skip("quotes unsupported in table names")
|
|
257
|
+
class QuotedNameArgumentTest(_QuotedNameArgumentTest):
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class IntegerTest(_IntegerTest):
|
|
262
|
+
@pytest.mark.skip("YQL doesn't support select with where without from")
|
|
263
|
+
def test_huge_int_auto_accommodation(self, connection, intvalue):
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.skip("TODO: fix & skip those tests - add Double/Decimal support. see #12")
|
|
268
|
+
class NumericTest(_NumericTest):
|
|
269
|
+
# SqlAlchemy maybe eat Decimal and throw Double
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@pytest.mark.skip("TODO: see issue #13")
|
|
274
|
+
class BinaryTest(_BinaryTest):
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class TrueDivTest(_TrueDivTest):
|
|
279
|
+
@pytest.mark.skip("Unsupported builtin: FLOOR")
|
|
280
|
+
def test_floordiv_numeric(self, connection, left, right, expected):
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
@pytest.mark.skip("Truediv unsupported for int")
|
|
284
|
+
def test_truediv_integer(self, connection, left, right, expected):
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
@pytest.mark.skip("Truediv unsupported for int")
|
|
288
|
+
def test_truediv_integer_bound(self, connection):
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
@pytest.mark.skip("Numeric is not Decimal")
|
|
292
|
+
def test_truediv_numeric(self):
|
|
293
|
+
# SqlAlchemy maybe eat Decimal and throw Double
|
|
294
|
+
pass
|
|
295
|
+
|
|
296
|
+
@testing.combinations(("6.25", "2.5", 2.5), argnames="left, right, expected")
|
|
297
|
+
def test_truediv_float(self, connection, left, right, expected):
|
|
298
|
+
eq_(
|
|
299
|
+
connection.scalar(select(literal_column(left, type_=sa.Float()) / literal_column(right, type_=sa.Float()))),
|
|
300
|
+
expected,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class ExistsTest(_ExistsTest):
|
|
305
|
+
"""
|
|
306
|
+
YDB says: Filtering is not allowed without FROM so rewrite queries
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def test_select_exists(self, connection):
|
|
310
|
+
stuff = self.tables.stuff
|
|
311
|
+
eq_(connection.execute(select(exists().where(stuff.c.data == "some data"))).fetchall(), [(True,)])
|
|
312
|
+
|
|
313
|
+
def test_select_exists_false(self, connection):
|
|
314
|
+
stuff = self.tables.stuff
|
|
315
|
+
eq_(connection.execute(select(exists().where(stuff.c.data == "no data"))).fetchall(), [(False,)])
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class LikeFunctionsTest(_LikeFunctionsTest):
|
|
319
|
+
@testing.requires.regexp_match
|
|
320
|
+
def test_not_regexp_match(self):
|
|
321
|
+
col = self.tables.some_table.c.data
|
|
322
|
+
# YDB fetch NULL columns too
|
|
323
|
+
self._test(~col.regexp_match("a.cde"), {2, 3, 4, 7, 8, 10, 11})
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class EscapingTest(_EscapingTest):
|
|
327
|
+
@provide_metadata
|
|
328
|
+
def test_percent_sign_round_trip(self):
|
|
329
|
+
"""test that the DBAPI accommodates for escaped / nonescaped
|
|
330
|
+
percent signs in a way that matches the compiler
|
|
331
|
+
|
|
332
|
+
"""
|
|
333
|
+
m = self.metadata
|
|
334
|
+
# table without pk unsupported
|
|
335
|
+
t = Table("t", m, Column("data", String(50), primary_key=True))
|
|
336
|
+
t.create(config.db)
|
|
337
|
+
with config.db.begin() as conn:
|
|
338
|
+
conn.execute(t.insert(), dict(data="some % value"))
|
|
339
|
+
conn.execute(t.insert(), dict(data="some %% other value"))
|
|
340
|
+
|
|
341
|
+
eq_(conn.scalar(select(t.c.data).where(t.c.data == literal_column("'some % value'"))), "some % value")
|
|
342
|
+
|
|
343
|
+
eq_(
|
|
344
|
+
conn.scalar(select(t.c.data).where(t.c.data == literal_column("'some %% other value'"))),
|
|
345
|
+
"some %% other value",
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@pytest.mark.skip("unsupported tricky names for columns")
|
|
350
|
+
class DifficultParametersTest(_DifficultParametersTest):
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@pytest.mark.skip("JOIN ON expression must be a conjunction of equality predicates")
|
|
355
|
+
class JoinTest(_JoinTest):
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class OrderByLabelTest(_OrderByLabelTest):
|
|
360
|
+
def test_composed_multiple(self):
|
|
361
|
+
table = self.tables.some_table
|
|
362
|
+
lx = (table.c.x + table.c.y).label("lx")
|
|
363
|
+
ly = (table.c.q + table.c.p).label("ly") # unknown builtin: lower
|
|
364
|
+
self._assert_result(
|
|
365
|
+
select(lx, ly).order_by(lx, ly.desc()),
|
|
366
|
+
[(3, "q1p3"), (5, "q2p2"), (7, "q3p1")],
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
@testing.requires.group_by_complex_expression
|
|
370
|
+
def test_group_by_composed(self):
|
|
371
|
+
"""
|
|
372
|
+
YDB says: column `some_table.x` must either be a key column in GROUP BY
|
|
373
|
+
or it should be used in aggregation function
|
|
374
|
+
"""
|
|
375
|
+
table = self.tables.some_table
|
|
376
|
+
expr = (table.c.x + table.c.y).label("lx")
|
|
377
|
+
stmt = select(func.count(table.c.id), column("lx")).group_by(expr).order_by(column("lx"))
|
|
378
|
+
self._assert_result(stmt, [(1, 3), (1, 5), (1, 7)])
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class FetchLimitOffsetTest(_FetchLimitOffsetTest):
|
|
382
|
+
def test_limit_render_multiple_times(self, connection):
|
|
383
|
+
"""
|
|
384
|
+
YQL does not support scalar subquery, so test was refiled with simple subquery
|
|
385
|
+
"""
|
|
386
|
+
table = self.tables.some_table
|
|
387
|
+
stmt = select(table.c.id).limit(1).subquery()
|
|
388
|
+
|
|
389
|
+
u = union(select(stmt), select(stmt)).subquery().select()
|
|
390
|
+
|
|
391
|
+
self._assert_result(
|
|
392
|
+
connection,
|
|
393
|
+
u,
|
|
394
|
+
[
|
|
395
|
+
(1,),
|
|
396
|
+
(1,),
|
|
397
|
+
],
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class InsertBehaviorTest(_InsertBehaviorTest):
|
|
402
|
+
@pytest.mark.skip("autoincrement unsupported")
|
|
403
|
+
def test_insert_from_select_autoinc(self, connection):
|
|
404
|
+
pass
|
|
405
|
+
|
|
406
|
+
@pytest.mark.skip("autoincrement unsupported")
|
|
407
|
+
def test_insert_from_select_autoinc_no_rows(self, connection):
|
|
408
|
+
pass
|
|
409
|
+
|
|
410
|
+
@pytest.mark.skip("implicit PK values unsupported")
|
|
411
|
+
def test_no_results_for_non_returning_insert(self, connection):
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class DateTest(_DateTest):
|
|
416
|
+
run_dispose_bind = "once"
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class DateTimeMicrosecondsTest(_DateTimeMicrosecondsTest):
|
|
420
|
+
run_dispose_bind = "once"
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class DateTimeTest(_DateTimeTest):
|
|
424
|
+
run_dispose_bind = "once"
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class TimestampMicrosecondsTest(_TimestampMicrosecondsTest):
|
|
428
|
+
run_dispose_bind = "once"
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@pytest.mark.skip("unsupported Time data type")
|
|
432
|
+
class TimeTest(_TimeTest):
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class JSONTest(_JSONTest):
|
|
437
|
+
@classmethod
|
|
438
|
+
def define_tables(cls, metadata):
|
|
439
|
+
Table(
|
|
440
|
+
"data_table",
|
|
441
|
+
metadata,
|
|
442
|
+
Column("id", Integer, primary_key=True, default=1),
|
|
443
|
+
Column("name", String(30), primary_key=True, nullable=False),
|
|
444
|
+
Column("data", cls.datatype, nullable=False),
|
|
445
|
+
Column("nulldata", cls.datatype(none_as_null=True)),
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
def _json_value_insert(self, connection, datatype, value, data_element):
|
|
449
|
+
if datatype == "float" and value is not None:
|
|
450
|
+
# As python's float is stored as C double, it needs to be shrank
|
|
451
|
+
value = ctypes.c_float(value).value
|
|
452
|
+
return super()._json_value_insert(connection, datatype, value, data_element)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class StringTest(_StringTest):
|
|
456
|
+
@requirements.unbounded_varchar
|
|
457
|
+
def test_nolength_string(self):
|
|
458
|
+
metadata = MetaData()
|
|
459
|
+
# table without pk unsupported
|
|
460
|
+
foo = Table("foo", metadata, Column("one", String, primary_key=True))
|
|
461
|
+
foo.create(config.db)
|
|
462
|
+
foo.drop(config.db)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class ContainerTypesTest(fixtures.TablesTest):
|
|
466
|
+
@classmethod
|
|
467
|
+
def define_tables(cls, metadata):
|
|
468
|
+
Table(
|
|
469
|
+
"container_types_test",
|
|
470
|
+
metadata,
|
|
471
|
+
Column("id", Integer),
|
|
472
|
+
sa.PrimaryKeyConstraint("id"),
|
|
473
|
+
schema=None,
|
|
474
|
+
test_needs_fk=True,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
def test_ARRAY_bind_variable(self, connection):
|
|
478
|
+
table = self.tables.container_types_test
|
|
479
|
+
|
|
480
|
+
connection.execute(sa.insert(table).values([{"id": 1}, {"id": 2}, {"id": 3}]))
|
|
481
|
+
|
|
482
|
+
stmt = select(table.c.id).where(table.c.id.in_(sa.bindparam("id", type_=sa.ARRAY(sa.Integer))))
|
|
483
|
+
|
|
484
|
+
eq_(connection.execute(stmt, {"id": [1, 2]}).fetchall(), [(1,), (2,)])
|
|
485
|
+
|
|
486
|
+
def test_list_type_bind_variable(self, connection):
|
|
487
|
+
table = self.tables.container_types_test
|
|
488
|
+
|
|
489
|
+
connection.execute(sa.insert(table).values([{"id": 1}, {"id": 2}, {"id": 3}]))
|
|
490
|
+
|
|
491
|
+
stmt = select(table.c.id).where(table.c.id.in_(sa.bindparam("id", type_=ydb_sa_types.ListType(sa.Integer))))
|
|
492
|
+
|
|
493
|
+
eq_(connection.execute(stmt, {"id": [1, 2]}).fetchall(), [(1,), (2,)])
|
|
494
|
+
|
|
495
|
+
def test_struct_type_bind_variable(self, connection):
|
|
496
|
+
table = self.tables.container_types_test
|
|
497
|
+
|
|
498
|
+
connection.execute(sa.insert(table).values([{"id": 1}, {"id": 2}, {"id": 3}]))
|
|
499
|
+
|
|
500
|
+
stmt = select(table.c.id).where(
|
|
501
|
+
table.c.id
|
|
502
|
+
== sa.text(":struct.id").bindparams(
|
|
503
|
+
sa.bindparam("struct", type_=ydb_sa_types.StructType({"id": sa.Integer})),
|
|
504
|
+
)
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
eq_(connection.scalar(stmt, {"struct": {"id": 1}}), 1)
|
|
508
|
+
|
|
509
|
+
def test_from_as_table(self, connection):
|
|
510
|
+
table = self.tables.container_types_test
|
|
511
|
+
|
|
512
|
+
connection.execute(
|
|
513
|
+
sa.insert(table).from_select(
|
|
514
|
+
["id"],
|
|
515
|
+
sa.select(sa.column("id")).select_from(
|
|
516
|
+
sa.func.as_table(
|
|
517
|
+
sa.bindparam(
|
|
518
|
+
"data",
|
|
519
|
+
value=[{"id": 1}, {"id": 2}, {"id": 3}],
|
|
520
|
+
type_=ydb_sa_types.ListType(ydb_sa_types.StructType({"id": sa.Integer})),
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
),
|
|
524
|
+
)
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
eq_(connection.execute(sa.select(table)).fetchall(), [(1,), (2,), (3,)])
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@pytest.mark.skip("uuid unsupported for columns")
|
|
531
|
+
class NativeUUIDTest(_NativeUUIDTest):
|
|
532
|
+
pass
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
@pytest.mark.skip("unsupported Time data type")
|
|
536
|
+
class TimeMicrosecondsTest(_TimeMicrosecondsTest):
|
|
537
|
+
pass
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
@pytest.mark.skip("unsupported coerce dates from datetime")
|
|
541
|
+
class DateTimeCoercedToDateTimeTest(_DateTimeCoercedToDateTimeTest):
|
|
542
|
+
pass
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
@pytest.mark.skip("named constraints unsupported")
|
|
546
|
+
class LongNameBlowoutTest(_LongNameBlowoutTest):
|
|
547
|
+
pass
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class RowFetchTest(_RowFetchTest):
|
|
551
|
+
@pytest.mark.skip("scalar subquery unsupported")
|
|
552
|
+
def test_row_w_scalar_select(self, connection):
|
|
553
|
+
pass
|
test_dbapi/__init__.py
ADDED
|
File without changes
|