Skip to content

fix(r13): make the value evaluator three-valued (R13 slice 2; P48, P50) - #84

Merged
TimelordUK merged 3 commits into
mainfrom
refactor/r13-slice2-three-valued-value-evaluator
Sep 13, 2026
Merged

TimelordUK merged 3 commits into
mainfrom
refactor/r13-slice2-three-valued-value-evaluator

Conversation

@TimelordUK

Copy link
Copy Markdown
Owner

R13 slice 2, the one slice in the plan that changes answers. ArithmeticEvaluator, which serves SELECT, HAVING and join conditions, now evaluates predicates in three-valued logic, with NULL standing for UNKNOWN. Closes P48 and P50.

What changed

Arm Before Now
Comparisons compare_with_op, no NULL guard: NULL vs value was false, NULL vs NULL was equal predicate-layer compare_trilean: NULL on either side gives NULL
AND / OR / NOT collapsed through to_bool (NULL → false) Trilean truth tables; to_bool deleted
IN / NOT IN two-valued; NULL matched NULL OR's truth table; NOT IN is its negation
BETWEEN two comparisons joined with && AND's truth table
LIKE stringified NULL NULL for a NULL operand
simple CASE x WHEN NULL matched a NULL x never matches

compare_values still reports NULL = NULL as equal. ORDER BY needs that, so the NULL test sits at the predicate layer, as R10 did for WHERE.

Trilean::from_value / to_value are now the single crossing between DataValue and the truth tables.

Acceptance, as slice 1 set it

  • Evaluator matrix: all 32 value-evaluator KNOWN entries went FIXED in this one change, and the WHERE column did not move. The value evaluator went from 4/36 to 36/36, and KNOWN is now empty (the mechanism stays).
  • Parity: moved by exactly the six P48/P50 cases, 168 → 174 AGREE over 204. No other bucket changed.
  • FORMAL examples: no expectation churn, so none had captured a projected false for NULL.
  • Rust, Python and example suites green. The one SMOKE failure (expander_rewriters query 7) is identical on main.

Scoping

Before editing, every consumer of predicate output was checked:

  • the WHERE evaluator already maps NULL to UNKNOWN;
  • HAVING's is_truthy treats NULL as not-true, which is correct;
  • IIF(NULL, …) already took the false branch.

So nothing downstream had to change.

Commits

  1. test(parity): pin simple CASE x WHEN NULL. It isn't a predicate, so the matrix can't see it.
  2. fix(r13): the evaluator change, Trilean conversions and their unit test, matrix KNOWN cleared, corpus expects dropped.
  3. docs(parity): P48 and P50 closed; R13 slice 2 result recorded.

Not in this slice

  • P52: NULL join keys, on both join paths. That's slice 6.
  • Case-insensitivity: not yet threaded into this evaluator. That's slice 3, one construction path.
  • P51: AND in a SELECT item doesn't parse. A parser fix, outside R13.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GpuTexUeuyuAdmDsukMTRW

TimelordUK and others added 3 commits September 13, 2026 16:33
A simple CASE compares with =, so CASE x WHEN NULL never matches. Ours treats
(Null, Null) as equal. Not a predicate, so outside the evaluator matrix; pinned
as a corpus case before the slice 2 change.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01GpuTexUeuyuAdmDsukMTRW
ArithmeticEvaluator - serving SELECT, HAVING and join conditions - now
evaluates predicates in three-valued logic, with NULL standing for UNKNOWN:

- comparisons yield NULL for a NULL operand, via a predicate-layer
  compare_trilean; compare_values keeps NULL = NULL equal for ORDER BY
- AND / OR / NOT use Trilean's truth tables instead of to_bool (removed)
- IN / NOT IN take OR's truth table, so a NULL probe or NULL item is UNKNOWN
- BETWEEN takes AND's truth table; LIKE yields NULL for a NULL operand
- simple CASE x WHEN NULL no longer matches a NULL x

Trilean gains from_value / to_value, the single crossing between the value
evaluator's DataValue and the truth tables.

Acceptance, as set by slice 1: all 32 value-evaluator KNOWN entries in the
evaluator matrix went FIXED in this one change and the WHERE column did not
move, so KNOWN is now empty. Parity moved by exactly the six P48/P50 cases,
168 -> 174 AGREE over 204. FORMAL examples unchanged; Rust, Python and example
suites green.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01GpuTexUeuyuAdmDsukMTRW
@TimelordUK
TimelordUK merged commit b084b22 into main Sep 13, 2026
8 checks passed
IgorOffline pushed a commit to IgorOffline/TimelordUK-sql-cli that referenced this pull request Oct 10, 2026
Adds data/frankenstein_excerpt.txt — Letter 1 and Chapter 5 from Mary
Shelley's Frankenstein (Project Gutenberg TimelordUK#84, public domain, 1818).
366 lines / ~20KB of real prose for deterministic demos.

Adds examples/text_processing_demo.sql walking through READ_TEXT and
GREP over real content, in six sections: basic ingestion, read-time
regex pre-filter, composable GREP with CTE, line-level analytics
(longest lines, length bucket distribution via x - (x % 10)), scoped
queries by line_num, and a "sentiment palette" pattern using scalar
subqueries over READ_TEXT with different regexes per category.

All 9 statements complete in <100ms over the excerpt. Demonstrates that
text ingestion + SQL composition covers non-trivial corpus exploration
without new type machinery.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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