chore: handoff 17 — ADR-0026 complex WHERE expressions implemented
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
# Session handoff — 2026-05-18 (17)
|
||||
|
||||
Seventeenth handover. This session was an **implementation
|
||||
run**: it built **ADR-0026 (complex WHERE expressions)** — the
|
||||
keystone the handoff-16 design run set up. Steps 1–4 and 6 of
|
||||
the ADR's build order are done, committed, tested, and clippy-
|
||||
clean. **Step 5 (the §7 diagnostic *flagging*) is deferred to
|
||||
ADR-0027** — a deliberate sequencing call; see §3, which is
|
||||
the one thing in this handoff that may want your input.
|
||||
|
||||
**Headline: complex WHERE expressions work end to end.**
|
||||
`update` / `delete` / `show data` take a full boolean
|
||||
expression — `AND` / `OR` / `NOT`, the six comparisons,
|
||||
`LIKE` / `IN` / `BETWEEN` / `IS [NOT] NULL`, parentheses; it
|
||||
compiles to parameterised SQL; `show data` gained `where` and
|
||||
`limit`. The functional `C5a` requirement is satisfied.
|
||||
|
||||
## State at handoff
|
||||
|
||||
**Branch:** `main`. Working tree clean. Four commits since
|
||||
handoff-16 (`ac41938`), all local — push asynchronously, do
|
||||
not treat as blocking:
|
||||
|
||||
```
|
||||
a50c6cd WHERE expressions: matrix cells + predicate_tail grammar fix (ADR-0026 step 6)
|
||||
f75f71b WHERE expressions: wire into update/delete/show data + SQL gen (ADR-0026 steps 3-4)
|
||||
59e6a54 grammar: WHERE-expression fragment + Expr AST + build_expr (ADR-0026 step 2)
|
||||
f0b2043 walker: add Subgrammar node + recursion-depth cap (ADR-0026 step 1)
|
||||
```
|
||||
|
||||
**Tests:** **1079 passing, 0 failing, 1 ignored** (`cargo
|
||||
test` — up from 1039). The ignored test is the long-standing
|
||||
`` ```ignore `` doc-test in `src/friendly/mod.rs`. The
|
||||
typing-surface matrix is **161 cells** (was 152 — `+9` in the
|
||||
new `tests/typing_surface/where_expression.rs`).
|
||||
|
||||
**Clippy:** clean (`cargo clippy --all-targets -- -D
|
||||
warnings`, nursery group).
|
||||
|
||||
## §1. What shipped — ADR-0026 steps 1–4 + 6
|
||||
|
||||
### Step 1 — `Subgrammar` node + depth cap (`f0b2043`)
|
||||
|
||||
New `Node::Subgrammar(&'static Node)` variant: a named
|
||||
`static` grammar fragment recurses through a reference (a
|
||||
`Seq` / `Choice` embeds children by value and cannot close a
|
||||
cycle). `WalkContext::subgrammar_depth` counts active frames;
|
||||
past `MAX_SUBGRAMMAR_DEPTH` (64) the walk fails friendly
|
||||
(`parse.custom.expression_too_deep`) instead of overflowing
|
||||
the stack. Depth is saved/restored per frame so a
|
||||
rolled-back `Choice` branch leaves no residue.
|
||||
|
||||
### Step 2 — expression grammar + `Expr` AST + `build_expr` (`59e6a54`)
|
||||
|
||||
`src/dsl/grammar/expr.rs` (new): the **stratified**
|
||||
WHERE-expression grammar — `or` / `and` / `not` /
|
||||
`bool_primary` / `predicate` tiers as named `static` `Node`
|
||||
fragments, recursing through `Subgrammar`. New recursive
|
||||
`Expr` / `Predicate` / `Operand` / `CompareOp` AST in
|
||||
`dsl::command`. `build_expr` folds the flat matched-terminal
|
||||
slice into an `Expr`.
|
||||
|
||||
### Steps 3–4 — wiring + SQL generation (`f75f71b`)
|
||||
|
||||
- **Grammar** (`grammar/data.rs`): `update` / `delete`
|
||||
`where` is now `Subgrammar(&expr::OR_EXPR)`; `show data`
|
||||
gained optional `where <expr>` and `limit <n>`.
|
||||
- **AST**: `RowFilter::Where{column,value}` →
|
||||
`RowFilter::Where(Expr)`; `ShowData` gained
|
||||
`filter: Option<Expr>` + `limit: Option<u64>`. A
|
||||
`RowFilter::eq` convenience constructor keeps
|
||||
simple-equality call sites readable.
|
||||
- **SQL** (`db.rs`): `compile_expr` lowers `Expr` to a
|
||||
parameterised WHERE (every literal a `?` placeholder, `<>`
|
||||
for inequality). `show data … limit n` emits `LIMIT ?`
|
||||
with an implicit primary-key `ORDER BY`.
|
||||
- **§8 hints**: the expression's right-hand operands resolve
|
||||
through a schema-aware `DynamicSubgrammar`
|
||||
(`where_rhs_operand`) so the hint panel narrows to the
|
||||
compared column's type — but the operand grammar carries
|
||||
**no validators**, so a type-mismatched literal parses
|
||||
(§7 permissive).
|
||||
- 11 db integration tests cover AND/OR/NOT, BETWEEN, IN,
|
||||
LIKE, filtered `show data`, limit ordering.
|
||||
|
||||
### Step 6 — matrix cells + a grammar fix (`a50c6cd`)
|
||||
|
||||
`tests/typing_surface/where_expression.rs` (9 cells). Writing
|
||||
them caught a real bug: `predicate_tail`'s `[NOT] negatable`
|
||||
branch started with `Optional(not)`, and an `Optional`-first
|
||||
`Seq` always "commits" — so the walker's `Choice` returned
|
||||
that branch's `Incomplete` early and **discarded the sibling
|
||||
branches' expected sets**, dropping `is` and the comparison
|
||||
operators from completion after a column. Fixed by splitting
|
||||
into explicit `NOT negatable` + bare `negatable` branches;
|
||||
the matched terminal sequence is unchanged so `build_expr`
|
||||
was untouched.
|
||||
|
||||
## §2. The §3 builder realization (settled with the user)
|
||||
|
||||
ADR-0026 §3 admitted two realizations of "the fragment-builder
|
||||
the walker invokes". The project owner chose **option 1
|
||||
("reconstruct in builder")** before implementation:
|
||||
|
||||
- The stratified grammar is walked normally; terminals flow
|
||||
into the flat `MatchedPath` unchanged (driving highlight /
|
||||
completion / the expected-set). `build_expr` then folds
|
||||
that flat slice into the `Expr` — a deterministic recursive
|
||||
descent, run only at submit-time dispatch.
|
||||
- Two honest deviations from §3's wording, recorded in
|
||||
ADR-0026's new **"As-built notes"** section: there is **no
|
||||
`MatchedKind::Expr` variant** (`MatchedPath` stays purely
|
||||
terminals; the `Expr` is built in the command
|
||||
`ast_builder`s), and there **is** a second structural pass
|
||||
(scoped to dispatch — "no second parse" read as "no
|
||||
separate parser framework").
|
||||
|
||||
Do not re-litigate; build to the as-built notes.
|
||||
|
||||
## §3. ⚠️ Step 5 is deferred — this is the one open call
|
||||
|
||||
ADR-0026 step 5 is "schema-aware type-mismatch and `= NULL`
|
||||
flagging in the walker". It splits in two:
|
||||
|
||||
- **The §7 *behaviour* relaxation — DONE.** A type-mismatched
|
||||
WHERE literal (`where Age = 3.14`, `Age` an int) used to be
|
||||
*rejected* at bind time. It no longer is: `bind_where_literal`
|
||||
binds it by its syntactic shape and the command runs. Test:
|
||||
`walker::tests::phase_d_delete_where_permits_decimal_at_int_column`.
|
||||
- **The §7 *diagnostic flagging* — DEFERRED to ADR-0027.**
|
||||
Surfacing a type-mismatched comparison or `= NULL` as an
|
||||
*error-class highlight + hint* is the seam with ADR-0027,
|
||||
which designs the **walker diagnostics-severity model** the
|
||||
flagging belongs in. ADR-0027 itself says its WARNING
|
||||
severity "has **no triggers** until ADR-0026 is
|
||||
implemented" — i.e. ADR-0026's type-mismatch / `= NULL`
|
||||
conditions *are* those triggers.
|
||||
|
||||
**Why deferred, not done badly now:** building the flagging
|
||||
as a standalone ad-hoc mechanism (a bespoke `HighlightClass::
|
||||
Error` pass + a bespoke hint) before ADR-0027's severity
|
||||
model exists would almost certainly be reworked when that
|
||||
model lands. The clean sequencing is: implement ADR-0027's
|
||||
diagnostics-severity model, and make ADR-0026 §7's
|
||||
type-mismatch + `= NULL` detection the first triggers feeding
|
||||
it.
|
||||
|
||||
**This is a sequencing judgement made while you were away.**
|
||||
If you'd rather have a quick standalone §7 flag now, say so;
|
||||
otherwise the recommendation is to fold it into ADR-0027.
|
||||
It's recorded in ADR-0026's "As-built notes", `requirements.md`
|
||||
`C5a`, and `CLAUDE.md`'s deferred list.
|
||||
|
||||
## §4. What's next
|
||||
|
||||
1. **ADR-0027 — input-field validity indicator.** Now
|
||||
genuinely unblocked: ADR-0026 supplies the WARNING-severity
|
||||
triggers (type mismatch, `= NULL`). Implementing ADR-0027
|
||||
should also land ADR-0026 step 5's flagging as its first
|
||||
triggers (see §3).
|
||||
2. **ADR-0028 — query plans (`explain`).** Also unblocked —
|
||||
`show data … where` (now real) is the filtered query whose
|
||||
plan flips between a full scan and an index search.
|
||||
3. Either order after ADR-0027, per handoff-16.
|
||||
|
||||
Other open clusters are unchanged from handoff-16 §3
|
||||
(snapshot/undo `U1`/`U2`; constraints `C3` — `CHECK` can now
|
||||
reuse ADR-0026's expression grammar; `C4` m:n; `C3a`; `C1`
|
||||
table rename; `H1`; `SD1`; `TT5` CI; `V4`; `I1`; `TU1`).
|
||||
Prioritisation is a user product decision — ask.
|
||||
|
||||
## §5. Architectural delta (vs. handoff-16)
|
||||
|
||||
### Grammar / walker
|
||||
|
||||
- `Node::Subgrammar(&'static Node)` — new variant; static
|
||||
counterpart of `DynamicSubgrammar`.
|
||||
- `WalkContext::subgrammar_depth: usize`;
|
||||
`walker::driver::MAX_SUBGRAMMAR_DEPTH = 64`.
|
||||
- `CompletionProbe` gained `pending_hint_mode` — completion
|
||||
now suppresses keyword candidates at a grammar-declared
|
||||
`ProseOnly` slot (the WHERE operand, which also accepts a
|
||||
column ident, would otherwise surface the misleading
|
||||
`null`/`true`/`false` trio).
|
||||
- `src/dsl/grammar/expr.rs` — the whole expression fragment +
|
||||
`build_expr`. Entry point: `pub static expr::OR_EXPR`.
|
||||
|
||||
### Command AST
|
||||
|
||||
- `Expr` / `Predicate` / `Operand` / `CompareOp` — new,
|
||||
recursive; in `dsl::command`, re-exported from `dsl`.
|
||||
- `RowFilter::Where(Expr)` (was `Where{column,value}`);
|
||||
`RowFilter::eq(col, val)` convenience constructor.
|
||||
- `Command::ShowData` gained `filter: Option<Expr>`,
|
||||
`limit: Option<u64>`.
|
||||
|
||||
### db
|
||||
|
||||
- `Request::QueryData` / `Database::query_data` gained
|
||||
`filter` + `limit`; signature is now
|
||||
`query_data(table, filter, limit, source)`.
|
||||
- `compile_expr` / `compile_predicate` / `compile_operand` /
|
||||
`bind_where_literal` / `syntactic_bound` — `Expr` → SQL.
|
||||
- `do_query_data` builds `WHERE` / `ORDER BY` / `LIMIT`.
|
||||
|
||||
### Catalog
|
||||
|
||||
- New key `parse.custom.expression_too_deep` (depth cap).
|
||||
|
||||
### A latent bug fixed in passing
|
||||
|
||||
`completion::invalid_ident_at_cursor` treated a digit-led
|
||||
token (`1`) as an identifier — now that the WHERE operand
|
||||
slot accepts a column ref *and* a literal, it mis-flagged
|
||||
`where id=1`'s `1` as an unknown column. It now skips
|
||||
digit-led tokens. Regression test:
|
||||
`completion::tests::numeric_literal_in_where_is_not_flagged_as_invalid_column`.
|
||||
|
||||
## §6. How to take over
|
||||
|
||||
1. **Read this file, then handoff-16** for the design-run
|
||||
context that set ADR-0026 up.
|
||||
2. **Read `CLAUDE.md`** — working-style rules.
|
||||
3. **Read `docs/adr/0026-complex-where-expressions.md`** —
|
||||
especially the new **"As-built notes"** section (§3
|
||||
builder realization, the deviations, the step-5 deferral).
|
||||
4. **Read `docs/adr/0027-input-validity-indicator.md`** — the
|
||||
recommended next ADR; §3 of this handoff explains how
|
||||
ADR-0026 step 5 folds into it.
|
||||
5. **Run `cargo test`** — 1079 passing, 0 failing, 1 ignored.
|
||||
6. **Run `cargo clippy --all-targets -- -D warnings`** — clean.
|
||||
7. **Decide the §3 question** (standalone §7 flag now vs.
|
||||
fold into ADR-0027), then start ADR-0027.
|
||||
|
||||
### Note on the typing-surface matrix
|
||||
|
||||
`tests/typing_surface/` is now **161 cells**. After any
|
||||
grammar/walker change: a failing cell with *correct* new
|
||||
behaviour → update its snapshot
|
||||
(`INSTA_UPDATE=always cargo test --test typing_surface_matrix
|
||||
<family>`); a failing cell with *wrong* behaviour → the cell
|
||||
earned its keep. This session: 5 `delete_with_where` /
|
||||
`update_with_where` snapshots were correctly updated (the
|
||||
WHERE operand surface widened), and the new
|
||||
`where_expression` family added 9 cells.
|
||||
Reference in New Issue
Block a user