# Phase 62 — sqllogictest blast-radius spike

**Date:** 2026-05-05
**Purpose:** Pre-phase scoping for Phase 62 (caret restoration + LRU removal). Inventory `statement error` matchers across `test/sql/*.test` to size the test-update delta needed when error rendering switches from runtime `Invalid Input Error` (synthesised `SELECT error('<msg>')`) back to parse-time `ParserException::SyntaxError` (with `LINE 1: … ^` caret).
**Outcome:** **SMALL.** Sqllogictest's substring-matching semantics + our message-text-stable design mean the existing 96 `statement error` blocks need essentially zero rewrites. Phase 62 test work concentrates in `test/integration/test_caret_position.py` (tighten 3 assertions) and 1–2 new sqllogictest cases for new error paths in `peg_compat.test`.

---

## Method

```bash
# Enumerate statement-error blocks
rg -n "^statement error" test/sql/

# Extract the matcher line (line after `----`) for each
for f in test/sql/*.test; do
  awk 'BEGIN{state=0} /^statement error/{state=1; next} state==1 && /^----/{state=2; next} state==2 && NF>0{print $0; state=0}' "$f"
done | sort -u
```

## Quantitative inventory

| Metric | Value |
|--------|------:|
| Files containing `statement error` | 32 |
| Total `statement error` occurrences | 96 |
| Unique matcher patterns | 57 |
| Matchers prefixed with `Parser Error:` / `Invalid Input Error:` / `Catalog Error:` / `Binder Error:` | **0** |
| Matchers asserting `LINE 1:` / caret position | **0** |
| `test/integration/test_caret_position.py` test functions | 3 (lines 123, 145, 167) |
| `extract_caret_position` helper present but unused in assertions | yes (lines 73–94) |

## Categorisation

### (a) Prefix-bound matchers — **0 of 96**

Sqllogictest matchers in this codebase are **substring-based**. None are anchored to DuckDB's error-class prefix (`Parser Error:`, `Invalid Input Error:`, `Catalog Error:`, etc.). The Phase 62 transition from runtime `Invalid Input Error` → parse-time `Parser Error: <msg>\nLINE 1: ... ^` does **not** break any matcher in this category, because there is no matcher in this category.

This was the headline risk in the architecture-direction read. It does not exist.

### (b) Substring matchers — **96 of 96**

All matchers are short message-text fragments:

| Pattern | Count | Origin |
|---------|------:|--------|
| `does not exist` | 16 | our existence checks |
| `fan trap detected` | 5 | our graph validation |
| `already exists` | 5 | our existence checks |
| `must not contain aggregate function` | 3 | our derived-metric validation |
| `unknown dimension` | 2 | our resolution |
| `TABLES` | 2 | our keyword-body parser |
| `SHOW SEMANTIC VIEWS requires IN SCHEMA` | 2 | our SHOW filtering |
| `private` / `PRIVATE` | 3 | our access-modifier check |
| `not found` | 2 | our resolution |
| `has no PRIMARY KEY` | 2 | our cardinality inference |
| `FROM YAML FILE failed` | 2 | our YAML loading |
| `Expected 'AS' or 'FROM YAML' after view name` | 2 | our DDL parser |
| `does not match any PRIMARY KEY or UNIQUE constraint` | 2 | our cardinality inference |
| `Did you mean` (case-sensitive + lowercase variants) | 3 | our near-miss helper |
| `cycle detected in facts` / `cycle detected` | 3 | our DAG validation |
| `cannot combine facts and metrics` | 2 | our query-mode mutex |
| `ambiguous` | 2 | our role-playing resolver |
| ... 27 other unique single-occurrence patterns | 27 | various |

**Phase 62 does not change message text.** The goal is to restore caret rendering at parse time; the text of `semantic view 'X' does not exist`, `Did you mean ...?`, `fan trap detected ...` and every other validation message stays byte-identical. Substring matchers will continue to match regardless of whether the message arrives wrapped in `Invalid Input Error: <msg>` (current runtime path) or `Parser Error: <msg>\nLINE 1: ... ^\n               ^` (Phase 62 parse-time path).

### (c) Caret-checking matchers — **0 sqllogictest, 3 Python**

No sqllogictest file asserts `LINE 1:` or caret-column position. All caret assertions live in `test/integration/test_caret_position.py`:

- `test_caret_missing_paren` (line 123)
- `test_caret_clause_typo` (line 145)
- `test_caret_near_miss` (line 167)

