---
phase: 27-alias-based-query-expansion
plan: 03
type: execute
wave: 1
depends_on: ["27-02"]
files_modified:
  - src/parse.rs
  - tests/parse_proptest.rs
  - test/sql/phase21_error_reporting.test
  - test/integration/test_caret_position.py
autonomous: true
gap_closure: true
requirements: [EXP-01, EXP-05, CLN-01, CLN-02, CLN-03]

must_haves:
  truths:
    - "The error message for non-AS-body DDL says 'Expected AS keyword after view name' without referencing old syntax or 'no longer supported'"
    - "All Rust unit tests asserting on the error message pass with the updated text"
    - "The proptest asserting on the error message passes with the updated text"
    - "The sqllogictest phase21_error_reporting.test Section 1 passes with the updated error substring"
    - "All 3 Python caret position integration tests pass (test_caret_missing_paren, test_caret_clause_typo, test_caret_near_miss)"
    - "just test-all passes (quality gate)"
  artifacts:
    - path: "src/parse.rs"
      provides: "Simplified error message in validate_create_body"
      contains: "Expected 'AS' keyword after view name"
    - path: "tests/parse_proptest.rs"
      provides: "Updated proptest assertion"
      contains: "Expected 'AS'"
    - path: "test/sql/phase21_error_reporting.test"
      provides: "Updated sqllogictest assertions"
      contains: "Expected 'AS'"
    - path: "test/integration/test_caret_position.py"
      provides: "Rewritten Python caret tests for AS-body error scenarios"
  key_links:
    - from: "src/parse.rs::validate_create_body"
      to: "test/integration/test_caret_position.py"
      via: "error message text and caret position"
      pattern: "Expected 'AS' keyword after view name"
---

<objective>
Close the single verification gap from 27-VERIFICATION.md: simplify the "no longer supported" error message (per user directive that paren-body was never released), update all test assertions to match, and rewrite the two failing Python caret tests to exercise AS-body error scenarios instead of paren-body scenarios.

Purpose: The CLAUDE.md quality gate (`just test-all`) fails because two Python caret integration tests still reference paren-body DDL that was removed in Plan 02. The user has directed that "no longer supported" language is inappropriate since the old syntax was never publicly released. This plan fixes the error message and all dependent tests.
Output: All tests pass, quality gate green.
</objective>

<execution_context>
@/Users/paul/.claude/get-shit-done/workflows/execute-plan.md
@/Users/paul/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/27-alias-based-query-expansion/27-VERIFICATION.md
@.planning/phases/27-alias-based-query-expansion/27-02-SUMMARY.md

<interfaces>
<!-- Key function and error type the executor needs -->

From src/parse.rs (line 436-441, the code to modify):
```rust
// Paren-body syntax (tables := [...]) is no longer supported (CLN-01).
let pos_in_trimmed = plen + (trimmed_no_semi.len() - plen - after_prefix.len()) + name_end;
Err(ParseError {
    message: "Expected 'AS' keyword after view name. The old paren-body syntax (tables := [...]) is no longer supported. Use: CREATE SEMANTIC VIEW name AS TABLES (...) DIMENSIONS (...) METRICS (...)".to_string(),
    position: Some(trim_offset + pos_in_trimmed),
})
```

From src/parse.rs (ParseError struct):
```rust
pub struct ParseError {
    pub message: String,
    pub position: Option<usize>,
}
```

From src/body_parser.rs (AS-body errors that produce positions for caret tests):
```rust
// Unknown clause keyword with suggestion (has position):
"Unknown clause keyword '{word}'; did you mean '{sug_upper}'?"
// position: Some(base_offset + word_start)

// Missing required TABLES clause (no position):
"Missing required clause 'TABLES'."
// position: None

// Expected '(' after clause keyword (has position):
"Expected '(' after clause keyword '{kw_upper}', found '{found}'."
// position: Some(base_offset + i)
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Simplify error message and update Rust + sqllogictest assertions</name>
  <files>src/parse.rs, tests/parse_proptest.rs, test/sql/phase21_error_reporting.test</files>
  <action>
1. In `src/parse.rs` line 436-441, change the error message and comment:
   - OLD comment: `// Paren-body syntax (tables := [...]) is no longer supported (CLN-01).`
   - NEW comment: `// Non-AS-body syntax rejected -- AS keyword required after view name.`
   - OLD message: `"Expected 'AS' keyword after view name. The old paren-body syntax (tables := [...]) is no longer supported. Use: CREATE SEMANTIC VIEW name AS TABLES (...) DIMENSIONS (...) METRICS (...)"`
   - NEW message: `"Expected 'AS' keyword after view name. Use: CREATE SEMANTIC VIEW name AS TABLES (...) DIMENSIONS (...) METRICS (...)"`
   - Keep the position calculation unchanged (it is correct).

2. In `src/parse.rs`, update three unit tests that assert on `"no longer supported"`:
   - `test_validate_and_rewrite_rejects_paren_body` (line ~1048): change `.contains("no longer supported")` to `.contains("Expected 'AS' keyword")` and update the assert message.
   - `test_parse_error_position_paren_body_rejected` (line ~1168): same change.
   - `old_paren_body_is_rejected` (line ~1231): same change.
   - Also update the test name comments from "no longer supported" to "Expected 'AS' keyword" where they appear.

3. In `tests/parse_proptest.rs` line ~328-341, update the proptest `position_invariant_paren_body_rejected`:
   - Change doc comment from `"no longer supported"` to `"Expected 'AS' keyword"`.
   - Change `err.message.contains("no longer supported")` to `err.message.contains("Expected 'AS' keyword")`.
   - Update the assertion message string similarly.

