Skip to content

Commit aebda7b

Browse files
committed
Guard for unsupported computed-column expressions
1 parent 5696a03 commit aebda7b

2 files changed

Lines changed: 53 additions & 0 deletions

File tree

src/blosc2/ctable.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8566,6 +8566,25 @@ def _normalize_transformer(self, expr, inputs=None) -> dict:
85668566
"jit_backend": obj.kwargs.get("jit_backend"),
85678567
}
85688568
lazy, col_deps = self._normalize_expression_transformer(obj)
8569+
# Guard: verify the expression string round-trips before storing.
8570+
# An empty string means the LazyExpr was not fully constructed, and a
8571+
# malformed string would silently break on reload. Catching both here
8572+
# gives an early, actionable error instead of a confusing failure later.
8573+
expression = lazy.expression
8574+
if not expression:
8575+
raise ValueError(
8576+
"The computed-column expression serializes to an empty string "
8577+
"and cannot be persisted. Make sure the lambda returns a "
8578+
"blosc2 expression built from table columns (e.g. cols['x'] * 2)."
8579+
)
8580+
try:
8581+
_ops = {f"o{i}": self._cols[dep] for i, dep in enumerate(col_deps)}
8582+
blosc2.lazyexpr(expression, _ops)
8583+
except Exception as exc:
8584+
raise ValueError(
8585+
f"The computed-column expression {expression!r} cannot be safely "
8586+
f"persisted and reloaded: {exc}"
8587+
) from exc
85698588
return {"kind": "expression", "lazy": lazy, "col_deps": col_deps}
85708589

85718590
def _dsl_result_dtype(self, kernel, col_deps, dtype):

tests/ctable/test_ctable_computed_cols.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,3 +1047,37 @@ def test_dsl_no_jit_backend_not_in_schema():
10471047
schema = t._schema_dict_with_computed()
10481048
mat = {m["name"]: m for m in schema["materialized_columns"]}
10491049
assert "jit_backend" not in mat["total"]
1050+
1051+
1052+
def test_add_computed_column_empty_expression_raises(monkeypatch):
1053+
"""add_computed_column raises ValueError when the LazyExpr serializes to empty string."""
1054+
t = _make_invoice_table()
1055+
1056+
original_normalize = t._normalize_expression_transformer.__func__
1057+
1058+
def patched(self, expr):
1059+
lazy, col_deps = original_normalize(self, expr)
1060+
monkeypatch.setattr(lazy, "expression", "")
1061+
return lazy, col_deps
1062+
1063+
monkeypatch.setattr(type(t), "_normalize_expression_transformer", patched)
1064+
1065+
with pytest.raises(ValueError, match="empty string"):
1066+
t.add_computed_column("total", lambda cols: cols["price"] * cols["qty"])
1067+
1068+
1069+
def test_add_computed_column_malformed_expression_raises(monkeypatch):
1070+
"""add_computed_column raises ValueError when the expression string cannot be re-parsed."""
1071+
t = _make_invoice_table()
1072+
1073+
original_normalize = t._normalize_expression_transformer.__func__
1074+
1075+
def patched(self, expr):
1076+
lazy, col_deps = original_normalize(self, expr)
1077+
monkeypatch.setattr(lazy, "expression", "this is not valid @@@ numexpr")
1078+
return lazy, col_deps
1079+
1080+
monkeypatch.setattr(type(t), "_normalize_expression_transformer", patched)
1081+
1082+
with pytest.raises(ValueError, match="cannot be safely persisted"):
1083+
t.add_computed_column("total", lambda cols: cols["price"] * cols["qty"])

0 commit comments

Comments
 (0)