---
phase: 15-entry-point-poc
plan: 02
type: execute
wave: 2
depends_on: ["15-01"]
files_modified:
  - cpp/src/shim.cpp
  - src/lib.rs
  - Makefile
  - _notes/entry-point-decision.md
  - .planning/STATE.md
autonomous: true
requirements: [ENTRY-01, ENTRY-02, ENTRY-03]

must_haves:
  truths:
    - "Extension loads via LOAD in DuckDB CLI with a stub parser hook registered"
    - "CREATE SEMANTIC VIEW triggers the stub parse_function which chains through plan_function to execute a SELECT statement, proving parse -> plan -> execute works"
    - "Existing semantic_view() queries return correct results after loading the new binary"
    - "A go/no-go decision is recorded with full rationale"
    - "STATE.md accumulated decisions updated with Phase 15 outcomes"
  artifacts:
    - path: "cpp/src/shim.cpp"
      provides: "C++ CPP entry point with parser hook stub registration and Rust FFI delegation"
      contains: "DUCKDB_CPP_EXTENSION_ENTRY"
    - path: "src/lib.rs"
      provides: "Rust init exposed as extern C for C++ entry to call"
      contains: "sv_rust_init"
    - path: "Makefile"
      provides: "ABI type set to CPP for extension footer metadata"
      contains: "CPP"
    - path: "_notes/entry-point-decision.md"
      provides: "Go/no-go decision with rationale for chosen entry point strategy"
    - path: ".planning/STATE.md"
      provides: "Updated accumulated decisions with Phase 15 entry point strategy outcome"
  key_links:
    - from: "cpp/src/shim.cpp"
      to: "src/lib.rs"
      via: "extern C FFI call to sv_rust_init"
      pattern: "sv_rust_init"
    - from: "cpp/src/shim.cpp"
      to: "duckdb::DBConfig"
      via: "parser_extensions.push_back(ext)"
      pattern: "parser_extensions"
    - from: "cpp/src/shim.cpp sv_parse_stub"
      to: "cpp/src/shim.cpp sv_plan_stub"
      via: "parse detects CREATE SEMANTIC VIEW, returns success -> DuckDB calls plan_function"
      pattern: "CREATE SEMANTIC VIEW"
    - from: "Makefile"
      to: "extension-ci-tools/scripts/append_extension_metadata.py"
      via: "UNSTABLE_C_API_FLAG override to --abi-type CPP"
      pattern: "abi-type CPP"
---

<objective>
Switch the extension from C_STRUCT entry point (pure Rust) to CPP entry point (C++ shim delegates to Rust via FFI), register a stub parser hook that proves the full hook chain works (parse -> plan -> execute) by intercepting `CREATE SEMANTIC VIEW` and returning a stub SELECT, and verify all existing functionality is preserved. Record the go/no-go decision and update STATE.md.

Purpose: This is the core spike. ENTRY-02 (Option B) is the primary path. If it succeeds and `just test-all` passes, Phase 15 is complete. If it fails, Option A (ENTRY-01) is attempted as fallback before declaring no-go.
Output: Working extension binary with CPP entry point, stub parser hook proving full chain, decision document, and updated STATE.md
</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/15-entry-point-poc/15-CONTEXT.md
@.planning/phases/15-entry-point-poc/15-RESEARCH.md
@.planning/phases/15-entry-point-poc/15-01-SUMMARY.md

<interfaces>
<!-- Key types and contracts from existing codebase that the executor needs -->

From src/lib.rs extension module (current init_extension signature, to be preserved):
```rust
fn init_extension(
    con: &Connection,
    db_handle: ffi::duckdb_database,
) -> Result<(), Box<dyn Error>> {
    // Resolves db_path, inits catalog, creates persist_conn and query_conn,
    // registers all DDL and query table functions.
    // ALL of this logic must be preserved -- only the entry point wrapper changes.
}
```

From src/lib.rs (current FFI entry point, to be REPLACED):
```rust
#[no_mangle]
pub unsafe extern "C" fn semantic_views_init_c_api(
    info: ffi::duckdb_extension_info,
    access: *const ffi::duckdb_extension_access,
) -> bool {
    // This C_STRUCT entry point is being replaced by the C++ CPP entry.
    // The init_extension() logic inside is preserved, only the wrapper changes.
}
```