4. In `test/sql/phase21_error_reporting.test` Section 1 (lines 15-25), update the two `statement error` assertions:
   - Line 15 comment: change from `"no longer supported"` to match the new error text.
   - Line 19: change expected error substring from `no longer supported` to `Expected 'AS' keyword`.
   - Line 25: change expected error substring from `no longer supported` to `Expected 'AS' keyword`.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test -- test_validate_and_rewrite_rejects_paren_body test_parse_error_position_paren_body_rejected old_paren_body_is_rejected position_invariant_paren_body_rejected 2>&1 | tail -5</automated>
  </verify>
  <done>Error message no longer contains "no longer supported" or "old paren-body". All Rust unit tests, the proptest, and the sqllogictest assertions use "Expected 'AS' keyword" as their match substring. `cargo test` passes.</done>
</task>

<task type="auto">
  <name>Task 2: Rewrite Python caret tests to AS-body error scenarios</name>
  <files>test/integration/test_caret_position.py</files>
  <action>
Rewrite the two failing Python caret tests. The tests must still exercise caret positioning end-to-end through the extension pipeline. Replace paren-body DDL queries with AS-body malformed DDL that produces errors WITH caret positions from `body_parser.rs`.

**test_caret_missing_paren (Test 1):**
Replace the structural error test. Instead of testing missing `(` in paren-body, test missing `(` after a clause keyword in AS-body syntax.

- OLD query: `"CREATE SEMANTIC VIEW myview tables := []"`
- NEW query: `"CREATE SEMANTIC VIEW myview AS TABLES x"` -- this will hit the "Expected '(' after clause keyword 'TABLES'" error in body_parser.rs's `find_clause_bounds`.
- Update the function name/docstring: "Caret points where '(' is expected after clause keyword"
- Assert error text contains `"Expected '('"` (body_parser.rs produces `"Expected '(' after clause keyword 'TABLES', found 'x'."`)
- Assert caret position is not None.
- Calculate expected position: the caret should point at the 'x' character. `"CREATE SEMANTIC VIEW myview AS TABLES "` is 38 chars, then 'x' at position 38. So `expected_pos = len("CREATE SEMANTIC VIEW myview AS TABLES ")` which is 38.
- Assert `pos == expected_pos` with a helpful message.
- Update the print message.

**test_caret_clause_typo (Test 2):**
Replace the clause typo test. Instead of testing a typo in paren-body, test a typo in AS-body clause keyword.

- OLD query: `"CREATE SEMANTIC VIEW x (tbles := [], dimensions := [])"`
- NEW query: `"CREATE SEMANTIC VIEW x AS TBLES (t AS tbl PRIMARY KEY (id)) DIMENSIONS (t.x AS x)"` -- this will hit the "Unknown clause keyword 'TBLES'; did you mean 'TABLES'?" error in body_parser.rs's `find_clause_bounds`.
- Update the function name/docstring: "Caret points at misspelled clause keyword in AS body"
- Assert error text contains `"did you mean"` (case-insensitive check is fine, or just check for `"TABLES"` in the error).
- Assert caret position is not None.
- Calculate expected position: `"CREATE SEMANTIC VIEW x AS "` is 26 chars, then 'TBLES' starts at position 26. So `expected_pos = len("CREATE SEMANTIC VIEW x AS ")` which is 26.
- Assert `query[pos:pos + 5] == "TBLES"` with a helpful message.
- Update the print message.

**test_caret_near_miss (Test 3):**
Leave unchanged -- it already passes. The query `"CRETAE SEMANTIC VIEW x (tables := [])"` tests a near-miss PREFIX error (before the view name is even parsed), which is independent of paren-body vs AS-body.

**Update the module docstring** at the top of the file:
- Change the three representative error types description:
  1. Structural error -- missing '(' after clause keyword (ERR-02)
  2. Clause-level error -- typo in AS-body keyword (ERR-01)
  3. Near-miss prefix error (ERR-03)
- Remove any reference to "paren" or "paren-body" in the module docstring.

**Update ALL_TESTS list names** if function names changed. Keep function names as `test_caret_missing_paren` and `test_caret_clause_typo` to avoid changing the test runner invocation pattern (the names are fine as-is since they describe the error type, not the DDL syntax).
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && just build && just test-caret 2>&1 | tail -10</automated>
  </verify>
  <done>All 3 Python caret position tests pass. Each test exercises a different error path with correct caret positioning: (1) missing '(' after clause keyword, (2) typo in clause keyword with "did you mean" suggestion, (3) near-miss DDL prefix. `just test-all` passes (quality gate met).</done>
</task>

</tasks>

<verification>
Full quality gate verification after both tasks:

```bash
just test-all
```

This runs: `cargo test` (Rust unit + proptest), `just test-sql` (sqllogictest including phase21_error_reporting.test), `just test-ducklake-ci`, `just test-vtab-crash`, and `just test-caret` (Python caret tests).

All must pass for the gap to be closed.
</verification>

<success_criteria>
1. `src/parse.rs` error message says "Expected 'AS' keyword after view name. Use: CREATE SEMANTIC VIEW name AS TABLES (...) DIMENSIONS (...) METRICS (...)" -- no mention of "old paren-body" or "no longer supported"
2. All 3 Rust unit tests asserting on the error pass with "Expected 'AS' keyword" match
3. The proptest `position_invariant_paren_body_rejected` passes with "Expected 'AS' keyword" match
4. `phase21_error_reporting.test` Section 1 passes with "Expected 'AS' keyword" match
5. All 3 Python caret tests pass -- tests 1 and 2 exercise AS-body error paths with correct caret positions
6. `just test-all` passes (quality gate)
</success_criteria>

<output>
After completion, create `.planning/phases/27-alias-based-query-expansion/27-03-SUMMARY.md`
</output>
