From 979874760de37b26a3c99407dfc34a177b98eb02 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Fri, 10 Jul 2026 07:49:43 +0200 Subject: [PATCH] =?UTF-8?q?fix(query):=20range=20sur=20colonnes=20non-num?= =?UTF-8?q?=C3=A9riques=20+=20blank/notBlank=20sur=20num=C3=A9rique/date?= =?UTF-8?q?=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `range` était ignoré silencieusement (TRUE, pas de filtre) sur les colonnes texte/date car seul `_numeric_to_sql` le gérait. Ajout du cas `range` dans la branche texte/date (BETWEEN, CAST VARCHAR pour les dates, comme les autres opérateurs de comparaison). - `blank`/`notBlank` comparaient toujours à `''`, ce qui fait planter DuckDB (Conversion Error) sur les colonnes numériques/date. Le check est déplacé après le calcul de is_numeric/col_is_date : ces types utilisent IS [NOT] NULL sans comparaison à chaîne vide. --- src/utils/query_ast.py | 19 ++++++++++++++----- tests/test_query_ast.py | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/utils/query_ast.py b/src/utils/query_ast.py index 9affe48..802ba14 100644 --- a/src/utils/query_ast.py +++ b/src/utils/query_ast.py @@ -82,14 +82,18 @@ def _condition_to_sql(cond: Condition, schema: pl.Schema) -> tuple[str, list]: col_type = schema[col] quoted = f'"{col}"' - if cond.operator == "blank": - return f"({quoted} IS NULL OR {quoted} = '')", [] - if cond.operator == "notBlank": - return f"({quoted} IS NOT NULL AND {quoted} <> '')", [] - is_numeric = col_type.is_numeric() col_is_date = col_type == pl.Date + if cond.operator == "blank": + if is_numeric or col_is_date: + return f"{quoted} IS NULL", [] + return f"({quoted} IS NULL OR {quoted} = '')", [] + if cond.operator == "notBlank": + if is_numeric or col_is_date: + return f"{quoted} IS NOT NULL", [] + return f"({quoted} IS NOT NULL AND {quoted} <> '')", [] + if is_numeric: return _numeric_to_sql(cond, col_type, quoted) @@ -106,6 +110,11 @@ def _condition_to_sql(cond: Condition, schema: pl.Schema) -> tuple[str, list]: return f"{quoted} IS NOT NULL AND {target} {op_map[cond.operator]} ?", [ str(cond.value) ] + if cond.operator == "range": + return f"{quoted} IS NOT NULL AND {target} BETWEEN ? AND ?", [ + str(cond.value), + str(cond.value2), + ] if cond.operator == "startsWith": return f"{quoted} ILIKE ?", [f"{cond.value}%"] if cond.operator == "endsWith": diff --git a/tests/test_query_ast.py b/tests/test_query_ast.py index 8d58ffb..7fd0de7 100644 --- a/tests/test_query_ast.py +++ b/tests/test_query_ast.py @@ -85,6 +85,32 @@ def test_blank_and_notblank(): assert "IS NOT NULL" in sql_nb +def test_date_range_uses_between(): + sql, params = _run( + Condition("dateNotification", "range", "2022-01-01", "2022-12-31") + ) + assert "BETWEEN" in sql + assert params == ["2022-01-01", "2022-12-31"] + + +def test_text_range_uses_between(): + sql, params = _run(Condition("acheteur_nom", "range", "a", "m")) + assert "BETWEEN" in sql + assert params == ["a", "m"] + + +def test_blank_on_numeric_column_no_empty_string(): + sql, params = _run(Condition("montant", "blank")) + assert sql == '"montant" IS NULL' + assert params == [] + + +def test_notblank_on_date_column_no_empty_string(): + sql, params = _run(Condition("dateNotification", "notBlank")) + assert sql == '"dateNotification" IS NOT NULL' + assert params == [] + + def test_unknown_column_is_true(): assert _run(Condition("colonne_inexistante", "contains", "x")) == ("TRUE", [])