From cdb6a70f7a3261d8ba1a6ca90d0ae52a2d330c16 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Tue, 21 Apr 2026 23:39:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(db):=20ajouter=20count=5Fmarches,=20count?= =?UTF-8?q?=5Funique=5Fmarches=20et=20param=C3=A8tre=20offset=20=C3=A0=20q?= =?UTF-8?q?uery=5Fmarches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/db.py | 19 ++++++++++++++++++- tests/test_db.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/db.py b/src/db.py index 9d96872..9290e48 100644 --- a/src/db.py +++ b/src/db.py @@ -135,10 +135,11 @@ def get_cursor() -> duckdb.DuckDBPyConnection: def query_marches( where_sql: str = "TRUE", - params: tuple = (), + params: tuple | list = (), columns: list[str] | None = None, order_by: str | None = None, limit: int | None = None, + offset: int | None = None, ) -> pl.DataFrame: """Run a parameterized SELECT against the decp table and return Polars. @@ -152,4 +153,20 @@ def query_marches( sql += f" ORDER BY {order_by}" if limit is not None: sql += f" LIMIT {int(limit)}" + if offset is not None: + sql += f" OFFSET {int(offset)}" return get_cursor().execute(sql, list(params)).pl() + + +def count_marches(where_sql: str = "TRUE", params: tuple | list = ()) -> int: + """Retourne le nombre de lignes correspondant à where_sql.""" + sql = f"SELECT COUNT(*) FROM decp WHERE {where_sql}" + result = get_cursor().execute(sql, list(params)).fetchone() + return int(result[0]) if result else 0 + + +def count_unique_marches(where_sql: str = "TRUE", params: tuple | list = ()) -> int: + """Retourne le nombre de uid distincts correspondant à where_sql.""" + sql = f"SELECT COUNT(DISTINCT uid) FROM decp WHERE {where_sql}" + result = get_cursor().execute(sql, list(params)).fetchone() + return int(result[0]) if result else 0 diff --git a/tests/test_db.py b/tests/test_db.py index 2edd1c5..0ca9e36 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -206,6 +206,38 @@ def test_query_marches_returns_polars_frame(built_db, monkeypatch): assert set(frame["uid"].to_list()) == {"1", "2"} +def test_count_marches_returns_total_without_filter(): + from src.db import count_marches + + n = count_marches() + assert isinstance(n, int) + assert n > 0 + + +def test_count_marches_with_filter(): + from src.db import count_marches + + n = count_marches('"uid" = ?', ["__nonexistent__"]) + assert n == 0 + + +def test_count_unique_marches_respects_distinct(): + from src.db import count_unique_marches + + n = count_unique_marches() + assert isinstance(n, int) + assert n > 0 + + +def test_query_marches_with_offset(): + from src.db import query_marches + + page_0 = query_marches(limit=2, offset=0) + page_1 = query_marches(limit=2, offset=2) + if page_0.height == 2 and page_1.height >= 1: + assert set(page_0["uid"].to_list()).isdisjoint(set(page_1["uid"].to_list())) + + def test_concurrent_build_serialized(tmp_path): """Multiple threads calling _ensure_database must serialize via flock.