From research -- C++ entry point pattern:
```cpp
// DUCKDB_CPP_EXTENSION_ENTRY(semantic_views, loader) expands to:
// extern "C" __attribute__((visibility("default")))
// void semantic_views_duckdb_cpp_init(duckdb::ExtensionLoader &loader)
```

From research -- ParserExtension registration:
```cpp
ParserExtension ext;
ext.parse_function = sv_parse_stub;
ext.plan_function = sv_plan_stub;
auto &config = DBConfig::GetConfig(db);
config.parser_extensions.push_back(ext);
// NOTE: ParserExtension::Register() does NOT exist in v1.4.4
```

From research -- ParserExtension hook chain:
```
1. DuckDB parser fails on unrecognized SQL (e.g., "CREATE SEMANTIC VIEW ...")
2. DuckDB iterates registered parser_extensions, calls each parse_function
3. If parse_function returns a successful result (not DISPLAY_ORIGINAL_ERROR),
   DuckDB stores the parse data and later calls plan_function
4. plan_function returns a TableRef (e.g., a table function or subquery)
   which DuckDB then plans and executes normally
```

From research -- CRITICAL open question:
The existing Rust code uses duckdb-rs C API bindings (libduckdb-sys).
Under loadable-extension feature, ALL C API calls go through function pointer stubs
initialized by duckdb_rs_extension_api_init(). With CPP ABI, this initialization
path may be skipped. The executor MUST resolve this -- either by manually calling
the init function or by restructuring the Rust init to avoid the issue.
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Rewrite shim.cpp as full CPP entry point with parser hook stubs and Rust FFI bridge</name>
  <files>cpp/src/shim.cpp, src/lib.rs</files>
  <action>
**shim.cpp -- Full rewrite from minimal stub to CPP entry point:**

Create `cpp/src/shim.cpp` with:

1. `#include "duckdb.hpp"` and `using namespace duckdb;`

2. Forward-declare the Rust init function:
   ```cpp
   extern "C" {
       void sv_rust_init(/* params -- see below */);
   }
   ```

3. A custom parse data class to carry the intercepted query through to plan_function:
   ```cpp
   struct SemanticViewParseData : public ParserExtensionParseData {
       string query;
       SemanticViewParseData(string q) : query(std::move(q)) {}
       unique_ptr<ParserExtensionParseData> Copy() const override {
           return make_uniq<SemanticViewParseData>(query);
       }
       string ToString() const override { return query; }
   };
   ```

4. Stub parse function that detects `CREATE SEMANTIC VIEW` and returns a successful parse result to trigger the plan_function. For all other queries, returns DISPLAY_ORIGINAL_ERROR so DuckDB handles them normally. Per user decision: "stub parse_function returns a no-op statement proving the full hook chain works: parse -> plan -> execute."
   ```cpp
   static ParserExtensionParseResult sv_parse_stub(
       ParserExtensionInfo *, const string &query) {
       // Trim leading whitespace for prefix matching
       auto trimmed = StringUtil::Lower(StringUtil::Trim(query));
       if (StringUtil::StartsWith(trimmed, "create semantic view")) {
           // Phase 15 stub: intercept the statement and pass it to plan_function
           ParserExtensionParseResult result;
           result.type = ParserExtensionResultType::PARSE_SUCCESSFUL;
           result.parse_data = make_uniq<SemanticViewParseData>(query);
           return result;
       }
       // Everything else: fall through to DuckDB's normal parser
       return ParserExtensionParseResult();  // Default = DISPLAY_ORIGINAL_ERROR
   }
   ```

