Skip to content

R13 slice 3: one evaluator construction path, case-insensitive mode honoured - #86

Merged
TimelordUK merged 5 commits into
mainfrom
refactor/r13-slice3-one-construction-path
Sep 14, 2026
Merged

TimelordUK merged 5 commits into
mainfrom
refactor/r13-slice3-one-construction-path

Conversation

@TimelordUK

Copy link
Copy Markdown
Owner

Summary

R13 slice 3 (docs/ENGINE_REFACTORING.md § R13), in the usual pattern: no-op commits first, then a pinned change that alters answers.

No-op (parity report byte-identical to main, 174 AGREE / 204)

  • e9e5269 Evaluator registries (function, both aggregate, window) are built once per process in a OnceLock and shared via Arc. Over trades_100000.csv, net of ~3 s load: high-cardinality GROUP BY 3.4 s -> 1.9 s, GROUP BY + HAVING 2.5 s -> 0.8 s.
  • 56e41ff One ArithmeticEvaluator::new. The three constructors differed only by a date-notation field nothing read; it is deleted along with QueryEngine's unread date_notation. The per-row evaluator in estimate_group_cardinality is hoisted.

3b: case-insensitive mode (changes answers only under --case-insensitive)

  • cc37217 Pin: case-insensitive run of the evaluator matrix (10 predicates, expectations from DuckDB with COLLATE NOCASE / ILIKE) plus 5 clause-level cases through QueryEngine::with_case_insensitive(true). 11 + 5 divergences recorded.
  • 9fc3a73 Fix: with_case_insensitive passed in at all 16 construction sites and used in comparisons, IN, BETWEEN and LIKE. Changes exactly the pinned cases; parity unchanged.
  • f595923 Docs.

Still case-sensitive outside WHERE, recorded for later slices: simple CASE x WHEN (slice 4) and method calls like .Contains() (slice 5).

Test plan

  • Parity --check report byte-identical after each commit
  • ./run_all_tests.sh: Rust, Python (572 passed), all FORMAL examples pass (the one SMOKE failure, expander_rewriters query 7, also fails on main)
  • CLI probe: --case-insensitive -q "SELECT status = 'ACTIVE'" now returns true for Active

🤖 Generated with Claude Code

https://claude.ai/code/session_01HX4HwUUyg1k6PDTFQm5Gha

TimelordUK and others added 5 commits September 14, 2026 20:18
…ice 3)

ArithmeticEvaluator construction rebuilt the function registry, both
aggregate registries and the window registry every time. The registries
are immutable after construction, so build them once per process in a
OnceLock and hand each evaluator Arc clones.

No-op: parity output byte-identical to main (174 AGREE / 204). Per-group
construction in GROUP BY was the visible cost: over trades_100000.csv,
net of ~3 s load, a high-cardinality GROUP BY drops 3.4 s -> 1.9 s and
GROUP BY + HAVING 2.5 s -> 0.8 s.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HX4HwUUyg1k6PDTFQm5Gha
ArithmeticEvaluator had three constructors, new, with_date_notation and
with_date_notation_and_registry, and the only thing separating them was a
date notation string stored in a field nothing ever read. Dates parse
through the global notation. The registry variant had no callers.

Collapse to ArithmeticEvaluator::new(table) plus the existing
with_visible_rows / with_table_aliases builders, delete the dead field,
the now-unused date_notation parameter on apply_group_by_expressions and
QueryEngine's own unread date_notation field. Also hoist the evaluator
out of the per-row loop in estimate_group_cardinality, matching the key
loop in group_by_expressions.

No-op: parity output byte-identical to main (174 AGREE / 204); cargo
test green, evaluator matrix included.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HX4HwUUyg1k6PDTFQm5Gha
…ice 3b)

Under the engine's case-insensitive mode the WHERE evaluator folds case,
but the value evaluator has no such mode, so the same predicate answers
differently in SELECT, HAVING, aggregate arguments and CASE WHEN - and in
WHERE too when the predicate sits under a CASE, which WHERE hands to an
inner value evaluator.

Extend the evaluator matrix with a case-insensitive run over the string
column (10 predicates, expectations from DuckDB with NOCASE collation /
ILIKE) and add clause-level cases through
QueryEngine::with_case_insensitive(true). Today's answers are recorded as
KNOWN, so the fix has to flip exactly these.

Test-only; no engine change.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HX4HwUUyg1k6PDTFQm5Gha
…ice 3b)

Hand the engine's case-insensitive mode to every ArithmeticEvaluator
construction site through a new with_case_insensitive builder, and use
it in comparisons, IN, BETWEEN and LIKE. Under --case-insensitive a
predicate in SELECT, HAVING, aggregate arguments or CASE WHEN now
answers as it does in WHERE; so does WHERE itself when the predicate
sits under a CASE, which it evaluates through its inner value evaluator.

Flips exactly what the previous commit pinned: all 11 matrix entries and
all 5 clause-level cases. Parity report byte-identical (the harness runs
case-sensitive); cargo test, Python and FORMAL examples green.

Not covered, recorded in R13: simple CASE x WHEN (own values_equal) and
method calls like .Contains() still compare case-sensitively outside
WHERE.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HX4HwUUyg1k6PDTFQm5Gha
@TimelordUK
TimelordUK merged commit a40f05f into main Sep 14, 2026
8 checks passed
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