fix(query): range sur colonnes non-numériques + blank/notBlank sur numérique/date (#41)
- `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.
This commit is contained in:
+14
-5
@@ -82,14 +82,18 @@ def _condition_to_sql(cond: Condition, schema: pl.Schema) -> tuple[str, list]:
|
|||||||
col_type = schema[col]
|
col_type = schema[col]
|
||||||
quoted = f'"{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()
|
is_numeric = col_type.is_numeric()
|
||||||
col_is_date = col_type == pl.Date
|
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:
|
if is_numeric:
|
||||||
return _numeric_to_sql(cond, col_type, quoted)
|
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]} ?", [
|
return f"{quoted} IS NOT NULL AND {target} {op_map[cond.operator]} ?", [
|
||||||
str(cond.value)
|
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":
|
if cond.operator == "startsWith":
|
||||||
return f"{quoted} ILIKE ?", [f"{cond.value}%"]
|
return f"{quoted} ILIKE ?", [f"{cond.value}%"]
|
||||||
if cond.operator == "endsWith":
|
if cond.operator == "endsWith":
|
||||||
|
|||||||
@@ -85,6 +85,32 @@ def test_blank_and_notblank():
|
|||||||
assert "IS NOT NULL" in sql_nb
|
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():
|
def test_unknown_column_is_true():
|
||||||
assert _run(Condition("colonne_inexistante", "contains", "x")) == ("TRUE", [])
|
assert _run(Condition("colonne_inexistante", "contains", "x")) == ("TRUE", [])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user