The `extract_caret_position` helper at `test_caret_position.py:73-94` already exists and parses `LINE 1: ... ^` to a column index. It is **currently unused in the assertions** because Phase 60's runtime-error workaround stripped caret rendering entirely (see `test_caret_position.py:14-21` comment). Phase 62 should restore tight assertions:

```python
caret_col = extract_caret_position(error_text)
assert caret_col is not None, "expected caret rendering after Phase 62"
assert caret_col == EXPECTED_COLUMN  # tighten per-test
```

## Phase 62 test delta

| Item | File | Estimated change |
|------|------|------------------|
| Re-enable caret-position assertions | `test/integration/test_caret_position.py` (3 test functions, lines 123–189) | ~30 LOC: assert `extract_caret_position(...)` non-`None` and equals expected column at the start of the offending token. |
| Remove the "errors arrive as runtime exceptions" prose | same file, comment block at lines 14–21 | ~8 LOC removed |
| Add new sqllogictest case: `disable_peg_parser → CREATE` exercises the new `rc=3` "valid but override disabled" actionable error | `test/sql/peg_compat.test` (insert near line 120) | ~15 LOC: `statement error` block expecting `parser_override disabled` substring + the actionable hint `SET allow_parser_override_extension='FALLBACK'`. |
| Optionally: parse-time error class probe | `test/sql/v081_caret_restoration.test` (new, optional) | If we want a sqllogictest that asserts the `Parser Error:` prefix directly. **Probably skip** — the substring matcher of an existing test already catches the message text; an extra prefix-probe test adds maintenance with no real coverage gain. |
| `src/parse.rs` LRU unit test | `src/parse.rs::tests` (existing test ~lines 2761–2786) | **Remove** — the LRU goes away in Phase 62. |
| `src/parse.rs` new tests for `sv_parse_function_rust` | `src/parse.rs::tests` | New: ~50 LOC covering rc=1 (malformed CREATE), rc=1 (near-miss `CRETAE`), rc=2 (plain `SELECT 1`). |

**Net:** roughly 100 LOC of test changes spanning 3 files. No mass rewrites of existing tests.

## Risks not surfaced by the inventory

1. **Error text drift through DuckDB's wrapper.** Our message text is preserved, but DuckDB's outer formatting around it changes (`Invalid Input Error: msg` → `Parser Error: msg\nLINE 1: ...`). The Python `duckdb` package's `Error` class subtypes also change (`InvalidInputException` → `ParserException`). If any test catches a specific DuckDB exception **type** rather than substring-matching the message, it'll break. Quick check below.

2. **`peg_compat.test` already re-issues `SET allow_parser_override_extension='FALLBACK'` after `CALL disable_peg_parser()`** to work around TECH-DEBT item 21. Phase 62's new `rc=3` actionable error is meant to surface for users who *forget* to re-issue that SET. The new test case must call `disable_peg_parser` and **not** re-issue the FALLBACK set, then expect the actionable error.

3. **`extract_caret_position` returns column index relative to the rewritten SQL, not the user's input.** The current helper subtracts the literal `LINE 1: ` 8-character prefix. If Phase 62's `error_location` is expressed as a byte offset into the rewritten SQL (which is the natural design from the ultraplan), it must be remapped to the original user input before being passed to DuckDB's error machinery, or the caret will land in the wrong place. **Worth flagging in Phase 62 plan.**

## Quick checks not yet run (for the Phase 62 plan to confirm)

- `rg -n "InvalidInputException|invalid_input_error|InvalidInputError" test/integration/` — does any Python integration test catch by exception type? If yes, those need updates.
- `rg -n "ParserException|parser_exception" test/integration/` — same question, the other direction.
- Verify `extract_caret_position` correctly extracts from the byte offset DuckDB renders for a synthetic `Parser Error` with caret.

## Size estimate

**SMALL.** ~100 LOC of test changes across 3 files. The architecture-direction estimate that "statement error text drift could be a handful or 50+" was a worst case; the actual answer is **zero needed rewrites**. The substring-matching semantics of sqllogictest plus our discipline of stable message text mean Phase 62's rendering change is invisible to the existing test suite.

This frees Phase 62 to focus on the structural work (re-introducing `parse_function`, attaching `OverrideContext` to `SemanticViewsParserInfo`) without budgeting test-rewrite time.
