Skip to content

Commit 091ac3f

Browse files
authored
Merge pull request #24 from ydb-platform/bump_sdk_constraint
Get column table names
2 parents 36859ea + 208b355 commit 091ac3f

File tree

8 files changed

+494
-781
lines changed

8 files changed

+494
-781
lines changed

.github/docker/docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: "3.3"
2+
services:
3+
ydb:
4+
image: ydbplatform/local-ydb:trunk
5+
restart: always
6+
ports:
7+
- "2136:2136"
8+
hostname: localhost
9+
environment:
10+
- YDB_USE_IN_MEMORY_PDISKS=true
11+
- YDB_ENABLE_COLUMN_TABLES=true

.github/workflows/tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ jobs:
4242
run: |
4343
poetry install
4444
45+
- name: Run docker compose
46+
uses: hoverkraft-tech/[email protected]
47+
with:
48+
compose-file: "./.github/docker/docker-compose.yml"
49+
up-flags: "--wait"
50+
4551
- name: Run tests
4652
run: |
4753
poetry run pytest tests

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* Bump ydb depencency to 3.21.6
2+
13
## 0.1.12 ##
24
* Ability to get view names
35

poetry.lock

Lines changed: 356 additions & 665 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ repository = "https://github.com/ydb-platform/ydb-python-dbapi/"
88

99
[tool.poetry.dependencies]
1010
python = "^3.8"
11-
ydb = "^3.18.16"
11+
ydb = "^3.21.6"
1212

1313
[tool.poetry.group.dev.dependencies]
1414
pre-commit = "^3.5.0"
1515
ruff = "^0.6.9"
1616
mypy = "^1.11.2"
1717
poethepoet = "0.28.0"
1818
types-protobuf = "^5.28.0.20240924"
19-
testcontainers = "^3.7.1"
2019
pytest = "^8.3.3"
2120
pytest-asyncio = "^0.24.0"
2221
sqlalchemy = "^2.0.36"

tests/conftest.py

Lines changed: 22 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3,118 +3,22 @@
33
from asyncio import AbstractEventLoop
44
from collections.abc import AsyncGenerator
55
from collections.abc import Generator
6-
from concurrent.futures import TimeoutError
7-
from typing import Any
8-
from typing import Callable
96

107
import pytest
118
import ydb
12-
from testcontainers.core.generic import DbContainer
13-
from testcontainers.core.generic import wait_container_is_ready
14-
from testcontainers.core.utils import setup_logger
15-
from typing_extensions import Self
16-
17-
logger = setup_logger(__name__)
18-
19-
20-
class YDBContainer(DbContainer):
21-
def __init__(
22-
self,
23-
name: str | None = None,
24-
port: str = "2136",
25-
image: str = "ydbplatform/local-ydb:trunk",
26-
**kwargs: Any,
27-
) -> None:
28-
docker_client_kw: dict[str, Any] = kwargs.pop("docker_client_kw", {})
29-
docker_client_kw["timeout"] = docker_client_kw.get("timeout") or 300
30-
super().__init__(
31-
image=image,
32-
hostname="localhost",
33-
docker_client_kw=docker_client_kw,
34-
**kwargs,
35-
)
36-
self.port_to_expose = port
37-
self._name = name
38-
self._database_name = "local"
39-
40-
def start(self) -> Self:
41-
self._maybe_stop_old_container()
42-
super().start()
43-
return self
44-
45-
def get_connection_url(self, driver: str = "ydb") -> str:
46-
host = self.get_container_host_ip()
47-
port = self.get_exposed_port(self.port_to_expose)
48-
return f"yql+{driver}://{host}:{port}/local"
49-
50-
def get_connection_string(self) -> str:
51-
host = self.get_container_host_ip()
52-
port = self.get_exposed_port(self.port_to_expose)
53-
return f"grpc://{host}:{port}/?database=/local"
54-
55-
def get_ydb_database_name(self) -> str:
56-
return self._database_name
57-
58-
def get_ydb_host(self) -> str:
59-
return self.get_container_host_ip()
60-
61-
def get_ydb_port(self) -> str:
62-
return self.get_exposed_port(self.port_to_expose)
63-
64-
@wait_container_is_ready(ydb.ConnectionError, TimeoutError)
65-
def _connect(self) -> None:
66-
with ydb.Driver(
67-
connection_string=self.get_connection_string()
68-
) as driver:
69-
driver.wait(fail_fast=True)
70-
try:
71-
driver.scheme_client.describe_path("/local/.sys_health/test")
72-
except ydb.SchemeError as e:
73-
msg = "Database is not ready"
74-
raise ydb.ConnectionError(msg) from e
75-
76-
def _configure(self) -> None:
77-
self.with_bind_ports(self.port_to_expose, self.port_to_expose)
78-
if self._name:
79-
self.with_name(self._name)
80-
self.with_env("YDB_USE_IN_MEMORY_PDISKS", "true")
81-
self.with_env("YDB_DEFAULT_LOG_LEVEL", "DEBUG")
82-
self.with_env("GRPC_PORT", self.port_to_expose)
83-
self.with_env("GRPC_TLS_PORT", self.port_to_expose)
84-
85-
def _maybe_stop_old_container(self) -> None:
86-
if not self._name:
87-
return
88-
docker_client = self.get_docker_client()
89-
running_container = docker_client.client.api.containers(
90-
filters={"name": self._name}
91-
)
92-
if running_container:
93-
logger.info("Stop existing container")
94-
docker_client.client.api.remove_container(
95-
running_container[0], force=True, v=True
96-
)
97-
98-
99-
@pytest.fixture(scope="session")
100-
def ydb_container(
101-
unused_tcp_port_factory: Callable[[], int],
102-
) -> Generator[YDBContainer, None, None]:
103-
with YDBContainer(port=str(unused_tcp_port_factory())) as ydb_container:
104-
yield ydb_container
1059