5. Stub plan function that rewrites the intercepted statement into a simple SELECT proving the full chain works. Per user decision: return `SELECT 'CREATE SEMANTIC VIEW stub fired'`.
   ```cpp
   static ParserExtensionPlanResult sv_plan_stub(
       ParserExtensionInfo *, ClientContext &context,
       unique_ptr<ParserExtensionParseData> parse_data) {
       // Phase 15 stub: rewrite to a SELECT that proves the full
       // parse -> plan -> execute chain works end-to-end.
       ParserExtensionPlanResult result;
       result.function = make_uniq<TableFunctionRef>();
       // Use a subquery approach: rewrite to SELECT 'CREATE SEMANTIC VIEW stub fired' AS result
       // The simplest way is to create a replacement query via Connection::Query or
       // return a TableRef that DuckDB can plan.
       //
       // Approach: Use SubqueryRef wrapping a SelectStatement that returns the stub string.
       // Alternatively, use the simpler approach of returning a table function ref.
       //
       // Simplest approach: return a TableFunctionRef pointing to a
       // query_table function, OR create a SubqueryRef. DuckDB's parser
       // extension examples (prql) use Connection to parse a replacement SQL
       // string. Follow that pattern:
       auto &db = DatabaseInstance::GetDatabase(context);
       Connection conn(db);
       auto statements = conn.ExtractStatements("SELECT 'CREATE SEMANTIC VIEW stub fired' AS result");
       if (!statements.empty()) {
           // Return the first (only) statement as a replacement
           result.plan = std::move(statements[0]);
           result.type = ParserExtensionPlanResultType::REPLACE_STATEMENT;
           return result;
       }
       throw InternalException("sv_plan_stub: failed to create replacement statement");
   }
   ```

   NOTE: The exact API for returning a replacement statement may vary. The executor should consult the prql extension source and `parser_extension.hpp` in the vendored header to determine the correct `ParserExtensionPlanResult` fields. The key contract is: `CREATE SEMANTIC VIEW anything(...)` must execute and return the string `'CREATE SEMANTIC VIEW stub fired'`. If `REPLACE_STATEMENT` is not available in v1.4.4, an alternative approach is to return a `TableFunctionRef` that wraps a simple SELECT.

6. The entry point using `DUCKDB_CPP_EXTENSION_ENTRY`:
   ```cpp
   extern "C" {
   DUCKDB_CPP_EXTENSION_ENTRY(semantic_views, loader) {
       auto &db = loader.GetDatabaseInstance();

       // Register parser extension (stub for Phase 15)
       ParserExtension ext;
       ext.parse_function = sv_parse_stub;
       ext.plan_function = sv_plan_stub;
       auto &config = DBConfig::GetConfig(db);
       config.parser_extensions.push_back(ext);

       // Delegate to Rust for catalog init + function registration
       sv_rust_init(/* connection/db handle -- design below */);
   }
   }
   ```

**CRITICAL DESIGN DECISION -- How to pass database handle to Rust:**

The existing `init_extension()` needs a `&Connection` (duckdb-rs) and a `duckdb_database` (C API handle). The C++ entry receives `ExtensionLoader& loader` -> `loader.GetDatabaseInstance()` -> `DatabaseInstance&`.

The highest-risk item (Research Open Question 2) is whether Rust's C API stubs are initialized. Under the `loadable-extension` feature, `duckdb_rs_extension_api_init()` must be called to initialize the function pointer table. With CPP ABI, DuckDB does NOT call this automatically.

**Approach:** Restructure the Rust init to work differently under CPP entry. Instead of receiving `duckdb_extension_info` + `duckdb_extension_access` (which are C_STRUCT-specific), receive a raw `duckdb_database` pointer constructed in C++.

