Skip to content

Commit bda07f4

Browse files
authored
Merge pull request #44 Add ydb_table_path_prefix parameter from LuckySting/feat/table-prefix-setting
2 parents f729afa + 8807edd commit bda07f4

File tree

4 files changed

+98
-23
lines changed

4 files changed

+98
-23
lines changed

test/test_core.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,3 +995,60 @@ def test_index_with_join_usage(self, connection: sa.Connection, metadata: sa.Met
995995

996996
cursor = connection.execute(select_stmt)
997997
assert cursor.one() == ("Sarah Connor", "wanted")
998+
999+
1000+
class TestTablePathPrefix(TablesTest):
1001+
__backend__ = True
1002+
1003+
@classmethod
1004+
def define_tables(cls, metadata: sa.MetaData):
1005+
Table("some_dir/nested_dir/table", metadata, sa.Column("id", sa.Integer, primary_key=True))
1006+
Table("table", metadata, sa.Column("id", sa.Integer, primary_key=True))
1007+
1008+
@classmethod
1009+
def insert_data(cls, connection: sa.Connection):
1010+
table = cls.tables["some_dir/nested_dir/table"]
1011+
root_table = cls.tables["table"]
1012+
1013+
connection.execute(sa.insert(table).values({"id": 1}))
1014+
connection.execute(sa.insert(root_table).values({"id": 2}))
1015+
1016+
def test_select(self):
1017+
engine = sa.create_engine(config.db_url, connect_args={"ydb_table_path_prefix": "/local/some_dir/nested_dir"})
1018+
rel_table = Table("table", sa.MetaData(), sa.Column("id", sa.Integer, primary_key=True))
1019+
abs_table = Table("/local/table", sa.MetaData(), sa.Column("id", sa.Integer, primary_key=True))
1020+
1021+
with engine.connect() as conn:
1022+
result1 = conn.execute(sa.select(rel_table)).scalar()
1023+
result2 = conn.execute(sa.select(abs_table)).scalar()
1024+
1025+
assert result1 == 1
1026+
assert result2 == 2
1027+
1028+
def test_two_engines(self):
1029+
create_engine = sa.create_engine(
1030+
config.db_url, connect_args={"ydb_table_path_prefix": "/local/two/engines/test"}
1031+
)
1032+
select_engine = sa.create_engine(config.db_url, connect_args={"ydb_table_path_prefix": "/local/two"})
1033+
table_to_create = Table("table", sa.MetaData(), sa.Column("id", sa.Integer, primary_key=True))
1034+
table_to_select = Table("engines/test/table", sa.MetaData(), sa.Column("id", sa.Integer, primary_key=True))
1035+
1036+
table_to_create.create(create_engine)
1037+
try:
1038+
with create_engine.begin() as conn:
1039+
conn.execute(sa.insert(table_to_create).values({"id": 42}))
1040+
1041+
with select_engine.begin() as conn:
1042+
result = conn.execute(sa.select(table_to_select)).scalar()
1043+
finally:
1044+
table_to_create.drop(create_engine)
1045+
1046+
assert result == 42
1047+
1048+
def test_reflection(self):
1049+
reflection_engine = sa.create_engine(config.db_url, connect_args={"ydb_table_path_prefix": "/local/some_dir"})
1050+
metadata = sa.MetaData()
1051+
1052+
metadata.reflect(reflection_engine)
1053+
1054+
assert "nested_dir/table" in metadata.tables

test/test_orm.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from types import MethodType
2+
13
import pytest
24
import sqlalchemy as sa
3-
from types import MethodType
45
from sqlalchemy import Column, Integer, Unicode
56
from sqlalchemy.orm import declarative_base, sessionmaker
67
from sqlalchemy.testing.fixtures import TablesTest, config

ydb_sqlalchemy/dbapi/connection.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(
3838
self.database = database
3939
self.conn_kwargs = conn_kwargs
4040
self.credentials = self.conn_kwargs.pop("credentials", None)
41+
self.table_path_prefix = self.conn_kwargs.pop("ydb_table_path_prefix", "")
4142

4243
if "ydb_session_pool" in self.conn_kwargs: # Use session pool managed manually
4344
self._shared_session_pool = True
@@ -58,20 +59,22 @@ def __init__(
5859
self.tx_context: Optional[ydb.TxContext] = None
5960

6061
def cursor(self):
61-
return self._cursor_class(self.session_pool, self.tx_mode, self.tx_context)
62+
return self._cursor_class(self.session_pool, self.tx_mode, self.tx_context, self.table_path_prefix)
6263

6364
def describe(self, table_path: str) -> ydb.TableDescription:
64-
abs_table_path = posixpath.join(self.database, table_path)
65+
abs_table_path = posixpath.join(self.database, self.table_path_prefix, table_path)
6566
cursor = self.cursor()
6667
return cursor.describe_table(abs_table_path)
6768

6869
def check_exists(self, table_path: str) -> ydb.SchemeEntry:
70+
abs_table_path = posixpath.join(self.database, self.table_path_prefix, table_path)
6971
cursor = self.cursor()
70-
return cursor.check_exists(table_path)
72+
return cursor.check_exists(abs_table_path)
7173

7274
def get_table_names(self) -> List[str]:
75+
abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
7376
cursor = self.cursor()
74-
return cursor.get_table_names()
77+
return [posixpath.relpath(path, abs_dir_path) for path in cursor.get_table_names(abs_dir_path)]
7578

7679
def set_isolation_level(self, isolation_level: str):
7780
class IsolationSettings(NamedTuple):

ydb_sqlalchemy/dbapi/cursor.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import functools
44
import itertools
55
import logging
6+
import posixpath
67
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
78

89
import ydb
@@ -78,6 +79,7 @@ def __init__(
7879
session_pool: Union[ydb.SessionPool, ydb.aio.SessionPool],
7980
tx_mode: ydb.AbstractTransactionModeBuilder,
8081
tx_context: Optional[ydb.BaseTxContext] = None,
82+
table_path_prefix: str = "",
8183
):
8284
self.session_pool = session_pool
8385
self.tx_mode = tx_mode
@@ -86,33 +88,36 @@ def __init__(
8688
self.arraysize = 1
8789
self.rows = None
8890
self._rows_prefetched = None
91+
self.root_directory = table_path_prefix
8992

9093
@_handle_ydb_errors
9194
def describe_table(self, abs_table_path: str) -> ydb.TableDescription:
9295
return self._retry_operation_in_pool(self._describe_table, abs_table_path)
9396

94-
def check_exists(self, table_path: str) -> bool:
97+
def check_exists(self, abs_table_path: str) -> bool:
9598
try:
96-
self._retry_operation_in_pool(self._describe_path, table_path)
99+
self._retry_operation_in_pool(self._describe_path, abs_table_path)
97100
return True
98101
except ydb.SchemeError:
99102
return False
100103

101104
@_handle_ydb_errors
102-
def get_table_names(self) -> List[str]:
103-
directory: ydb.Directory = self._retry_operation_in_pool(self._list_directory)
104-
return [child.name for child in directory.children if child.is_table()]
105+
def get_table_names(self, abs_dir_path: str) -> List[str]:
106+
directory: ydb.Directory = self._retry_operation_in_pool(self._list_directory, abs_dir_path)
107+
result = []
108+
for child in directory.children:
109+
child_abs_path = posixpath.join(abs_dir_path, child.name)
110+
if child.is_table():
111+
result.append(child_abs_path)
112+
elif child.is_directory() and not child.name.startswith("."):
113+
result.extend(self.get_table_names(child_abs_path))
114+
return result
105115

106116
def execute(self, operation: YdbQuery, parameters: Optional[Mapping[str, Any]] = None):
107-
if operation.is_ddl or not operation.parameters_types:
108-
query = operation.yql_text
109-
is_ddl = operation.is_ddl
110-
else:
111-
query = ydb.DataQuery(operation.yql_text, operation.parameters_types)
112-
is_ddl = operation.is_ddl
117+
query = self._get_ydb_query(operation)
113118

114119
logger.info("execute sql: %s, params: %s", query, parameters)
115-
if is_ddl:
120+
if operation.is_ddl:
116121
chunks = self._execute_ddl(query)
117122
else:
118123
chunks = self._execute_dml(query, parameters)
@@ -130,6 +135,15 @@ def execute(self, operation: YdbQuery, parameters: Optional[Mapping[str, Any]] =
130135

131136
self.rows = rows
132137

138+
def _get_ydb_query(self, operation: YdbQuery) -> Union[ydb.DataQuery, str]:
139+
pragma = ""
140+
if self.root_directory:
141+
pragma = f'PRAGMA TablePathPrefix = "{self.root_directory}";\n'
142+
if operation.is_ddl or not operation.parameters_types:
143+
return pragma + operation.yql_text
144+
145+
return ydb.DataQuery(pragma + operation.yql_text, operation.parameters_types)
146+
133147
@_handle_ydb_errors
134148
def _execute_dml(
135149
self, query: Union[ydb.DataQuery, str], parameters: Optional[Mapping[str, Any]] = None
@@ -163,8 +177,8 @@ def _describe_path(session: ydb.Session, table_path: str) -> ydb.SchemeEntry:
163177
return session._driver.scheme_client.describe_path(table_path)
164178

165179
@staticmethod
166-
def _list_directory(session: ydb.Session) -> ydb.Directory:
167-
return session._driver.scheme_client.list_directory(session._driver._driver_config.database)
180+
def _list_directory(session: ydb.Session, abs_dir_path: str) -> ydb.Directory:
181+
return session._driver.scheme_client.list_directory(abs_dir_path)
168182

169183
@staticmethod
170184
def _prepare(session: ydb.Session, query: str) -> ydb.DataQuery:
@@ -264,12 +278,12 @@ async def _describe_table(session: ydb.aio.table.Session, abs_table_path: str) -
264278
return await session.describe_table(abs_table_path)
265279

266280
@staticmethod
267-
async def _describe_path(session: ydb.aio.table.Session, table_path: str) -> ydb.SchemeEntry:
268-
return await session._driver.scheme_client.describe_path(table_path)
281+
async def _describe_path(session: ydb.aio.table.Session, abs_table_path: str) -> ydb.SchemeEntry:
282+
return await session._driver.scheme_client.describe_path(abs_table_path)
269283

270284
@staticmethod
271-
async def _list_directory(session: ydb.aio.table.Session) -> ydb.Directory:
272-
return await session._driver.scheme_client.list_directory(session._driver._driver_config.database)
285+
async def _list_directory(session: ydb.aio.table.Session, abs_dir_path: str) -> ydb.Directory:
286+
return await session._driver.scheme_client.list_directory(abs_dir_path)
273287

274288
@staticmethod
275289
async def _execute_scheme(session: ydb.aio.table.Session, query: str) -> ydb.convert.ResultSets:

0 commit comments

Comments
 (0)