10610

107-
@pytest.fixture(scope="session")
108-
def connection_string(ydb_container: YDBContainer) -> str:
109-
return ydb_container.get_connection_string()
11+
@pytest.fixture
12+
def connection_string() -> str:
13+
return "grpc://localhost:2136/?database=/local"
11014

11115

112-
@pytest.fixture(scope="session")
113-
def connection_kwargs(ydb_container: YDBContainer) -> dict:
16+
@pytest.fixture
17+
def connection_kwargs() -> dict:
11418
return {
115-
"host": ydb_container.get_ydb_host(),
116-
"port": ydb_container.get_ydb_port(),
117-
"database": ydb_container.get_ydb_database_name(),
19+
"host": "localhost",
20+
"port": "2136",
21+
"database": "/local",
11822
}
11923

12024

@@ -176,6 +80,13 @@ async def session_pool(
17680

17781
yield session_pool
17882

83+
for name in ["table", "table1", "table2"]:
84+
await session_pool.execute_with_retries(
85+
f"""
86+
DROP TABLE {name};
87+
"""
88+
)
89+
17990

18091
@pytest.fixture
18192
def session_pool_sync(
@@ -207,3 +118,10 @@ def session_pool_sync(
207118
)
208119

209120
yield session_pool
121+
122+
for name in ["table", "table1", "table2"]:
123+
session_pool.execute_with_retries(
124+
f"""
125+
DROP TABLE {name};
126+
"""
127+
)

tests/test_connections.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,14 @@ def _test_error_with_interactive_tx(
236236
with pytest.raises(dbapi.Error):
237237
maybe_await(cur.execute("INSERT INTO test(id, val) VALUES (1,1)"))
238238

239+
maybe_await(
240+
cur.execute_scheme(
241+
"""
242+
DROP TABLE IF EXISTS test;
243+
"""
244+
)
245+
)
246+
239247
maybe_await(cur.close())
240248
maybe_await(connection.rollback())
241249

@@ -272,6 +280,68 @@ def _test_get_view_names(
272280
assert len(res) == 1
273281
assert res[0] == "test_view"
274282

283+
maybe_await(
284+
cur.execute_scheme(
285+
"""
286+
DROP VIEW test_view;
287+
"""
288+
)
289+
)
290+
291+
maybe_await(cur.close())
292+
293+
def _test_get_table_names(
294+
self,
295+
connection: dbapi.Connection,
296+
) -> None:
297+
cur = connection.cursor()
298+
299+
row_table_name = "test_table_names_row"
300+
column_table_name = "test_table_names_column"
301+
302+
maybe_await(
303+
cur.execute_scheme(
304+
f"""
305+
DROP TABLE if exists {row_table_name};
306+
DROP TABLE if exists {column_table_name};
307+
"""
308+
)
309+
)
310+
311+
res = maybe_await(connection.get_table_names())
312+
313+
assert len(res) == 0
314+
315+
maybe_await(
316+
cur.execute_scheme(
317+
f"""
318+
CREATE TABLE {row_table_name} (
319+
id Utf8 NOT NULL,
320+
PRIMARY KEY(id)
321+
);
322+
CREATE TABLE {column_table_name} (
323+
id Utf8 NOT NULL,
324+
PRIMARY KEY(id)
325+
) WITH (STORE = COLUMN);
326+
"""
327+
)
328+
)
329+
330+
res = maybe_await(connection.get_table_names())
331+
332+
assert len(res) == 2
333+
assert row_table_name in res
334+
assert column_table_name in res
335+
336+
maybe_await(
337+
cur.execute_scheme(
338+
f"""
339+
DROP TABLE {row_table_name};
340+
DROP TABLE {column_table_name};
341+
"""
342+
)
343+
)
344+
275345
maybe_await(cur.close())
276346

277347

@@ -329,6 +399,11 @@ def test_get_view_names(
329399
) -> None:
330400
self._test_get_view_names(connection)
331401

402+
def test_get_table_names(
403+
self, connection: dbapi.Connection
404+
) -> None:
405+
self._test_get_table_names(connection)
406+
332407

333408
class TestAsyncConnection(BaseDBApiTestSuit):
334409
@pytest_asyncio.fixture
@@ -404,3 +479,9 @@ async def test_get_view_names(
404479
self, connection: dbapi.AsyncConnection
405480
) -> None:
406481
await greenlet_spawn(self._test_get_view_names, connection)
482+
483+
@pytest.mark.asyncio
484+
async def test_get_table_names(
485+
self, connection: dbapi.AsyncConnection
486+
) -> None:
487+
await greenlet_spawn(self._test_get_table_names, connection)

ydb_dbapi/connections.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,18 @@ def check_exists(self, table_path: str) -> bool:
277277
@handle_ydb_errors
278278
def get_table_names(self) -> list[str]:
279279
abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
280-
names = self._get_entity_names(abs_dir_path, ydb.SchemeEntryType.TABLE)
280+
names = self._get_entity_names(
281+
abs_dir_path,
282+
[ydb.SchemeEntryType.TABLE, ydb.SchemeEntryType.COLUMN_TABLE],
283+
)
281284
return [posixpath.relpath(path, abs_dir_path) for path in names]
282285

283286
@handle_ydb_errors
284287
def get_view_names(self) -> list[str]:
285288
abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
286-
names = self._get_entity_names(abs_dir_path, ydb.SchemeEntryType.VIEW)
289+
names = self._get_entity_names(
290+
abs_dir_path, [ydb.SchemeEntryType.VIEW]
291+
)
287292
return [posixpath.relpath(path, abs_dir_path) for path in names]
288293

289294
def _check_path_exists(self, table_path: str) -> bool:
@@ -302,7 +307,7 @@ def callee() -> None:
302307
return True
303308

304309
def _get_entity_names(
305-
self, abs_dir_path: str, etype: ydb.SchemeEntryType
310+
self, abs_dir_path: str, etypes: list[ydb.SchemeEntryType]
306311
) -> list[str]:
307312
settings = self._get_request_settings()
308313

@@ -316,10 +321,10 @@ def callee() -> ydb.Directory:
316321
result = []
317322
for child in directory.children:
318323
child_abs_path = posixpath.join(abs_dir_path, child.name)
319-
if child.type == etype:
324+
if child.type in etypes:
320325
result.append(child_abs_path)
321326
elif child.is_directory() and not child.name.startswith("."):
322-
result.extend(self._get_entity_names(child_abs_path, etype))
327+
result.extend(self._get_entity_names(child_abs_path, etypes))
323328
return result
324329

325330
@handle_ydb_errors
@@ -462,7 +467,7 @@ async def get_table_names(self) -> list[str]:
462467
abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
463468
names = await self._get_entity_names(
464469
abs_dir_path,
465-
ydb.SchemeEntryType.TABLE,
470+
[ydb.SchemeEntryType.TABLE, ydb.SchemeEntryType.COLUMN_TABLE],
466471
)
467472
return [posixpath.relpath(path, abs_dir_path) for path in names]
468473

@@ -471,7 +476,7 @@ async def get_view_names(self) -> list[str]:
471476
abs_dir_path = posixpath.join(self.database, self.table_path_prefix)
472477
names = await self._get_entity_names(
473478
abs_dir_path,
474-
ydb.SchemeEntryType.VIEW,
479+
[ydb.SchemeEntryType.VIEW],
475480
)
476481
return [posixpath.relpath(path, abs_dir_path) for path in names]
477482

@@ -492,7 +497,7 @@ async def callee() -> None:
492497
return True
493498

494499
async def _get_entity_names(
495-
self, abs_dir_path: str, etype: ydb.SchemeEntryType
500+
self, abs_dir_path: str, etypes: list[ydb.SchemeEntryType]
496501
) -> list[str]:
497502
settings = self._get_request_settings()
498503

@@ -506,11 +511,11 @@ async def callee() -> ydb.Directory:
506511
result = []
507512
for child in directory.children:
508513
child_abs_path = posixpath.join(abs_dir_path, child.name)
509-
if child.type == etype:
514+
if child.type in etypes:
510515
result.append(child_abs_path)
511516
elif child.is_directory() and not child.name.startswith("."):
512517
result.extend(
513-
await self._get_entity_names(child_abs_path, etype)
518+
await self._get_entity_names(child_abs_path, etypes)
514519
)
515520
return result
516521

0 commit comments

Comments
 (0)