In the C++ entry:
- Create a `Connection conn(db)` on the stack
- The `duckdb_connection` C API handle can be obtained as a pointer to the internal `Connection` object (since `duckdb_connection` is typedef'd as `void*` wrapping the connection)
- However, `duckdb_database` is also needed and is NOT simply `&db` (it wraps a struct containing `shared_ptr<DatabaseInstance>`)

**Simplest resolution:** Have the C++ entry create BOTH connections needed by Rust (the init connection and the persist connection and the query connection), pass all of them as raw pointers. OR better: pass `DatabaseInstance*` as an opaque pointer, and have Rust reconstruct what it needs.

**Actually simplest:** The duckdb-rs `Connection::open_from_raw()` expects a raw pointer. We can create a `duckdb_database`-compatible handle in C++. But the real question is whether `duckdb_connect()` (C API) works without `duckdb_rs_extension_api_init()`.

**Recommended approach for the executor:**
1. Try passing `&db` (DatabaseInstance*) cast to `duckdb_database` to Rust's `sv_rust_init`, then have Rust call `duckdb_connect()` on it. If the C API stubs are not initialized, this will crash.
2. If it crashes, try initializing the C API stubs manually. The `duckdb_rs_extension_api_init` function is declared in `libduckdb_sys`. Try calling it from C++ by looking up the symbol from the host process, or restructure the Rust init to create connections via duckdb-rs `Connection` constructors instead of C API calls.
3. If neither works, try creating Connection objects in C++ and passing them as `duckdb_connection` handles.

This is a spike -- empirical exploration is expected. Try the simplest approach first and iterate.

**src/lib.rs -- Rewrite the extension module:**

1. Keep `init_extension()` intact -- it contains all the catalog/function registration logic.

2. Replace the `semantic_views_init_c_api` entry point and `semantic_views_init_c_api_internal` with a new `sv_rust_init` function:
   ```rust
   /// Called by C++ entry point to initialize all Rust-side functionality.
   ///
   /// # Safety
   /// Called across FFI boundary by C++ shim. Parameters must be valid.
   #[no_mangle]
   pub unsafe extern "C" fn sv_rust_init(/* params matching C++ side */) {
       // Initialize C API stubs if needed (for loadable-extension feature)
       // Create Connection from raw handle
       // Call init_extension(con, db_handle)
       // Handle errors (eprintln or set_error callback)
   }
   ```

3. Remove or `#[cfg]`-gate the old `semantic_views_init_c_api` entry point. Since we are switching to CPP ABI, this symbol should NOT be exported. If it must exist for compilation reasons (duckdb-rs generates it), make it a no-op or gate it out.

4. The `MINIMUM_DUCKDB_VERSION` constant can be removed or repurposed -- version checking is handled by the CPP entry/ExtensionLoader.

AVOID:
- Do NOT use `reinterpret_cast<duckdb_database>(&db)` naively -- `duckdb_database` is NOT a direct pointer to `DatabaseInstance` (per research anti-pattern)
- Do NOT keep both entry points active -- only `semantic_views_duckdb_cpp_init` should be exported
- Do NOT use `ParserExtension::Register()` -- it does NOT exist in v1.4.4 (per research anti-pattern)
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo build --no-default-features --features extension 2>&amp;1 | tail -10</automated>
  </verify>
  <done>shim.cpp contains DUCKDB_CPP_EXTENSION_ENTRY with parser hook stubs: sv_parse_stub detects CREATE SEMANTIC VIEW prefix and returns PARSE_SUCCESSFUL, sv_plan_stub rewrites to SELECT stub string. src/lib.rs exposes sv_rust_init as extern "C" that delegates to init_extension. Extension binary compiles.</done>
</task>

<task type="auto">
  <name>Task 2: Update Makefile ABI type, verify full build chain with hook chain test, run test-all, record decision, and update STATE.md</name>
  <files>Makefile, _notes/entry-point-decision.md, .planning/STATE.md</files>
  <action>
1. **Update Makefile ABI type** from C_STRUCT_UNSTABLE to CPP:
   Change `USE_UNSTABLE_C_API=1` to override the `UNSTABLE_C_API_FLAG` directly:
   ```makefile
   # Override: CPP entry point for parser hook access
   UNSTABLE_C_API_FLAG=--abi-type CPP
   ```
   Remove or comment out `USE_UNSTABLE_C_API=1`.

2. **Build the full extension**: `just build`
   This runs `make debug` which: compiles Rust + C++ shim, copies the library, and stamps the footer metadata with `--abi-type CPP`.

3. **Verify symbol export**:
   ```bash
   nm -gU target/debug/libsemantic_views.dylib | grep semantic_views
   ```
   Expected: `_semantic_views_duckdb_cpp_init` is present.
   Expected: `_semantic_views_init_c_api` is NOT present (or not exported).

4. **Verify the full parser hook chain** (parse -> plan -> execute) by invoking `CREATE SEMANTIC VIEW` in DuckDB CLI and asserting the stub fires. Per user decision: "stub parse_function returns a no-op statement (e.g., `SELECT 'CREATE SEMANTIC VIEW stub fired'`) proving the full hook chain works."
   ```bash
   # Load the extension and run CREATE SEMANTIC VIEW to test the hook chain
   duckdb -unsigned -cmd "LOAD 'build/debug/semantic_views.duckdb_extension'" \
     -c "CREATE SEMANTIC VIEW test_stub (tables := [], dimensions := [], metrics := []);" \
     2>&1
   ```
   Expected output should contain `CREATE SEMANTIC VIEW stub fired` (the string returned by sv_plan_stub).
   If the output shows an error instead, the hook chain is not working and must be debugged.

5. **Run the full test suite**: `just test-all`
   This runs: `cargo test` (Rust unit/prop tests) + `just test-sql` (sqllogictest) + `just test-ducklake-ci` (DuckLake integration).
   ALL existing tests must pass -- this proves ENTRY-03 (existing functionality preserved).

6. **If test-all FAILS:**
   - Diagnose the failure. Most likely cause: C API stub initialization issue (Open Question 2 from research).
   - If the issue is fixable (e.g., manually initializing stubs), fix it and re-run.
   - If Option B is fundamentally broken, pivot to Option A (C_STRUCT + C++ helper):
     - Revert shim.cpp to a helper function (not an entry point)
     - Keep `semantic_views_init_c_api` as the entry point
     - Call the C++ helper from the Rust entry to register parser hooks
     - Re-test with `just test-all`
   - Only declare no-go if BOTH options fail.

7. **Record the decision** in `_notes/entry-point-decision.md`:
   ```markdown
   # Entry Point Decision: Phase 15

   **Date:** [date]
   **Decision:** [GO / NO-GO]
   **Strategy:** [Option B (CPP) / Option A (C_STRUCT + helper) / Neither]

   ## What Was Tried
   [Describe Option B attempt and result]
   [If applicable: describe Option A attempt and result]

   ## Why It Works / Doesn't Work
   [Technical explanation with specific error messages or success evidence]

   ## Key Findings
   - C API stub initialization: [what happened]
   - Connection lifetime: [how it was resolved]
   - Parser hook registration: [confirmed working / not working]
   - Hook chain verification: [CREATE SEMANTIC VIEW -> stub fired? yes/no]

   ## Implications for Phase 16+
   [What the next phases should know]
   ```

8. **Update `.planning/STATE.md`** with accumulated decisions from Phase 15. Per user decision: "STATE.md accumulated decisions updated as usual." Add to the `### Decisions` section:
   - `[Phase 15]: Entry point strategy: [Option B (CPP) / Option A / no-go] -- [brief rationale]`
   - `[Phase 15]: Parser hook chain verified: parse -> plan -> execute works via stub`
   - Update `stopped_at`, `last_activity`, `progress` fields to reflect Phase 15 completion

9. **Commit all changes** on the feature branch.

IMPORTANT: This is a spike. If something doesn't work on first try, debugging and iteration are expected. The goal is a GO/NO-GO answer, not perfection.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && just build 2>&amp;1 | tail -5 && duckdb -unsigned -cmd "LOAD 'build/debug/semantic_views.duckdb_extension'" -c "CREATE SEMANTIC VIEW test_stub (tables := [], dimensions := [], metrics := []);" 2>&amp;1 | grep -i "stub fired" && just test-all 2>&amp;1 | tail -20</automated>
  </verify>
  <done>Extension loads via LOAD with CPP ABI. Parser hook chain verified: `CREATE SEMANTIC VIEW` triggers sv_parse_stub -> sv_plan_stub -> executes SELECT returning 'CREATE SEMANTIC VIEW stub fired'. All existing tests pass via `just test-all`. Go/no-go decision recorded in _notes/entry-point-decision.md. STATE.md updated with Phase 15 decisions.</done>
</task>

</tasks>

<verification>
1. `nm -gU target/debug/libsemantic_views.dylib | grep duckdb_cpp_init` shows the CPP entry symbol
2. `CREATE SEMANTIC VIEW test(...)` in DuckDB CLI returns 'CREATE SEMANTIC VIEW stub fired' (full hook chain: parse -> plan -> execute)
3. `just test-all` passes (cargo test + sqllogictest + DuckLake CI)
4. `_notes/entry-point-decision.md` exists with go/no-go rationale
5. Makefile uses `--abi-type CPP` instead of `C_STRUCT_UNSTABLE`
6. `cargo test` (default features) passes without C++ compilation
7. `.planning/STATE.md` updated with Phase 15 decisions
</verification>

<success_criteria>
- Extension loads via LOAD in DuckDB with stub parser hook registered (ENTRY-02)
- CREATE SEMANTIC VIEW triggers the full hook chain and returns stub result (per user verification decision)
- Existing semantic_view() queries return correct results -- just test-all green (ENTRY-03)
- Go/no-go decision recorded with rationale (ENTRY-01 is addressed as fallback path if needed)
- STATE.md updated with accumulated Phase 15 decisions
- Feature branch has all changes committed
</success_criteria>

<output>
After completion, create `.planning/phases/15-entry-point-poc/15-02-SUMMARY.md`
</output>
