---
phase: 10-pragma-query-t-catalog-persistence
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/shim/shim.cpp
  - src/shim/shim.h
  - src/shim/mod.rs
autonomous: true
requirements: [PERSIST-01, PERSIST-02]

must_haves:
  truths:
    - "shim.cpp registers two PRAGMA callbacks (define_semantic_view_internal, drop_semantic_view_internal) via ExtensionLoader::RegisterFunction at load time"
    - "shim.cpp exports two C functions (semantic_views_pragma_define, semantic_views_pragma_drop) that execute INSERT OR REPLACE / DELETE via a separate stored connection"
    - "shim.h declares the two new exported C functions so Rust can call them via FFI"
    - "mod.rs declares the unsafe extern C FFI bindings for both functions"
    - "The code compiles under cargo build --features extension (C++ side) and cargo test (Rust FFI declarations present)"
  artifacts:
    - path: "src/shim/shim.cpp"
      provides: "pragma_query_t callbacks + semantic_views_pragma_define/drop C functions"
      contains: "ExtensionLoader"
    - path: "src/shim/shim.h"
      provides: "C header declaring semantic_views_pragma_define and semantic_views_pragma_drop"
      contains: "semantic_views_pragma_define"
    - path: "src/shim/mod.rs"
      provides: "Rust unsafe extern C declarations for the two pragma FFI functions"
      contains: "semantic_views_pragma_define"
  key_links:
    - from: "src/shim/shim.cpp semantic_views_register_shim"
      to: "DuckDB ExtensionLoader::RegisterFunction"
      via: "PragmaFunction::PragmaCall with pragma_query_t callback"
      pattern: "ExtensionLoader"
    - from: "src/shim/mod.rs"
      to: "src/shim/shim.cpp semantic_views_pragma_define/drop"
      via: "unsafe extern C link — statically resolved at build time"
      pattern: "semantic_views_pragma_define"
---

<objective>
Implement the C++ shim pragma registration and add the two Rust-callable C functions for persist-on-invoke.

Purpose: Phase 10 requires two distinct write paths. (1) The PRAGMA path: `pragma_query_t` callbacks registered in `semantic_views_register_shim` — DuckDB executes the returned INSERT/DELETE SQL in the caller's transaction, enabling PERSIST-02 ROLLBACK semantics. (2) The invoke path: `semantic_views_pragma_define` / `semantic_views_pragma_drop` C functions that take a separate `duckdb_connection` and execute INSERT/DELETE directly — safe from within scalar invoke because it is a different connection with its own transaction context.

Output: Updated shim.cpp with real pragma logic, updated shim.h with new function declarations, updated mod.rs with Rust FFI declarations.
</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/STATE.md
@.planning/phases/10-pragma-query-t-catalog-persistence/10-RESEARCH.md
@src/shim/shim.cpp
@src/shim/shim.h
@src/shim/mod.rs

<interfaces>
<!-- Key types from vendored headers (verified against duckdb_capi/) -->

From duckdb_capi/duckdb/function/pragma_function.hpp:
```cpp
typedef string (*pragma_query_t)(ClientContext &context, const FunctionParameters &parameters);
typedef void (*pragma_function_t)(ClientContext &context, const FunctionParameters &parameters);

class PragmaFunction {
public:
    static PragmaFunction PragmaCall(const string &name, pragma_query_t function,
                                      vector<LogicalType> arguments);
};
// FunctionParameters::values is a vector<Value>; Value::GetValue<string>() extracts a string.
```

From duckdb_capi/duckdb/main/extension/extension_loader.hpp:
```cpp
class ExtensionLoader {
public:
    explicit ExtensionLoader(DatabaseInstance &db, const string &extension_name);
    void RegisterFunction(PragmaFunction &function);
};
```

From duckdb_capi/duckdb/main/capi/capi_internal.hpp:
```cpp
// Cast chain (HIGH confidence from header inspection):
// db_instance_ptr (void*) is actually a duckdb_database pointer cast to void*
// duckdb_database = struct _duckdb_database { void *internal_ptr; } *
// internal_ptr points to DatabaseWrapper { shared_ptr<DuckDB> database; }
// DuckDB::instance is shared_ptr<DatabaseInstance>
struct DatabaseWrapper {
    shared_ptr<DuckDB> database;
};
```

From duckdb_capi/duckdb.h:
```c
typedef struct _duckdb_database { void *internal_ptr; } * duckdb_database;
typedef struct _duckdb_connection { void *internal_ptr; } * duckdb_connection;
```

DuckDB SQL API (C API):
```c
// Execute SQL on a connection (not the main connection — no deadlock)
duckdb_state duckdb_query(duckdb_connection connection, const char *query, duckdb_result *out_result);
void duckdb_destroy_result(duckdb_result *result);
```

