Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions packages/inputlayer-py/src/inputlayer/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,17 @@ def compile_rule(
else:
head_parts.append(column_to_variable(col))

# Compile filter conditions BEFORE building body atoms, so every column
# the condition references is registered in the env and gets bound to a
# variable in its atom. Compiling them after produced atoms with `_` for
# condition-only columns while the condition referenced an unbound
# variable - which the engine accepts and silently satisfies, deriving
# wrong results (e.g. a tier == "gold" filter matching every row).
cond_parts: list[str] = []
if condition:
cond_parts = compile_bool_expr(condition, env)
cond_parts = [p for p in cond_parts if p]

# Build body atoms
body_atoms: list[str] = []
for rn, cls, alias in body_relations:
Expand All @@ -680,12 +691,6 @@ def compile_rule(
atom_parts.append("_")
body_atoms.append(f"{rn}({', '.join(atom_parts)})")

# Compile filter conditions
cond_parts: list[str] = []
if condition:
cond_parts = compile_bool_expr(condition, env)
cond_parts = [p for p in cond_parts if p]

all_body = body_atoms + cond_parts
prefix = "+" if persistent else ""
head_str = f"{prefix}{head_name}({', '.join(head_parts)})"
Expand Down
19 changes: 19 additions & 0 deletions packages/inputlayer-py/tests/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,25 @@ def test_with_condition(self):
)
assert "+high_earner(Id, Name)" in result
assert "Salary > 100000" in result
# The condition column must be BOUND in the body atom. Emitting
# employee(Id, Name, _, _, _) alongside `Salary > 100000` leaves
# Salary unbound - the engine accepts that and silently satisfies
# the comparison, deriving wrong results.
assert "employee(Id, Name, _, Salary, _)" in result

def test_condition_only_column_is_bound(self):
# Regression: a column referenced ONLY by the where-condition (not
# selected into the head) must still be bound in its atom.
cond = Comparison("=", AstColumn("employee", "department"), Literal("eng"))
result = compile_rule(
"eng_member",
["name"],
{"name": AstColumn("employee", "name")},
[(Employee._resolve_name(), Employee, None)],
condition=cond,
persistent=True,
)
assert result == '+eng_member(Name) <- employee(_, Name, Department, _, _), Department = "eng"'

def test_recursive(self):
# reachable(Src, Dst) <- reachable(Src, Mid), edge(Mid, Dst)
Expand Down
Loading