Skip to content

fix(runtime): SQL-style null and 3VL in residual WHERE#27

Draft
Felipe705x wants to merge 3 commits into
mainfrom
feat/where-null-3vl
Draft

fix(runtime): SQL-style null and 3VL in residual WHERE#27
Felipe705x wants to merge 3 commits into
mainfrom
feat/where-null-3vl

Conversation

@Felipe705x
Copy link
Copy Markdown
Collaborator

@Felipe705x Felipe705x commented May 1, 2026

Summary

Missing keys on bound values read as null; general WHERE uses SQL-style 3VL (AND / OR / NOT; comparisons with null → unknown). Rows are kept only when the condition is definitely true.

Graph properties (AttrLookup) and record fields (FieldAccess) now match: missing key → Success(Value::Null), not Failure, so OR / AND do not short-circuit on a missing nested field (same pattern as missing top-level property).

Bug visibility (before)

Carol has no email; Failure on missing property broke OR:

MATCH (x: User)
WHERE (x.email = 'nobody@example.com') OR (1 = 1)
RETURN x.name

Expected: Alice, Bob, Carol. Before: Carol dropped.

Record layer (same OR pattern, missing nested field on address):

MATCH (x: User)
WHERE (x.address.nonexistent = true) OR (1 = 1)
RETURN x.name

Before FieldAccess fix: Failure on missing record key → whole OR became Failure → no rows. After: unknown true → true for all users with address (see record_test).

Spec alignment (concise)

ISO/IEC 39075:2024: <property reference> unifies graph elements and record expressions; missing field → null for nullable / open cases; WHERE uses 3VL (e.g. unknown OR true = true); IS NULL vs = NULL differ as in SQL. Material / non-nullable (22G12) and <property exists> remain out of scope for this PR.

FPPC: Rule R_a: missing attribute read → ok null (not stuck); for null literal at static meet is sound; nested FieldAccess aligned with AttrLookup matches that intent.

ISO nuance — §5.3.2.4 vs pushdown

Subclause 5.3.2.4 allows reorderings when observable effect matches the General Rules. Treating unknown as false in a filter often matches WHERE (both drop the row) when that decision is the full condition.

It does not automatically justify every split of a boolean expression across pushed (bool-only, e.g. cmp_values: null → false) vs residual (full 3VL) layers: under OR, unknown true must stay true; a rewrite that evaluates only a subexpression as false where the full expression would be true can diverge. So pushdown remains defensible per literal ISO text only when equivalence is proved for that rewrite; the CLAUDE.md follow-up stays the right place to track unsafe splits.

Does this block the PR? No. It sharpens the follow-up: unify or prove observational equivalence case-by-case, not assume §5.3.2.4 covers all null pushdown.

Changes

  • AttrLookup: missing property → Success(Null).
  • FieldAccess: missing record key → Success(Null) (aligns with graph property reads and ISO unified property reference / FPPC R_a).
  • eval_binop: 3VL; As passes null; unary - / NOT preserve null.
  • Aggregates: skip null and Failure; COUNT tests renamed/clarified.
  • Docs: CLAUDE.md, docs/iso-gql-gaps.md; cmp_values comment; Value::Null doc mentions 3VL in WHERE.

Tests

parser_test, runtime_test, store_runtime_test, text2gql_test, null_test, count_test, record_test (green locally).

Follow-up

  • Prove or extend pushdown so compound WHERE (especially under OR) cannot change row sets vs residual 3VL, or document safe subsets per §5.3.2.4.
  • Material types / 22G12, <property exists>: separate workstreams.

- AttrLookup: missing property reads as Success(Null), not Failure
- eval_binop: 3VL for comparisons, AND/OR/NOT, arithmetic; As passes Null
- Unary -/NOT: Null preserved; tighten dynamic error strings
- Aggregates: skip Success(Null) and Failure (COUNT tests/comments)
- cmp_values doc: clarify bool-only pushdown vs full WHERE 3VL
- Docs: CLAUDE.md, iso-gql-gaps; tests: null_test, count_test
- Typechecker: null literal maps to Star; IsNull expr doc

Co-authored-by: Cursor <cursoragent@cursor.com>
@Felipe705x Felipe705x changed the title fix(runtime): SQL-style null and 3VL in residual WHER fix(runtime): SQL-style null and 3VL in residual WHERE May 1, 2026
Felipe705x and others added 2 commits May 1, 2026 18:35
…okup)

- ISO: unified property reference semantics for records vs graph elements
- FPPC R_a: missing read ok null; Failure must not replace OR/AND 3VL path
- CLAUDE.md + Value docs; record_test regression for nested miss OR (1=1)

Co-authored-by: Cursor <cursoragent@cursor.com>
- Null.field → Success(Null); record arm uses get().unwrap_or
- Fixture: Dave without address; OR regression expects 4 names
- CLAUDE.md: chained access on null base

Co-authored-by: Cursor <cursoragent@cursor.com>
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