StringUtil escaping (from duckdb_capi/duckdb/common/string_util.hpp):
```cpp
static string StringUtil::Replace(const string &source, const string &from, const string &to);
```

Current shim.cpp (Phase 8 skeleton):
```cpp
void semantic_views_register_shim(void* /* db_instance_ptr */) {
    // Intentional no-op
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Implement pragma registration in shim.cpp and declare new functions in shim.h</name>
  <files>src/shim/shim.cpp, src/shim/shim.h</files>
  <action>
    Replace the Phase 8 skeleton in shim.cpp with real pragma registration logic. Add two new C functions. Update shim.h to declare the new functions.

    ### shim.h changes

    Add two new function declarations after the existing `semantic_views_register_shim` declaration:

    ```c
    // Called from Rust define_semantic_view invoke to persist a view definition.
    // conn is the separate query_conn created at init time (NOT the main connection).
    // Returns 0 on success, -1 on error (error message written to error_buf).
    int32_t semantic_views_pragma_define(
        duckdb_connection conn,
        const char* name,
        const char* json
    );

    // Called from Rust drop_semantic_view invoke to delete a view definition.
    // Returns 0 on success, -1 on error.
    int32_t semantic_views_pragma_drop(
        duckdb_connection conn,
        const char* name
    );
    ```

    Note: shim.h must remain includable from plain C. Do NOT add C++ headers or `using namespace` inside shim.h. The `duckdb_connection` type is from `duckdb.h` — include it via `#include "duckdb.h"` guarded inside the `#ifdef __cplusplus` block or at the top of shim.h (plain C-compatible).

    Since `duckdb_connection` is a C-compatible typedef from `duckdb.h`, add `#include "duckdb.h"` at the top of shim.h (before the `#ifdef __cplusplus` guard).

    ### shim.cpp changes

    Replace the entire shim.cpp with the following (preserve the Phase 8 comment history with a note):

    ```cpp
    // src/shim/shim.cpp
    // Phase 10: pragma_query_t catalog persistence.
    // Registers define_semantic_view_internal and drop_semantic_view_internal
    // PRAGMAs via ExtensionLoader. Also implements semantic_views_pragma_define
    // and semantic_views_pragma_drop for use from Rust scalar invoke (separate
    // connection — no deadlock).
    //
    // Phase 8 skeleton: proves Rust+C++ build boundary (now replaced with real logic).
    // Phase 11 will add parser_extension hooks for CREATE SEMANTIC VIEW DDL.

    #include "duckdb.hpp"
    #include "duckdb/main/extension/extension_loader.hpp"
    #include "duckdb/function/pragma_function.hpp"
    #include "duckdb/main/capi/capi_internal.hpp"
    #include "duckdb/common/string_util.hpp"
    #include "shim.h"

    using namespace duckdb;

    // ---------------------------------------------------------------------------
    // PRAGMA callbacks (pragma_query_t) — transaction-aware via returned SQL
    // ---------------------------------------------------------------------------

    // Returns INSERT SQL for DuckDB to execute in the caller's transaction.
    // Used by: PRAGMA define_semantic_view_internal('name', 'json')
    static string PragmaDefineSemanticView(ClientContext &context,
                                            const FunctionParameters &params) {
        auto name = params.values[0].GetValue<string>();
        auto json = params.values[1].GetValue<string>();
        // Escape single quotes in values to avoid SQL breakage (Pitfall 1 from RESEARCH)
        auto safe_name = StringUtil::Replace(name, "'", "''");
        auto safe_json = StringUtil::Replace(json, "'", "''");
        return "INSERT OR REPLACE INTO semantic_layer._definitions (name, definition) "
               "VALUES ('" + safe_name + "', '" + safe_json + "')";
    }

    // Returns DELETE SQL for DuckDB to execute in the caller's transaction.
    // Used by: PRAGMA drop_semantic_view_internal('name')
    static string PragmaDropSemanticView(ClientContext &context,
                                          const FunctionParameters &params) {
        auto name = params.values[0].GetValue<string>();
        auto safe_name = StringUtil::Replace(name, "'", "''");
        return "DELETE FROM semantic_layer._definitions WHERE name = '" + safe_name + "'";
    }

    extern "C" {

    // ---------------------------------------------------------------------------
    // semantic_views_register_shim — called at extension load time (lib.rs)
    // Phase 10: registers pragma callbacks. Phase 11 will also add parser hooks.
    // ---------------------------------------------------------------------------
    void semantic_views_register_shim(void* db_instance_ptr) {
        // Cast chain: void* -> duckdb_database -> DatabaseWrapper -> DatabaseInstance&
        // See RESEARCH.md Pitfall 3 and capi_internal.hpp for struct definitions.
        auto* db_c = reinterpret_cast<duckdb_database>(db_instance_ptr);
        auto* wrapper = reinterpret_cast<DatabaseWrapper*>(db_c->internal_ptr);
        DatabaseInstance& db_instance = *wrapper->database->instance;

        ExtensionLoader loader(db_instance, "semantic_views");

        // Register PRAGMA define_semantic_view_internal(name VARCHAR, json VARCHAR)
        // pragma_query_t: returned SQL executes in the caller's transaction (PERSIST-02)
        auto define_pragma = PragmaFunction::PragmaCall(
            "define_semantic_view_internal",
            PragmaDefineSemanticView,
            {LogicalType::VARCHAR, LogicalType::VARCHAR}
        );
        loader.RegisterFunction(define_pragma);

        // Register PRAGMA drop_semantic_view_internal(name VARCHAR)
        auto drop_pragma = PragmaFunction::PragmaCall(
            "drop_semantic_view_internal",
            PragmaDropSemanticView,
            {LogicalType::VARCHAR}
        );
        loader.RegisterFunction(drop_pragma);
    }

    // ---------------------------------------------------------------------------
    // semantic_views_pragma_define / _drop — called from Rust scalar invoke
    //
    // These use a SEPARATE connection (conn) created at init time. Because it is
    // a different connection, it has its own transaction and does NOT deadlock with
    // the main connection's execution lock. The write is auto-committed.
    //
    // Limitation (documented in RESEARCH.md Pitfall 4): this write is NOT in the
    // user's explicit transaction. The scalar define_semantic_view() is not intended
    // to be wrapped in user BEGIN/ROLLBACK. For transactional usage, call
    // PRAGMA define_semantic_view_internal(...) directly.
    // ---------------------------------------------------------------------------

    int32_t semantic_views_pragma_define(
        duckdb_connection conn,
        const char* name,
        const char* json
    ) {
        if (!conn || !name || !json) return -1;

        // Build parameterized INSERT OR REPLACE using string escaping
        // (pragma_query_t uses raw SQL; here we use duckdb_query directly)
        string name_str(name);
        string json_str(json);
        // Escape single quotes in values
        auto safe_name = StringUtil::Replace(name_str, "'", "''");
        auto safe_json = StringUtil::Replace(json_str, "'", "''");

        string sql = "INSERT OR REPLACE INTO semantic_layer._definitions (name, definition) "
                     "VALUES ('" + safe_name + "', '" + safe_json + "')";

        duckdb_result result;
        auto state = duckdb_query(conn, sql.c_str(), &result);
        duckdb_destroy_result(&result);
        return (state == DuckDBSuccess) ? 0 : -1;
    }

    int32_t semantic_views_pragma_drop(
        duckdb_connection conn,
        const char* name
    ) {
        if (!conn || !name) return -1;

        string name_str(name);
        auto safe_name = StringUtil::Replace(name_str, "'", "''");

        string sql = "DELETE FROM semantic_layer._definitions WHERE name = '" + safe_name + "'";

        duckdb_result result;
        auto state = duckdb_query(conn, sql.c_str(), &result);
        duckdb_destroy_result(&result);
        return (state == DuckDBSuccess) ? 0 : -1;
    }

    } // extern "C"
    ```

    IMPORTANT NOTES for the executor:
    - The `LogicalType::VARCHAR` in the pragma registration uses the DuckDB C++ API (not the Rust `LogicalTypeId::Varchar`). It is defined in `duckdb/common/types/logical_type.hpp` which is included transitively through `duckdb.hpp`. This should compile without extra includes.
    - `duckdb_query` and `duckdb_destroy_result` are C API functions declared in `duckdb.h` which is included via `duckdb.hpp`. They are available in the C++ context.
    - If `ExtensionLoader` constructor signature differs (check `duckdb_capi/duckdb/main/extension/extension_loader.hpp`), adjust accordingly. The form `ExtensionLoader loader(db_instance, "semantic_views")` matches the vendored header.
    - If `StringUtil::Replace` is not available (compile error), use a manual replace: `for (size_t pos; (pos = s.find("'")) != string::npos; s.replace(pos, 1, "''"));` wrapped in a helper lambda.
    - The `#include "duckdb/main/config.hpp"` and `#include "duckdb/parser/parser_extension.hpp"` from Phase 8 may be removed since Phase 11 is not in scope here; OR leave them (they compile cleanly and Phase 11 will need them). Leaving them is the safer choice.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test 2>&1 | tail -20</automated>
  </verify>
  <done>
    `cargo test` passes (C++ compilation gated by `--features extension` so the unit test suite uses default features and compiles fine). The shim.cpp contains `ExtensionLoader`, `PragmaFunction::PragmaCall`, `semantic_views_pragma_define`, `semantic_views_pragma_drop`. shim.h declares the two new functions. `grep -n "semantic_views_pragma_define" src/shim/shim.h` returns a match.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add Rust FFI declarations for the pragma functions in mod.rs</name>
  <files>src/shim/mod.rs</files>
  <action>
    Replace the empty Phase 8 placeholder in mod.rs with FFI declarations for the two new C functions.

    These functions are implemented in shim.cpp and statically linked into the cdylib at build time (via `cc::Build::new().file("src/shim/shim.cpp")` in build.rs). They are NOT exported symbols — they resolve at link time, not runtime. Therefore they do NOT need to be added to the macOS `.exp` or Linux `.map` symbol visibility files.

    Replace the entire contents of src/shim/mod.rs with:

    ```rust
    // src/shim/mod.rs
    // Phase 10: FFI declarations for C++ pragma functions.
    // These are implemented in src/shim/shim.cpp and statically linked
    // into the cdylib. They are NOT exported (internal link only).
    //
    // Two write paths exist (see RESEARCH.md):
    // 1. semantic_views_register_shim: registers pragma_query_t callbacks —
    //    transactional (DuckDB executes returned SQL in caller's transaction).
    //    Called at load time from lib.rs. Declared in lib.rs (not here).
    // 2. semantic_views_pragma_define / _drop: execute INSERT/DELETE directly
    //    on a separate stored connection — NOT in the user's transaction.
    //    Called from ddl/define.rs and ddl/drop.rs invoke.
    //
    // Only feature = "extension" compiles shim.cpp, so these declarations
    // are also gated. Unit tests (default/bundled feature) never call them.

    #[cfg(feature = "extension")]
    pub mod ffi {
        use libduckdb_sys::duckdb_connection;

        extern "C" {
            /// Write a semantic view definition to `semantic_layer._definitions`
            /// using a pre-stored separate connection.
            ///
            /// Returns 0 on success, -1 on error.
            ///
            /// # Safety
            /// `conn` must be a valid `duckdb_connection`. `name` and `json` must
            /// be valid null-terminated C strings for the duration of the call.
            pub fn semantic_views_pragma_define(
                conn: duckdb_connection,
                name: *const std::ffi::c_char,
                json: *const std::ffi::c_char,
            ) -> i32;

            /// Delete a semantic view definition from `semantic_layer._definitions`
            /// using a pre-stored separate connection.
            ///
            /// Returns 0 on success, -1 on error.
            ///
            /// # Safety
            /// `conn` must be a valid `duckdb_connection`. `name` must be a valid
            /// null-terminated C string for the duration of the call.
            pub fn semantic_views_pragma_drop(
                conn: duckdb_connection,
                name: *const std::ffi::c_char,
            ) -> i32;
        }
    }
    ```

    Note: The `extern "C"` block does NOT need `unsafe` keyword on Rust 2021 edition — the functions themselves are declared unsafe at their call sites. The `pub mod ffi` is gated on `#[cfg(feature = "extension")]` to match the build.rs gate that compiles shim.cpp. Under `cargo test` (default feature), this module is empty and no link-time resolution is needed.
  </action>
  <verify>
    <automated>cd /Users/paul/Documents/Dev/Personal/duckdb-semantic-views && cargo test 2>&1 | tail -10</automated>
  </verify>
  <done>
    `cargo test` passes. `src/shim/mod.rs` contains `semantic_views_pragma_define` and `semantic_views_pragma_drop` declarations gated under `#[cfg(feature = "extension")]`. `grep -n "semantic_views_pragma_define" src/shim/mod.rs` returns a match.
  </done>
</task>

</tasks>

<verification>
Run `cargo test` — all tests pass with no warnings.

Spot checks:
- `grep -n "ExtensionLoader\|PragmaFunction" src/shim/shim.cpp` — both present
- `grep -n "semantic_views_pragma_define\|semantic_views_pragma_drop" src/shim/shim.h` — both declared
- `grep -n "semantic_views_pragma_define\|semantic_views_pragma_drop" src/shim/mod.rs` — both present
- `grep -n "semantic_views_pragma_define" src/shim/shim.cpp` — C function implemented
</verification>

<success_criteria>
- `cargo test` passes clean with no warnings
- shim.cpp implements `semantic_views_register_shim` with real `ExtensionLoader` pragma registration
- shim.cpp exports `semantic_views_pragma_define` and `semantic_views_pragma_drop` C functions
- shim.h declares the two new C functions (C-compatible, no C++ in the header)
- mod.rs declares Rust FFI bindings for both functions under `#[cfg(feature = "extension")]`
- No symbol visibility changes needed in build.rs (internal link only)
</success_criteria>

<output>
After completion, create `.planning/phases/10-pragma-query-t-catalog-persistence/10-01-SUMMARY.md`
</output>
