---
phase: 20-extended-ddl-statements
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/parse.rs
  - cpp/src/shim.cpp
  - test/sql/phase20_extended_ddl.test
  - test/sql/TEST_LIST
autonomous: true
requirements: [DDL-03, DDL-04, DDL-05, DDL-06]

must_haves:
  truths:
    - "DROP SEMANTIC VIEW x removes the view from the catalog"
    - "DROP SEMANTIC VIEW IF EXISTS x is silent when the view does not exist"
    - "CREATE OR REPLACE SEMANTIC VIEW x (...) updates an existing view in place"
    - "CREATE SEMANTIC VIEW IF NOT EXISTS x (...) is a no-op when the view already exists"
    - "All 7 DDL prefixes are detected by the parser hook (including DESCRIBE/SHOW for Plan 02)"
    - "Existing CREATE SEMANTIC VIEW still works unchanged"
    - "Function-based DDL still works unchanged"
  artifacts:
    - path: "src/parse.rs"
      provides: "DdlKind enum, multi-prefix detection, multi-form rewrite, DDL-aware name extraction"
      contains: "DdlKind"
    - path: "cpp/src/shim.cpp"
      provides: "Generic error message for all DDL forms"
      contains: "Semantic view DDL failed"
    - path: "test/sql/phase20_extended_ddl.test"
      provides: "Integration tests for DROP, DROP IF EXISTS, CREATE OR REPLACE, CREATE IF NOT EXISTS"
  key_links:
    - from: "src/parse.rs"
      to: "cpp/src/shim.cpp"
      via: "sv_parse_rust FFI (detect_semantic_view_ddl) + sv_execute_ddl_rust FFI (rewrite_ddl)"
      pattern: "detect_semantic_view_ddl|rewrite_ddl"
    - from: "src/parse.rs"
      to: "src/parse.rs"
      via: "rewrite_ddl_to_function_call uses detect_ddl_kind internally"
      pattern: "detect_ddl_kind"
---

<objective>
Extend Rust detection and rewriting to handle all 7 DDL prefixes, and deliver 4 mutating DDL statements end-to-end (DROP, DROP IF EXISTS, CREATE OR REPLACE, CREATE IF NOT EXISTS).

Purpose: These 4 mutating DDL forms work with the existing C++ sv_ddl_bind/execute path because they all return a single view name confirmation -- no C++ changes needed beyond the error message. DESCRIBE and SHOW require C++ result forwarding and are handled in Plan 02.

Output: Working native DDL for DROP, DROP IF EXISTS, CREATE OR REPLACE, CREATE IF NOT EXISTS. All 7 prefixes detected (DESCRIBE/SHOW detection ready for Plan 02).
</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/20-extended-ddl-statements/20-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

From src/parse.rs (current, to be refactored):
```rust
pub const PARSE_NOT_OURS: u8 = 0;
pub const PARSE_DETECTED: u8 = 1;

// Current single-prefix detection -- to be replaced with multi-prefix
pub fn detect_create_semantic_view(query: &str) -> u8 { ... }

// Current CREATE-only parser -- to be extended for all DDL forms
pub fn parse_ddl_text(query: &str) -> Result<(&str, &str), String> { ... }

// Current CREATE-only rewrite -- to be extended for all DDL forms
pub fn rewrite_ddl_to_function_call(query: &str) -> Result<String, String> { ... }

// FFI entry points (extension feature-gated):
pub extern "C" fn sv_parse_rust(query_ptr: *const u8, query_len: usize) -> u8;
pub extern "C" fn sv_execute_ddl_rust(
    query_ptr: *const u8, query_len: usize,
    exec_conn: duckdb_connection,
    name_out: *mut u8, name_out_len: usize,
    error_out: *mut u8, error_out_len: usize,
) -> u8;
```

From cpp/src/shim.cpp (current):
```cpp
// Line 118 -- hardcoded error message to update:
throw BinderException("CREATE SEMANTIC VIEW failed: %s", error_buf);
// Line 99-100 -- single-column output schema (unchanged for Plan 01):
return_types.push_back(LogicalType::VARCHAR);
names.push_back("view_name");
```

