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_dbapi/test_dbapi.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
import sqlalchemy.util as util
|
|
6
|
+
import ydb
|
|
7
|
+
|
|
8
|
+
import ydb_sqlalchemy.dbapi as dbapi
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseDBApiTestSuit:
|
|
12
|
+
def _test_isolation_level_read_only(self, connection: dbapi.Connection, isolation_level: str, read_only: bool):
|
|
13
|
+
connection.cursor().execute(
|
|
14
|
+
dbapi.YdbQuery("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))", is_ddl=True)
|
|
15
|
+
)
|
|
16
|
+
connection.set_isolation_level(isolation_level)
|
|
17
|
+
|
|
18
|
+
cursor = connection.cursor()
|
|
19
|
+
|
|
20
|
+
connection.begin()
|
|
21
|
+
|
|
22
|
+
query = dbapi.YdbQuery("UPSERT INTO foo(id) VALUES (1)")
|
|
23
|
+
if read_only:
|
|
24
|
+
with pytest.raises(dbapi.DatabaseError):
|
|
25
|
+
cursor.execute(query)
|
|
26
|
+
else:
|
|
27
|
+
cursor.execute(query)
|
|
28
|
+
|
|
29
|
+
connection.rollback()
|
|
30
|
+
|
|
31
|
+
connection.cursor().execute(dbapi.YdbQuery("DROP TABLE foo", is_ddl=True))
|
|
32
|
+
connection.cursor().close()
|
|
33
|
+
|
|
34
|
+
def _test_connection(self, connection: dbapi.Connection):
|
|
35
|
+
connection.commit()
|
|
36
|
+
connection.rollback()
|
|
37
|
+
|
|
38
|
+
cur = connection.cursor()
|
|
39
|
+
with suppress(dbapi.DatabaseError):
|
|
40
|
+
cur.execute(dbapi.YdbQuery("DROP TABLE foo", is_ddl=True))
|
|
41
|
+
|
|
42
|
+
assert not connection.check_exists("/local/foo")
|
|
43
|
+
with pytest.raises(dbapi.ProgrammingError):
|
|
44
|
+
connection.describe("/local/foo")
|
|
45
|
+
|
|
46
|
+
cur.execute(dbapi.YdbQuery("CREATE TABLE foo(id Int64 NOT NULL, PRIMARY KEY (id))", is_ddl=True))
|
|
47
|
+
|
|
48
|
+
assert connection.check_exists("/local/foo")
|
|
49
|
+
|
|
50
|
+
col = connection.describe("/local/foo").columns[0]
|
|
51
|
+
assert col.name == "id"
|
|
52
|
+
assert col.type == ydb.PrimitiveType.Int64
|
|
53
|
+
|
|
54
|
+
cur.execute(dbapi.YdbQuery("DROP TABLE foo", is_ddl=True))
|
|
55
|
+
cur.close()
|
|
56
|
+
|
|
57
|
+
def _test_cursor_raw_query(self, connection: dbapi.Connection):
|
|
58
|
+
cur = connection.cursor()
|
|
59
|
+
assert cur
|
|
60
|
+
|
|
61
|
+
with suppress(dbapi.DatabaseError):
|
|
62
|
+
cur.execute(dbapi.YdbQuery("DROP TABLE test", is_ddl=True))
|
|
63
|
+
|
|
64
|
+
cur.execute(dbapi.YdbQuery("CREATE TABLE test(id Int64 NOT NULL, text Utf8, PRIMARY KEY (id))", is_ddl=True))
|
|
65
|
+
|
|
66
|
+
cur.execute(
|
|
67
|
+
dbapi.YdbQuery(
|
|
68
|
+
"""
|
|
69
|
+
DECLARE $data AS List<Struct<id:Int64, text: Utf8>>;
|
|
70
|
+
|
|
71
|
+
INSERT INTO test SELECT id, text FROM AS_TABLE($data);
|
|
72
|
+
""",
|
|
73
|
+
parameters_types={
|
|
74
|
+
"$data": ydb.ListType(
|
|
75
|
+
ydb.StructType()
|
|
76
|
+
.add_member("id", ydb.PrimitiveType.Int64)
|
|
77
|
+
.add_member("text", ydb.PrimitiveType.Utf8)
|
|
78
|
+
)
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
{
|
|
82
|
+
"$data": [
|
|
83
|
+
{"id": 17, "text": "seventeen"},
|
|
84
|
+
{"id": 21, "text": "twenty one"},
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
cur.execute(dbapi.YdbQuery("DROP TABLE test", is_ddl=True))
|
|
90
|
+
|
|
91
|
+
cur.close()
|
|
92
|
+
|
|
93
|
+
def _test_errors(self, connection: dbapi.Connection):
|
|
94
|
+
with pytest.raises(dbapi.InterfaceError):
|
|
95
|
+
dbapi.YdbDBApi().connect("localhost:2136", database="/local666")
|
|
96
|
+
|
|
97
|
+
cur = connection.cursor()
|
|
98
|
+
|
|
99
|
+
with suppress(dbapi.DatabaseError):
|
|
100
|
+
cur.execute(dbapi.YdbQuery("DROP TABLE test", is_ddl=True))
|
|
101
|
+
|
|
102
|
+
with pytest.raises(dbapi.DataError):
|
|
103
|
+
cur.execute(dbapi.YdbQuery("SELECT 18446744073709551616"))
|
|
104
|
+
|
|
105
|
+
with pytest.raises(dbapi.DataError):
|
|
106
|
+
cur.execute(dbapi.YdbQuery("SELECT * FROM 拉屎"))
|
|
107
|
+
|
|
108
|
+
with pytest.raises(dbapi.DataError):
|
|
109
|
+
cur.execute(dbapi.YdbQuery("SELECT floor(5 / 2)"))
|
|
110
|
+
|
|
111
|
+
with pytest.raises(dbapi.ProgrammingError):
|
|
112
|
+
cur.execute(dbapi.YdbQuery("SELECT * FROM test"))
|
|
113
|
+
|
|
114
|
+
cur.execute(dbapi.YdbQuery("CREATE TABLE test(id Int64, PRIMARY KEY (id))", is_ddl=True))
|
|
115
|
+
|
|
116
|
+
cur.execute(dbapi.YdbQuery("INSERT INTO test(id) VALUES(1)"))
|
|
117
|
+
with pytest.raises(dbapi.IntegrityError):
|
|
118
|
+
cur.execute(dbapi.YdbQuery("INSERT INTO test(id) VALUES(1)"))
|
|
119
|
+
|
|
120
|
+
cur.execute(dbapi.YdbQuery("DROP TABLE test", is_ddl=True))
|
|
121
|
+
cur.close()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class TestSyncConnection(BaseDBApiTestSuit):
|
|
125
|
+
@pytest.fixture
|
|
126
|
+
def sync_connection(self) -> dbapi.Connection:
|
|
127
|
+
conn = dbapi.YdbDBApi().connect(host="localhost", port="2136", database="/local")
|
|
128
|
+
try:
|
|
129
|
+
yield conn
|
|
130
|
+
finally:
|
|
131
|
+
conn.close()
|
|
132
|
+
|
|
133
|
+
@pytest.mark.parametrize(
|
|
134
|
+
"isolation_level, read_only",
|
|
135
|
+
[
|
|
136
|
+
(dbapi.IsolationLevel.SERIALIZABLE, False),
|
|
137
|
+
(dbapi.IsolationLevel.AUTOCOMMIT, False),
|
|
138
|
+
(dbapi.IsolationLevel.ONLINE_READONLY, True),
|
|
139
|
+
(dbapi.IsolationLevel.ONLINE_READONLY_INCONSISTENT, True),
|
|
140
|
+
(dbapi.IsolationLevel.STALE_READONLY, True),
|
|
141
|
+
(dbapi.IsolationLevel.SNAPSHOT_READONLY, True),
|
|
142
|
+
],
|
|
143
|
+
)
|
|
144
|
+
def test_isolation_level_read_only(self, isolation_level: str, read_only: bool, sync_connection: dbapi.Connection):
|
|
145
|
+
self._test_isolation_level_read_only(sync_connection, isolation_level, read_only)
|
|
146
|
+
|
|
147
|
+
def test_connection(self, sync_connection: dbapi.Connection):
|
|
148
|
+
self._test_connection(sync_connection)
|
|
149
|
+
|
|
150
|
+
def test_cursor_raw_query(self, sync_connection: dbapi.Connection):
|
|
151
|
+
return self._test_cursor_raw_query(sync_connection)
|
|
152
|
+
|
|
153
|
+
def test_errors(self, sync_connection: dbapi.Connection):
|
|
154
|
+
return self._test_errors(sync_connection)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class TestAsyncConnection(BaseDBApiTestSuit):
|
|
158
|
+
@pytest_asyncio.fixture
|
|
159
|
+
async def async_connection(self) -> dbapi.AsyncConnection:
|
|
160
|
+
def connect():
|
|
161
|
+
return dbapi.YdbDBApi().async_connect(host="localhost", port="2136", database="/local")
|
|
162
|
+
|
|
163
|
+
conn = await util.greenlet_spawn(connect)
|
|
164
|
+
try:
|
|
165
|
+
yield conn
|
|
166
|
+
finally:
|
|
167
|
+
await util.greenlet_spawn(conn.close)
|
|
168
|
+
|
|
169
|
+
@pytest.mark.asyncio
|
|
170
|
+
@pytest.mark.parametrize(
|
|
171
|
+
"isolation_level, read_only",
|
|
172
|
+
[
|
|
173
|
+
(dbapi.IsolationLevel.SERIALIZABLE, False),
|
|
174
|
+
(dbapi.IsolationLevel.AUTOCOMMIT, False),
|
|
175
|
+
(dbapi.IsolationLevel.ONLINE_READONLY, True),
|
|
176
|
+
(dbapi.IsolationLevel.ONLINE_READONLY_INCONSISTENT, True),
|
|
177
|
+
(dbapi.IsolationLevel.STALE_READONLY, True),
|
|
178
|
+
(dbapi.IsolationLevel.SNAPSHOT_READONLY, True),
|
|
179
|
+
],
|
|
180
|
+
)
|
|
181
|
+
async def test_isolation_level_read_only(
|
|
182
|
+
self, isolation_level: str, read_only: bool, async_connection: dbapi.AsyncConnection
|
|
183
|
+
):
|
|
184
|
+
await util.greenlet_spawn(self._test_isolation_level_read_only, async_connection, isolation_level, read_only)
|
|
185
|
+
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_connection(self, async_connection: dbapi.AsyncConnection):
|
|
188
|
+
await util.greenlet_spawn(self._test_connection, async_connection)
|
|
189
|
+
|
|
190
|
+
@pytest.mark.asyncio
|
|
191
|
+
async def test_cursor_raw_query(self, async_connection: dbapi.AsyncConnection):
|
|
192
|
+
await util.greenlet_spawn(self._test_cursor_raw_query, async_connection)
|
|
193
|
+
|
|
194
|
+
@pytest.mark.asyncio
|
|
195
|
+
async def test_errors(self, async_connection: dbapi.AsyncConnection):
|
|
196
|
+
await util.greenlet_spawn(self._test_errors, async_connection)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.0.1b4"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .connection import AsyncConnection, Connection, IsolationLevel # noqa: F401
|
|
2
|
+
from .cursor import AsyncCursor, Cursor, YdbQuery # noqa: F401
|
|
3
|
+
from .errors import (
|
|
4
|
+
DatabaseError,
|
|
5
|
+
DataError,
|
|
6
|
+
Error,
|
|
7
|
+
IntegrityError,
|
|
8
|
+
InterfaceError,
|
|
9
|
+
InternalError,
|
|
10
|
+
NotSupportedError,
|
|
11
|
+
OperationalError,
|
|
12
|
+
ProgrammingError,
|
|
13
|
+
Warning,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class YdbDBApi:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.paramstyle = "pyformat"
|
|
20
|
+
self.threadsafety = 0
|
|
21
|
+
self.apilevel = "1.0"
|
|
22
|
+
self._init_dbapi_attributes()
|
|
23
|
+
|
|
24
|
+
def _init_dbapi_attributes(self):
|
|
25
|
+
for name, value in {
|
|
26
|
+
"Warning": Warning,
|
|
27
|
+
"Error": Error,
|
|
28
|
+
"InterfaceError": InterfaceError,
|
|
29
|
+
"DatabaseError": DatabaseError,
|
|
30
|
+
"DataError": DataError,
|
|
31
|
+
"OperationalError": OperationalError,
|
|
32
|
+
"IntegrityError": IntegrityError,
|
|
33
|
+
"InternalError": InternalError,
|
|
34
|
+
"ProgrammingError": ProgrammingError,
|
|
35
|
+
"NotSupportedError": NotSupportedError,
|
|
36
|
+
}.items():
|
|
37
|
+
setattr(self, name, value)
|
|
38
|
+
|
|
39
|
+
def connect(self, *args, **kwargs) -> Connection:
|
|
40
|
+
return Connection(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
def async_connect(self, *args, **kwargs) -> AsyncConnection:
|
|
43
|
+
return AsyncConnection(*args, **kwargs)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import posixpath
|
|
3
|
+
from typing import Any, List, NamedTuple, Optional
|
|
4
|
+
|
|
5
|
+
import sqlalchemy.util as util
|
|
6
|
+
import ydb
|
|
7
|
+
|
|
8
|
+
from .cursor import AsyncCursor, Cursor
|
|
9
|
+
from .errors import InterfaceError, InternalError, NotSupportedError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IsolationLevel:
|
|
13
|
+
SERIALIZABLE = "SERIALIZABLE"
|
|
14
|
+
ONLINE_READONLY = "ONLINE READONLY"
|
|
15
|
+
ONLINE_READONLY_INCONSISTENT = "ONLINE READONLY INCONSISTENT"
|
|
16
|
+
STALE_READONLY = "STALE READONLY"
|
|
17
|
+
SNAPSHOT_READONLY = "SNAPSHOT READONLY"
|
|
18
|
+
AUTOCOMMIT = "AUTOCOMMIT"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Connection:
|
|
22
|
+
_await = staticmethod(util.await_only)
|
|
23
|
+
|
|
24
|
+
_is_async = False
|
|
25
|
+
_ydb_driver_class = ydb.Driver
|
|
26
|
+
_ydb_session_pool_class = ydb.SessionPool
|
|
27
|
+
_ydb_table_client_class = ydb.TableClient
|
|
28
|
+
_cursor_class = Cursor
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
host: str = "",
|
|
33
|
+
port: str = "",
|
|
34
|
+
database: str = "",
|
|
35
|
+
**conn_kwargs: Any,
|
|
36
|
+
):
|
|
37
|
+
self.endpoint = f"grpc://{host}:{port}"
|
|
38
|
+
self.database = database
|
|
39
|
+
self.conn_kwargs = conn_kwargs
|
|
40
|
+
|
|
41
|
+
if "ydb_session_pool" in self.conn_kwargs: # Use session pool managed manually
|
|
42
|
+
self._shared_session_pool = True
|
|
43
|
+
self.session_pool: ydb.SessionPool = self.conn_kwargs.pop("ydb_session_pool")
|
|
44
|
+
self.driver = (
|
|
45
|
+
self.session_pool._driver
|
|
46
|
+
if hasattr(self.session_pool, "_driver")
|
|
47
|
+
else self.session_pool._pool_impl._driver
|
|
48
|
+
)
|
|
49
|
+
self.driver.table_client = self._ydb_table_client_class(self.driver, self._get_table_client_settings())
|
|
50
|
+
else:
|
|
51
|
+
self._shared_session_pool = False
|
|
52
|
+
self.driver = self._create_driver()
|
|
53
|
+
self.session_pool = self._ydb_session_pool_class(self.driver, size=5)
|
|
54
|
+
|
|
55
|
+
self.interactive_transaction: bool = False # AUTOCOMMIT
|
|
56
|
+
self.tx_mode: ydb.AbstractTransactionModeBuilder = ydb.SerializableReadWrite()
|
|
57
|
+
self.tx_context: Optional[ydb.TxContext] = None
|
|
58
|
+
|
|
59
|
+
def cursor(self):
|
|
60
|
+
return self._cursor_class(self.session_pool, self.tx_mode, self.tx_context)
|
|
61
|
+
|
|
62
|
+
def describe(self, table_path: str) -> ydb.TableDescription:
|
|
63
|
+
abs_table_path = posixpath.join(self.database, table_path)
|
|
64
|
+
cursor = self.cursor()
|
|
65
|
+
return cursor.describe_table(abs_table_path)
|
|
66
|
+
|
|
67
|
+
def check_exists(self, table_path: str) -> ydb.SchemeEntry:
|
|
68
|
+
cursor = self.cursor()
|
|
69
|
+
return cursor.check_exists(table_path)
|
|
70
|
+
|
|
71
|
+
def get_table_names(self) -> List[str]:
|
|
72
|
+
cursor = self.cursor()
|
|
73
|
+
return cursor.get_table_names()
|
|
74
|
+
|
|
75
|
+
def set_isolation_level(self, isolation_level: str):
|
|
76
|
+
class IsolationSettings(NamedTuple):
|
|
77
|
+
ydb_mode: ydb.AbstractTransactionModeBuilder
|
|
78
|
+
interactive: bool
|
|
79
|
+
|
|
80
|
+
ydb_isolation_settings_map = {
|
|
81
|
+
IsolationLevel.AUTOCOMMIT: IsolationSettings(ydb.SerializableReadWrite(), interactive=False),
|
|
82
|
+
IsolationLevel.SERIALIZABLE: IsolationSettings(ydb.SerializableReadWrite(), interactive=True),
|
|
83
|
+
IsolationLevel.ONLINE_READONLY: IsolationSettings(ydb.OnlineReadOnly(), interactive=False),
|
|
84
|
+
IsolationLevel.ONLINE_READONLY_INCONSISTENT: IsolationSettings(
|
|
85
|
+
ydb.OnlineReadOnly().with_allow_inconsistent_reads(), interactive=False
|
|
86
|
+
),
|
|
87
|
+
IsolationLevel.STALE_READONLY: IsolationSettings(ydb.StaleReadOnly(), interactive=False),
|
|
88
|
+
IsolationLevel.SNAPSHOT_READONLY: IsolationSettings(ydb.SnapshotReadOnly(), interactive=True),
|
|
89
|
+
}
|
|
90
|
+
ydb_isolation_settings = ydb_isolation_settings_map[isolation_level]
|
|
91
|
+
if self.tx_context and self.tx_context.tx_id:
|
|
92
|
+
raise InternalError("Failed to set transaction mode: transaction is already began")
|
|
93
|
+
self.tx_mode = ydb_isolation_settings.ydb_mode
|
|
94
|
+
self.interactive_transaction = ydb_isolation_settings.interactive
|
|
95
|
+
|
|
96
|
+
def get_isolation_level(self) -> str:
|
|
97
|
+
if self.tx_mode.name == ydb.SerializableReadWrite().name:
|
|
98
|
+
if self.interactive_transaction:
|
|
99
|
+
return IsolationLevel.SERIALIZABLE
|
|
100
|
+
else:
|
|
101
|
+
return IsolationLevel.AUTOCOMMIT
|
|
102
|
+
elif self.tx_mode.name == ydb.OnlineReadOnly().name:
|
|
103
|
+
if self.tx_mode.settings.allow_inconsistent_reads:
|
|
104
|
+
return IsolationLevel.ONLINE_READONLY_INCONSISTENT
|
|
105
|
+
else:
|
|
106
|
+
return IsolationLevel.ONLINE_READONLY
|
|
107
|
+
elif self.tx_mode.name == ydb.StaleReadOnly().name:
|
|
108
|
+
return IsolationLevel.STALE_READONLY
|
|
109
|
+
elif self.tx_mode.name == ydb.SnapshotReadOnly().name:
|
|
110
|
+
return IsolationLevel.SNAPSHOT_READONLY
|
|
111
|
+
else:
|
|
112
|
+
raise NotSupportedError(f"{self.tx_mode.name} is not supported")
|
|
113
|
+
|
|
114
|
+
def begin(self):
|
|
115
|
+
self.tx_context = None
|
|
116
|
+
if self.interactive_transaction:
|
|
117
|
+
session = self._maybe_await(self.session_pool.acquire)
|
|
118
|
+
self.tx_context = session.transaction(self.tx_mode)
|
|
119
|
+
self._maybe_await(self.tx_context.begin)
|
|
120
|
+
|
|
121
|
+
def commit(self):
|
|
122
|
+
if self.tx_context and self.tx_context.tx_id:
|
|
123
|
+
self._maybe_await(self.tx_context.commit)
|
|
124
|
+
self._maybe_await(self.session_pool.release, self.tx_context.session)
|
|
125
|
+
self.tx_context = None
|
|
126
|
+
|
|
127
|
+
def rollback(self):
|
|
128
|
+
if self.tx_context and self.tx_context.tx_id:
|
|
129
|
+
self._maybe_await(self.tx_context.rollback)
|
|
130
|
+
self._maybe_await(self.session_pool.release, self.tx_context.session)
|
|
131
|
+
self.tx_context = None
|
|
132
|
+
|
|
133
|
+
def close(self):
|
|
134
|
+
self.rollback()
|
|
135
|
+
if not self._shared_session_pool:
|
|
136
|
+
self._maybe_await(self.session_pool.stop)
|
|
137
|
+
self._stop_driver()
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def _maybe_await(cls, callee: collections.abc.Callable, *args, **kwargs) -> Any:
|
|
141
|
+
if cls._is_async:
|
|
142
|
+
return cls._await(callee(*args, **kwargs))
|
|
143
|
+
return callee(*args, **kwargs)
|
|
144
|
+
|
|
145
|
+
def _get_table_client_settings(self) -> ydb.TableClientSettings:
|
|
146
|
+
return (
|
|
147
|
+
ydb.TableClientSettings()
|
|
148
|
+
.with_native_date_in_result_sets(True)
|
|
149
|
+
.with_native_datetime_in_result_sets(True)
|
|
150
|
+
.with_native_timestamp_in_result_sets(True)
|
|
151
|
+
.with_native_interval_in_result_sets(True)
|
|
152
|
+
.with_native_json_in_result_sets(False)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _create_driver(self):
|
|
156
|
+
driver_config = ydb.DriverConfig(
|
|
157
|
+
endpoint=self.endpoint,
|
|
158
|
+
database=self.database,
|
|
159
|
+
table_client_settings=self._get_table_client_settings(),
|
|
160
|
+
)
|
|
161
|
+
driver = self._ydb_driver_class(driver_config)
|
|
162
|
+
try:
|
|
163
|
+
self._maybe_await(driver.wait, timeout=5, fail_fast=True)
|
|
164
|
+
except ydb.Error as e:
|
|
165
|
+
raise InterfaceError(e.message, original_error=e) from e
|
|
166
|
+
except Exception as e:
|
|
167
|
+
self._maybe_await(driver.stop)
|
|
168
|
+
raise InterfaceError(f"Failed to connect to YDB, details {driver.discovery_debug_details()}") from e
|
|
169
|
+
return driver
|
|
170
|
+
|
|
171
|
+
def _stop_driver(self):
|
|
172
|
+
self._maybe_await(self.driver.stop)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class AsyncConnection(Connection):
|
|
176
|
+
_is_async = True
|
|
177
|
+
_ydb_driver_class = ydb.aio.Driver
|
|
178
|
+
_ydb_session_pool_class = ydb.aio.SessionPool
|
|
179
|
+
_ydb_table_client_class = ydb.aio.table.TableClient
|
|
180
|
+
_cursor_class = AsyncCursor
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
YDB_KEYWORDS = {
|
|
2
|
+
"abort",
|
|
3
|
+
"action",
|
|
4
|
+
"add",
|
|
5
|
+
"after",
|
|
6
|
+
"all",
|
|
7
|
+
"alter",
|
|
8
|
+
"analyze",
|
|
9
|
+
"and",
|
|
10
|
+
"ansi",
|
|
11
|
+
"any",
|
|
12
|
+
"array",
|
|
13
|
+
"as",
|
|
14
|
+
"asc",
|
|
15
|
+
"assume",
|
|
16
|
+
"async",
|
|
17
|
+
"attach",
|
|
18
|
+
"autoincrement",
|
|
19
|
+
"before",
|
|
20
|
+
"begin",
|
|
21
|
+
"bernoulli",
|
|
22
|
+
"between",
|
|
23
|
+
"bitcast",
|
|
24
|
+
"by",
|
|
25
|
+
"cascade",
|
|
26
|
+
"case",
|
|
27
|
+
"cast",
|
|
28
|
+
"changefeed",
|
|
29
|
+
"check",
|
|
30
|
+
"collate",
|
|
31
|
+
"column",
|
|
32
|
+
"columns",
|
|
33
|
+
"commit",
|
|
34
|
+
"compact",
|
|
35
|
+
"conditional",
|
|
36
|
+
"conflict",
|
|
37
|
+
"constraint",
|
|
38
|
+
"consumer",
|
|
39
|
+
"cover",
|
|
40
|
+
"create",
|
|
41
|
+
"cross",
|
|
42
|
+
"cube",
|
|
43
|
+
"current",
|
|
44
|
+
"current_date",
|
|
45
|
+
"current_time",
|
|
46
|
+
"current_timestamp",
|
|
47
|
+
"data",
|
|
48
|
+
"database",
|
|
49
|
+
"decimal",
|
|
50
|
+
"declare",
|
|
51
|
+
"default",
|
|
52
|
+
"deferrable",
|
|
53
|
+
"deferred",
|
|
54
|
+
"define",
|
|
55
|
+
"delete",
|
|
56
|
+
"desc",
|
|
57
|
+
"detach",
|
|
58
|
+
"disable",
|
|
59
|
+
"discard",
|
|
60
|
+
"distinct",
|
|
61
|
+
"do",
|
|
62
|
+
"drop",
|
|
63
|
+
"each",
|
|
64
|
+
"else",
|
|
65
|
+
"empty",
|
|
66
|
+
"empty_action",
|
|
67
|
+
"encrypted",
|
|
68
|
+
"end",
|
|
69
|
+
"erase",
|
|
70
|
+
"error",
|
|
71
|
+
"escape",
|
|
72
|
+
"evaluate",
|
|
73
|
+
"except",
|
|
74
|
+
"exclude",
|
|
75
|
+
"exclusion",
|
|
76
|
+
"exclusive",
|
|
77
|
+
"exists",
|
|
78
|
+
"explain",
|
|
79
|
+
"export",
|
|
80
|
+
"external",
|
|
81
|
+
"fail",
|
|
82
|
+
"family",
|
|
83
|
+
"filter",
|
|
84
|
+
"flatten",
|
|
85
|
+
"following",
|
|
86
|
+
"for",
|
|
87
|
+
"foreign",
|
|
88
|
+
"from",
|
|
89
|
+
"full",
|
|
90
|
+
"function",
|
|
91
|
+
"glob",
|
|
92
|
+
"group",
|
|
93
|
+
"grouping",
|
|
94
|
+
"groups",
|
|
95
|
+
"hash",
|
|
96
|
+
"having",
|
|
97
|
+
"hop",
|
|
98
|
+
"if",
|
|
99
|
+
"ignore",
|
|
100
|
+
"ilike",
|
|
101
|
+
"immediate",
|
|
102
|
+
"import",
|
|
103
|
+
"in",
|
|
104
|
+
"index",
|
|
105
|
+
"indexed",
|
|
106
|
+
"inherits",
|
|
107
|
+
"initially",
|
|
108
|
+
"inner",
|
|
109
|
+
"insert",
|
|
110
|
+
"instead",
|
|
111
|
+
"intersect",
|
|
112
|
+
"into",
|
|
113
|
+
"is",
|
|
114
|
+
"isnull",
|
|
115
|
+
"join",
|
|
116
|
+
"json_exists",
|
|
117
|
+
"json_query",
|
|
118
|
+
"json_value",
|
|
119
|
+
"key",
|
|
120
|
+
"left",
|
|
121
|
+
"like",
|
|
122
|
+
"limit",
|
|
123
|
+
"local",
|
|
124
|
+
"match",
|
|
125
|
+
"natural",
|
|
126
|
+
"no",
|
|
127
|
+
"not",
|
|
128
|
+
"notnull",
|
|
129
|
+
"null",
|
|
130
|
+
"nulls",
|
|
131
|
+
"object",
|
|
132
|
+
"of",
|
|
133
|
+
"offset",
|
|
134
|
+
"on",
|
|
135
|
+
"only",
|
|
136
|
+
"or",
|
|
137
|
+
"order",
|
|
138
|
+
"others",
|
|
139
|
+
"outer",
|
|
140
|
+
"over",
|
|
141
|
+
"partition",
|
|
142
|
+
"passing",
|
|
143
|
+
"password",
|
|
144
|
+
"plan",
|
|
145
|
+
"pragma",
|
|
146
|
+
"preceding",
|
|
147
|
+
"presort",
|
|
148
|
+
"primary",
|
|
149
|
+
"process",
|
|
150
|
+
"raise",
|
|
151
|
+
"range",
|
|
152
|
+
"reduce",
|
|
153
|
+
"references",
|
|
154
|
+
"regexp",
|
|
155
|
+
"reindex",
|
|
156
|
+
"release",
|
|
157
|
+
"rename",
|
|
158
|
+
"replace",
|
|
159
|
+
"replication",
|
|
160
|
+
"reset",
|
|
161
|
+
"respect",
|
|
162
|
+
"restrict",
|
|
163
|
+
"result",
|
|
164
|
+
"return",
|
|
165
|
+
"returning",
|
|
166
|
+
"revert",
|
|
167
|
+
"right",
|
|
168
|
+
"rlike",
|
|
169
|
+
"rollback",
|
|
170
|
+
"rollup",
|
|
171
|
+
"row",
|
|
172
|
+
"rows",
|
|
173
|
+
"sample",
|
|
174
|
+
"savepoint",
|
|
175
|
+
"schema",
|
|
176
|
+
"select",
|
|
177
|
+
"semi",
|
|
178
|
+
"sets",
|
|
179
|
+
"source",
|
|
180
|
+
"stream",
|
|
181
|
+
"subquery",
|
|
182
|
+
"symbols",
|
|
183
|
+
"sync",
|
|
184
|
+
"system",
|
|
185
|
+
"table",
|
|
186
|
+
"tablesample",
|
|
187
|
+
"tablestore",
|
|
188
|
+
"temp",
|
|
189
|
+
"temporary",
|
|
190
|
+
"then",
|
|
191
|
+
"ties",
|
|
192
|
+
"to",
|
|
193
|
+
"topic",
|
|
194
|
+
"transaction",
|
|
195
|
+
"trigger",
|
|
196
|
+
"type",
|
|
197
|
+
"unbounded",
|
|
198
|
+
"unconditional",
|
|
199
|
+
"union",
|
|
200
|
+
"unique",
|
|
201
|
+
"unknown",
|
|
202
|
+
"update",
|
|
203
|
+
"upsert",
|
|
204
|
+
"use",
|
|
205
|
+
"user",
|
|
206
|
+
"using",
|
|
207
|
+
"vacuum",
|
|
208
|
+
"values",
|
|
209
|
+
"view",
|
|
210
|
+
"virtual",
|
|
211
|
+
"when",
|
|
212
|
+
"where",
|
|
213
|
+
"window",
|
|
214
|
+
"with",
|
|
215
|
+
"without",
|
|
216
|
+
"wrapper",
|
|
217
|
+
"xor",
|
|
218
|
+
}
|