Skip to content

feat(3vl): SQL three-valued logic for AND/OR/NOT + null cast pass-through#63

Closed
Felipe705x wants to merge 4 commits into
mainfrom
feature/3vl-null-boolean-connectives
Closed

feat(3vl): SQL three-valued logic for AND/OR/NOT + null cast pass-through#63
Felipe705x wants to merge 4 commits into
mainfrom
feature/3vl-null-boolean-connectives

Conversation

@Felipe705x

Copy link
Copy Markdown
Collaborator

What & why

Make null propagation follow SQL three-valued logic (3VL) for the boolean connectives AND / OR / NOT.

Today a null/missing operand of a logical operator is routed through the implicit argument-cast wrapper, which turns a missing attribute (Failure) or a null into a hard failure and drops the whole row. The headline symptom:

MATCH (x:Person) WHERE x.status OR true RETURN x.name

When status is absent this returns no rows (every row dropped) instead of all of them. Correct 3VL: unknown OR true = true.

Changes

Runtime — src/runtime/engine.rs

  • Dedicated AND/OR arms in run_expr, outside the operator wrapper, with real short-circuit (true OR _, false AND _) and the 3VL truth table. Both Value::Null and Failure (missing attribute / unbound var / type error) are read as the 3VL unknown, matching the existing IsNull/Coalesce/aggregate convention.
  • NOT null → null.
  • null AS T → null in the BinOp::As arm — null inhabits every type; this also lets a null operand reach the 3VL logic (the runtime relaxation of the SimpleType-only argument check) without touching the IS/TYPED predicate.
  • eval_binop AND/OR arms updated to the same 3VL table for any direct caller.

Typechecker — src/typing/checker.rs

  • Short-circuit override of the codomain (c̃od) check for AND/OR: result is B unless both operands meet bottom () against B (a /null operand beside a valid Bool still yields Bool — e.g. Integer OR true → B).

Truth tables (now)

true OR null → true · false OR null → null · false AND null → false · true AND null → null · null · null → null · NOT null → null.

Scope / no-regression

Boolean connectives only. Comparison/equality operators (=, <>, <, …) keep their pre-existing behavior in this PR — including the known null = null → true wart. 3VL for comparisons + the cmp_valueseval_binop (pushdown) unification is a follow-up PR. New tests pin the current comparison behavior as a no-regression guard.

Tests — tests/three_valued_logic_test.rs (17)

3VL truth tables, missing-property-as-unknown, WHERE-clause differentiators that fail under the old behavior, null/invalid cast handling, the typechecker short-circuit, and comparison no-regression guards.

Verified the differentiators genuinely catch the bug: reverting just the two source files (test file kept) makes 7 of 17 tests fail — exactly the 3VL differentiators (e.g. WHERE x.status OR true[] old vs ["A","B","C"] fixed) — while the 10 guards stay green.

Checklist

  • cargo fmt --all
  • cargo clippy --workspace --all-targets -- -D clippy::all
  • Full test sweep (cargo test --lib + the integration list in CLAUDE.md) — no regressions
  • Follow-up: comparison-operator 3VL + pushdown unification

🤖 Generated with Claude Code

@Felipe705x Felipe705x force-pushed the feature/3vl-null-boolean-connectives branch 2 times, most recently from afcee05 to d79b426 Compare June 29, 2026 15:32
Felipe705x and others added 4 commits July 4, 2026 19:33
Evaluate the boolean connectives under SQL 3VL with short-circuit. `AND`/`OR`
get dedicated arms in `run_expr`, outside the implicit argument-cast wrapper, so
a `Null` or `Failure` operand (missing attribute, unbound var, type error) is
read as the 3VL unknown rather than collapsing the whole expression:

  true OR null -> true     false AND null -> false
  false OR null -> null    true AND null  -> null
  null OR null -> null     null AND null  -> null
  NOT null -> null

`Failure` is treated as the unknown input, matching the existing
IsNull/Coalesce/aggregate convention, so e.g. `WHERE x.status OR true` returns
all rows when `status` is absent (previously: every row dropped).

The unreachable `eval_binop` And/Or arms are updated to the same table for any
direct caller.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`null AS T` returns null for any target type instead of failing the
membership check. This satisfies `null AS T -> null` and lets a null operand
slip through the implicit argument cast used by the generic operator wrapper,
so it reaches the 3VL logic (the runtime relaxation of the SimpleType-only
`△(bop)` check). The `IS`/`TYPED` predicate is untouched — it calls
`value_is_type` directly, not this arm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Override the codomain (c̃od) check for the short-circuit connectives: `AND`/`OR`
type as `B` unless BOTH operands meet bottom (⊥) against `B`. A ⊥/Null operand
beside a valid Bool still yields Bool (e.g. `Integer OR true : B`); only when
both sides are incompatible with Bool does the expression degrade to ⊥ with a
warning. All other operators keep the default delta-based codomain check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers the OR/AND/NOT truth tables, missing-property-as-unknown, the `null AS T`
pass-through, invalid-cast handling (empty in WHERE, null cell in RETURN), and
the typechecker short-circuit. Includes WHERE-clause differentiators that fail
under the pre-fix behavior (e.g. `WHERE x.status OR true` returned no rows), and
no-regression guards pinning the pre-existing comparison-operator behavior
(handled in a follow-up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Felipe705x

Copy link
Copy Markdown
Collaborator Author

Superseded by #73 — closing.

#73 reaches the same goal at the correct altitude, validated against both the ISO GQL and FPPC oracles:

  • Read-site nulls (FPPC Ra): a missing property/field reads as null, so a missing value (lenient 3VL) is distinct from a genuine type error (empties the path). This is the split feat(3vl): SQL three-valued logic for AND/OR/NOT + null cast pass-through #63 couldn't express — it kept missing reads as Failure and absorbed them at the connective, which also laundered real type errors into booleans (e.g. ('a' AS INTEGER) OR true wrongly kept all rows).
  • Classic 3VL for comparisons/arithmetic/IN (null = null → null), plus the SQL truth tables for AND/OR and NOT null → null.
  • cod override for the short-circuit connectives (kept from feat(3vl): SQL three-valued logic for AND/OR/NOT + null cast pass-through #63; confirmed necessary for the vacuity under-approximation, FPPC Thm 6.5).
  • Type errors empty the path regardless of essential/inessential position — the strict, portable subset of ISO UA004; static B imposes no runtime success obligation (FPPC DGG Thm 6.8).

Known ISO gap (pre-existing, out of scope, shrunk by #73): essential type errors / NOT NULL-site nulls should raise a hard data exception (22G12/22G03) rather than empty — tracked as a future ISO data-exceptions workstream.

@Felipe705x Felipe705x closed this Jul 5, 2026
@Felipe705x Felipe705x deleted the feature/3vl-null-boolean-connectives branch July 5, 2026 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant