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.