Function call targets (already registered in src/lib.rs):
```
create_semantic_view('name', tables := [...], dimensions := [...], metrics := [...])
create_or_replace_semantic_view('name', tables := [...], dimensions := [...], metrics := [...])
create_semantic_view_if_not_exists('name', tables := [...], dimensions := [...], metrics := [...])
drop_semantic_view('name')
drop_semantic_view_if_exists('name')
describe_semantic_view('name')   -- Plan 02
list_semantic_views()            -- Plan 02
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Refactor parse.rs to DdlKind enum with multi-prefix detection and rewriting</name>
  <files>src/parse.rs</files>
  <behavior>
    Detection tests (detect_semantic_view_ddl):
    - "CREATE SEMANTIC VIEW x (...)" returns PARSE_DETECTED
    - "CREATE OR REPLACE SEMANTIC VIEW x (...)" returns PARSE_DETECTED
    - "CREATE SEMANTIC VIEW IF NOT EXISTS x (...)" returns PARSE_DETECTED
    - "DROP SEMANTIC VIEW x" returns PARSE_DETECTED
    - "DROP SEMANTIC VIEW IF EXISTS x" returns PARSE_DETECTED
    - "DESCRIBE SEMANTIC VIEW x" returns PARSE_DETECTED
    - "SHOW SEMANTIC VIEWS" returns PARSE_DETECTED
    - "create or replace semantic view x (...)" returns PARSE_DETECTED (case insensitive)
    - "  DROP SEMANTIC VIEW x  ;" returns PARSE_DETECTED (whitespace + semicolon)
    - "SELECT 1" returns PARSE_NOT_OURS
    - "CREATE TABLE t (id INT)" returns PARSE_NOT_OURS
    - "DESCRIBE my_table" returns PARSE_NOT_OURS (must not intercept normal DESCRIBE)
    - "SHOW TABLES" returns PARSE_NOT_OURS
    - "" returns PARSE_NOT_OURS

    DdlKind detection tests (detect_ddl_kind):
    - "CREATE SEMANTIC VIEW x (...)" returns Some(DdlKind::Create)
    - "CREATE OR REPLACE SEMANTIC VIEW x (...)" returns Some(DdlKind::CreateOrReplace) (NOT Create)
    - "CREATE SEMANTIC VIEW IF NOT EXISTS x (...)" returns Some(DdlKind::CreateIfNotExists) (NOT Create)
    - "DROP SEMANTIC VIEW x" returns Some(DdlKind::Drop)
    - "DROP SEMANTIC VIEW IF EXISTS x" returns Some(DdlKind::DropIfExists) (NOT Drop)
    - "DESCRIBE SEMANTIC VIEW x" returns Some(DdlKind::Describe)
    - "SHOW SEMANTIC VIEWS" returns Some(DdlKind::Show)
    - "SELECT 1" returns None

    Rewrite tests (rewrite_ddl):
    - "CREATE SEMANTIC VIEW sales (...body...)" rewrites to "SELECT * FROM create_semantic_view('sales', ...body...)"
    - "CREATE OR REPLACE SEMANTIC VIEW sales (...body...)" rewrites to "SELECT * FROM create_or_replace_semantic_view('sales', ...body...)"
    - "CREATE SEMANTIC VIEW IF NOT EXISTS sales (...body...)" rewrites to "SELECT * FROM create_semantic_view_if_not_exists('sales', ...body...)"
    - "DROP SEMANTIC VIEW sales" rewrites to "SELECT * FROM drop_semantic_view('sales')"
    - "DROP SEMANTIC VIEW IF EXISTS sales" rewrites to "SELECT * FROM drop_semantic_view_if_exists('sales')"
    - "DESCRIBE SEMANTIC VIEW sales" rewrites to "SELECT * FROM describe_semantic_view('sales')"
    - "SHOW SEMANTIC VIEWS" rewrites to "SELECT * FROM list_semantic_views()"
    - Name with single quote ("it's") is escaped to ("it''s")
    - "DROP SEMANTIC VIEW" with no name returns Err

    Name extraction tests (extract_ddl_name):
    - "DROP SEMANTIC VIEW x" returns Ok("x")
    - "DROP SEMANTIC VIEW IF EXISTS x" returns Ok("x")
    - "DESCRIBE SEMANTIC VIEW x" returns Ok("x")
    - "SHOW SEMANTIC VIEWS" returns Ok(None) or equivalent no-name result
    - "CREATE SEMANTIC VIEW x (body)" returns Ok("x")
    - "CREATE OR REPLACE SEMANTIC VIEW x (body)" returns Ok("x")
  </behavior>
  <action>
    Refactor src/parse.rs as follows:

    1. Add a `DdlKind` enum with 7 variants: Create, CreateOrReplace, CreateIfNotExists, Drop, DropIfExists, Describe, Show.

    2. Replace `detect_create_semantic_view` with `detect_semantic_view_ddl(query: &str) -> u8` that checks all 7 prefixes using longest-first ordering (see RESEARCH.md "Pattern: Multi-Prefix Detection"). Keep the same PARSE_NOT_OURS/PARSE_DETECTED return type for FFI compatibility. Internally use a helper `detect_ddl_kind(query: &str) -> Option<DdlKind>`.

    3. Group the 7 DDL forms into 3 parsing categories per RESEARCH.md:
       - CREATE-with-body (Create, CreateOrReplace, CreateIfNotExists): strip prefix, extract name + parenthesized body, rewrite to `SELECT * FROM target_fn('name', body)`
       - Name-only (Drop, DropIfExists, Describe): strip prefix, extract name only, rewrite to `SELECT * FROM target_fn('name')`
       - No-args (Show): strip prefix, rewrite to `SELECT * FROM list_semantic_views()`

    4. Replace `rewrite_ddl_to_function_call` with `rewrite_ddl(query: &str) -> Result<String, String>` that uses `detect_ddl_kind` internally and dispatches to the appropriate rewrite per category.

    5. Add `extract_ddl_name(query: &str) -> Result<Option<String>, String>` that returns the view name for CREATE/DROP/DESCRIBE forms and None for SHOW. This replaces `parse_ddl_text` for name extraction in `sv_execute_ddl_rust`.

    6. Keep `parse_ddl_text` as a private helper for CREATE-with-body forms (it extracts name + body). Add `extract_name_only` for name-only forms.

    7. Update `sv_parse_rust` to call `detect_semantic_view_ddl` instead of `detect_create_semantic_view`.

    8. Update `sv_execute_ddl_rust` to:
       - Call `rewrite_ddl(query)` instead of `rewrite_ddl_to_function_call(query)`
       - Call `extract_ddl_name(query)` instead of `parse_ddl_text(query)` for name_out
       - For SHOW (no name), write "ok" to name_out as a placeholder

    9. Preserve all existing unit tests (update function names as needed). The old function names (`detect_create_semantic_view`, `rewrite_ddl_to_function_call`) can be kept as thin wrappers or the tests can be updated to use the new names.

    Anti-patterns to avoid (from RESEARCH.md):
    - Do NOT use regex for detection -- use byte-slice eq_ignore_ascii_case as the current code does
    - Do NOT modify C++ hook registration -- the same sv_parse_stub path works for all DDL forms
    - Prefix ordering is CRITICAL: check "create or replace semantic view" and "create semantic view if not exists" BEFORE "create semantic view"; check "drop semantic view if exists" BEFORE "drop semantic view"
    - "describe semantic view" must require all 3 words (not just "describe semantic") to avoid intercepting normal `DESCRIBE mytable`
    - "show semantic views" must require "views" (plural) to avoid intercepting normal `SHOW TABLES`
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test</automated>
  </verify>
  <done>
    - DdlKind enum with 7 variants exists
    - detect_semantic_view_ddl handles all 7 prefixes with longest-first ordering
    - rewrite_ddl produces correct function calls for all 7 DDL forms
    - extract_ddl_name returns appropriate names (or None for SHOW)
    - sv_parse_rust and sv_execute_ddl_rust use the new functions
    - All unit tests pass including new tests for each DDL form
    - Existing CREATE SEMANTIC VIEW tests still pass
  </done>
</task>

<task type="auto">
  <name>Task 2: Update C++ error message and add sqllogictest for mutating DDL</name>
  <files>cpp/src/shim.cpp, test/sql/phase20_extended_ddl.test, test/sql/TEST_LIST</files>
  <action>
    1. In cpp/src/shim.cpp, update line 118 from:
       `throw BinderException("CREATE SEMANTIC VIEW failed: %s", error_buf);`
       to:
       `throw BinderException("Semantic view DDL failed: %s", error_buf);`
       This is the ONLY C++ change in Plan 01. No changes to sv_ddl_bind schema, sv_ddl_execute, sv_plan_function, or sv_register_parser_hooks.

    2. Create test/sql/phase20_extended_ddl.test with sqllogictest covering DDL-03, DDL-04, DDL-05, DDL-06:

       Setup:
       - `require semantic_views`
       - Create a source table: `CREATE TABLE sv_test (region VARCHAR, amount DOUBLE);`
       - Insert rows: `INSERT INTO sv_test VALUES ('East', 100.0), ('West', 200.0);`

       DDL-05 (CREATE OR REPLACE):
       - Create a view: `CREATE SEMANTIC VIEW sv_replace_test (tables := [...], dimensions := [...], metrics := [...])`
       - Query it to verify initial state
       - Replace with different metrics: `CREATE OR REPLACE SEMANTIC VIEW sv_replace_test (tables := [...], dimensions := [...], metrics := [new_metrics])`
       - Query it again to verify updated definition
       - Verify only one view exists with `SELECT count(*) FROM list_semantic_views() WHERE name = 'sv_replace_test'` = 1

       DDL-06 (CREATE IF NOT EXISTS):
       - `CREATE SEMANTIC VIEW IF NOT EXISTS sv_idempotent_test (tables := [...], dimensions := [...], metrics := [...])` -- succeeds
       - Same statement again -- should succeed (no error)
       - Verify still only one view: `SELECT count(*) FROM list_semantic_views() WHERE name = 'sv_idempotent_test'` = 1

       DDL-03 (DROP):
       - `DROP SEMANTIC VIEW sv_replace_test` -- succeeds
       - Verify view is gone: `statement error` on `SELECT * FROM describe_semantic_view('sv_replace_test')` with `does not exist`
       - `DROP SEMANTIC VIEW sv_nonexistent` -- `statement error` with `does not exist`

       DDL-04 (DROP IF EXISTS):
       - `DROP SEMANTIC VIEW IF EXISTS sv_idempotent_test` -- succeeds
       - `DROP SEMANTIC VIEW IF EXISTS sv_nonexistent` -- succeeds (no error)

       Case insensitivity:
       - `drop semantic view if exists some_view` -- works (lowercase)
       - `Create Or Replace Semantic View ...` -- works (mixed case)

       Backward compatibility:
       - Normal SQL still works: `SELECT 42` returns 42
       - Function-based DDL still works: `SELECT * FROM create_semantic_view('fn_test', ...)` then `SELECT * FROM drop_semantic_view('fn_test')`

       Cleanup:
       - `DROP TABLE sv_test;`

    3. Add `test/sql/phase20_extended_ddl.test` to `test/sql/TEST_LIST`.

    4. Build and run: `just build && just test-sql` to verify all integration tests pass.

    Note: This test file intentionally omits DESCRIBE SEMANTIC VIEW and SHOW SEMANTIC VIEWS tests -- those are covered in Plan 02 after the C++ result forwarding refactor.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && just build && just test-all</automated>
  </verify>
  <done>
    - C++ error message updated to "Semantic view DDL failed"
    - phase20_extended_ddl.test exists and passes
    - DROP SEMANTIC VIEW removes a view (DDL-03)
    - DROP SEMANTIC VIEW IF EXISTS is silent on missing (DDL-04)
    - CREATE OR REPLACE SEMANTIC VIEW updates in place (DDL-05)
    - CREATE SEMANTIC VIEW IF NOT EXISTS is a no-op on duplicate (DDL-06)
    - Case insensitive detection works for all forms
    - Function-based DDL and normal SQL still work
    - `just test-all` passes (all existing tests + new tests)
  </done>
</task>

</tasks>

<verification>
- `cargo test` -- all Rust unit tests pass (detection, rewrite, name extraction for all 7 forms)
- `just build && just test-sql` -- all sqllogictest files pass including phase20_extended_ddl.test
- `just test-all` -- full suite green (Rust + sqllogictest + DuckLake CI)
- Existing phase16_parser.test and phase2_ddl.test still pass unchanged
</verification>

<success_criteria>
- User can run `DROP SEMANTIC VIEW name` and the view is removed (DDL-03)
- User can run `DROP SEMANTIC VIEW IF EXISTS name` without error when the view does not exist (DDL-04)
- User can run `CREATE OR REPLACE SEMANTIC VIEW name (...)` and the existing view is updated (DDL-05)
- User can run `CREATE SEMANTIC VIEW IF NOT EXISTS name (...)` without error when the view exists (DDL-06)
- All DDL forms are case-insensitive and handle whitespace/semicolons
- Function-based DDL and normal SQL are unaffected
- All 7 DDL prefixes are detected by the parser hook (DESCRIBE/SHOW are detected but not yet end-to-end testable until Plan 02)
</success_criteria>

<output>
After completion, create `.planning/phases/20-extended-ddl-statements/20-01-SUMMARY.md`
</output>
