---
phase: 13-type-mapping-and-property-based-tests-for-typed-column-dispatch
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/query/table_function.rs
autonomous: true
requirements: []

must_haves:
  truths:
    - "TIMESTAMP columns return non-NULL values (the silent-NULL bug is fixed)"
    - "BOOLEAN columns write correct 0/1 bytes into BOOLEAN output slots (the UB bug is fixed)"
    - "DECIMAL columns are declared as DECIMAL(width,scale) output type (not VARCHAR or DOUBLE)"
    - "LIST columns with scalar element types are declared as LIST(element_type) output (not VARCHAR)"
    - "All integer types (TINYINT through UBIGINT), FLOAT, UUID roundtrip through binary reads"
    - "ENUM columns decode to VARCHAR string values via duckdb_enum_dictionary_value()"
    - "build_varchar_cast_sql, parse_typed_from_str, and date_str_to_epoch_days are deleted from the codebase"
    - "cargo test passes (no regressions)"
  artifacts:
    - path: "src/query/table_function.rs"
      provides: "Binary-read dispatch pipeline replacing VARCHAR cast wrapper"
      contains: "read_typed_from_vector"
    - path: "src/query/table_function.rs"
      provides: "Extended TypedValue enum with Bool, I8, I16, U8, U16, U32, U64, F32, I128, List variants"
      contains: "TypedValue"
    - path: "src/query/table_function.rs"
      provides: "DECIMAL/LIST/ENUM-aware bind() with logical type metadata from LIMIT-0 query"
      contains: "duckdb_column_logical_type"
  key_links:
    - from: "func() in table_function.rs"
      to: "read_typed_from_vector()"
      via: "direct call per column per chunk row"
      pattern: "read_typed_from_vector"
    - from: "bind() in table_function.rs"
      to: "LogicalTypeHandle::decimal(width, scale)"
      via: "duckdb_column_logical_type + duckdb_decimal_width/scale"
      pattern: "decimal_width|decimal_scale"
    - from: "bind() in table_function.rs"
      to: "LogicalTypeHandle::list(child)"
      via: "duckdb_column_logical_type + duckdb_list_type_child_type"
      pattern: "list_type_child_type"
---

<objective>
Replace the VARCHAR-cast intermediary pipeline in `semantic_view()` with direct binary chunk reads per type, fix the three silent data-corruption bugs (TIMESTAMP all-NULL, BOOLEAN UB, DECIMAL as VARCHAR), and extend bind() to declare DECIMAL(width,scale) and LIST(element) output types using logical type metadata.

Purpose: Users are currently receiving corrupted output — TIMESTAMP columns are all-NULL, BOOLEAN is undefined behaviour, DECIMAL/LIST columns are strings unusable in arithmetic or list functions. This plan repairs all three bugs and extends the type dispatch to cover the full set of scalar types.

Output: Refactored `src/query/table_function.rs` with binary-read dispatch, extended `TypedValue` enum, deleted dead code, and updated bind() for DECIMAL/LIST/ENUM logical types. `cargo test` passes.
</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/13-type-mapping-and-property-based-tests-for-typed-column-dispatch/13-CONTEXT.md
@.planning/phases/13-type-mapping-and-property-based-tests-for-typed-column-dispatch/13-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from src/query/table_function.rs. -->

Current TypedValue enum (to be extended):
```rust
enum TypedValue {
    Null,
    I64(i64),    // BIGINT, UBIGINT, HUGEINT (truncated), TIMESTAMP
    I32(i32),    // INTEGER, DATE (days-since-epoch), SMALLINT, TINYINT
    F64(f64),    // DOUBLE, FLOAT upcast
    Str(String), // VARCHAR, all other types (fallback)
}
```

Target TypedValue enum (Phase 13):
```rust
enum TypedValue {
    Null,
    Bool(bool),           // BOOLEAN
    I8(i8),               // TINYINT
    I16(i16),             // SMALLINT
    I32(i32),             // INTEGER, DATE (days-since-epoch)
    I64(i64),             // BIGINT, TIMESTAMP, TIMESTAMP_S/MS/NS, TIMESTAMP_TZ, TIME
    U8(u8),               // UTINYINT, ENUM(u8 ordinal)
    U16(u16),             // USMALLINT
    U32(u32),             // UINTEGER
    U64(u64),             // UBIGINT
    F32(f32),             // FLOAT
    F64(f64),             // DOUBLE
    I128(i128),           // HUGEINT backing for DECIMAL
    Str(String),          // VARCHAR, ENUM decoded, UUID formatted, fallback
    List(Vec<TypedValue>), // LIST with scalar elements
}
```

Functions to DELETE:
- `build_varchar_cast_sql()`  — VARCHAR cast wrapper, no longer needed
- `parse_typed_from_str()`    — string parse dispatch, replaced by binary reads
- `date_str_to_epoch_days()`  — string-to-i32 date parse, replaced by direct i32 binary read

Function to keep (unchanged):
- `read_varchar_from_vector()` — reused for VARCHAR element columns in LIST child vectors and VARCHAR fallback

Function to keep and extend:
- `try_infer_schema()` — extend to also return logical type handles for DECIMAL/LIST/ENUM metadata

New function to add:
- `read_typed_from_vector(chunk, col_idx, row_idx, type_id, logical_type)` — dispatch by type_id, returns TypedValue

Binary layouts (confirmed from libduckdb-sys-1.4.4 bindgen):
| DuckDB Type | Binary Read | Rust type |
|-------------|-------------|-----------|
| BOOLEAN | u8 (0=false, 1=true) | `*data_ptr.cast::<u8>().add(row)` |
| TINYINT | i8 | `*data_ptr.cast::<i8>().add(row)` |
| SMALLINT | i16 | `*data_ptr.cast::<i16>().add(row)` |
| INTEGER | i32 | `*data_ptr.cast::<i32>().add(row)` |
| BIGINT | i64 | `*data_ptr.cast::<i64>().add(row)` |
| UTINYINT | u8 | `*data_ptr.cast::<u8>().add(row)` |
| USMALLINT | u16 | `*data_ptr.cast::<u16>().add(row)` |
| UINTEGER | u32 | `*data_ptr.cast::<u32>().add(row)` |
| UBIGINT | u64 | `*data_ptr.cast::<u64>().add(row)` |
| FLOAT | f32 | `*data_ptr.cast::<f32>().add(row)` |
| DOUBLE | f64 | `*data_ptr.cast::<f64>().add(row)` |
| DATE | i32 (days since epoch) | `*data_ptr.cast::<i32>().add(row)` |
| TIMESTAMP | i64 (µs since epoch) | `*data_ptr.cast::<i64>().add(row)` |
| TIMESTAMP_S | i64 (seconds) | `*data_ptr.cast::<i64>().add(row)` |
| TIMESTAMP_MS | i64 (milliseconds) | `*data_ptr.cast::<i64>().add(row)` |
| TIMESTAMP_NS | i64 (nanoseconds) | `*data_ptr.cast::<i64>().add(row)` |
| TIMESTAMP_TZ | i64 (µs) | `*data_ptr.cast::<i64>().add(row)` |
| TIME | i64 (µs since midnight) | `*data_ptr.cast::<i64>().add(row)` |
| HUGEINT | i128 (lower u64 + upper i64) | `*data_ptr.cast::<i128>().add(row)` (lower 64 bits = truncated to BIGINT output) |
| UHUGEINT | u128 | `*data_ptr.cast::<u128>().add(row)` (lower 64 bits = truncated to UBIGINT output) |
| UUID | 16 bytes stored as duckdb_uhugeint | write as UUID output slot |
| DECIMAL | backing type varies by precision | see Pitfall 1 below |
| ENUM | ordinal u8/u16/u32 by dict size | call duckdb_enum_dictionary_value() |
| LIST | parent duckdb_list_entry + child vector | see LIST pattern below |
| VARCHAR | duckdb_string_t | use existing read_varchar_from_vector() |

NULL check pattern (same as existing read_varchar_from_vector):
```rust
let validity = ffi::duckdb_vector_get_validity(vector);
if !validity.is_null() {
    let entry = *validity.add(row_idx / 64);
    if entry & (1u64 << (row_idx % 64)) == 0 {
        return TypedValue::Null;
    }
}
```

DECIMAL internal type dispatch (critical — do NOT assume all DECIMALs use HUGEINT):
```rust
// duckdb_decimal_internal_type returns DUCKDB_TYPE_SMALLINT for precision 1-4,
// DUCKDB_TYPE_INTEGER for 5-9, DUCKDB_TYPE_BIGINT for 10-18, DUCKDB_TYPE_HUGEINT for 19-38
let internal = ffi::duckdb_decimal_internal_type(logical_type);
```

LIST parent + child read:
```rust
let parent_vec = ffi::duckdb_data_chunk_get_vector(chunk, col_idx);
let entries_ptr = ffi::duckdb_vector_get_data(parent_vec);
let entry = *entries_ptr.cast::<ffi::duckdb_list_entry>().add(row_idx);
let child_vec = ffi::duckdb_list_vector_get_child(parent_vec);
// Read child elements at offsets [entry.offset .. entry.offset + entry.length]
```

Output declarations for bind():
```rust
// DECIMAL output:
bind.add_result_column(col_name, LogicalTypeHandle::decimal(width, scale));

// LIST output:
let child_type = LogicalTypeHandle::from(LogicalTypeId::from(child_type_id));
bind.add_result_column(col_name, LogicalTypeHandle::list(&child_type));

// ENUM output: declare as VARCHAR (user sees string values, not ordinals)
bind.add_result_column(col_name, LogicalTypeHandle::from(LogicalTypeId::Varchar));
```

Writing to output vectors:
```rust
// BOOLEAN:
let dst = out_vec.as_mut_slice_with_len::<u8>(n_rows);
dst[i] = if let TypedValue::Bool(b) = val { *b as u8 } else { 0 };

// LIST: use DataChunkHandle::list_vector(col_idx) → ListVector
// ListVector::set_entry(row_idx, child_offset, length)
// ListVector::set_child(&child_flat_slice)

// DECIMAL (i128 backing for HUGEINT-backed DECIMALs):
// Declare output as DECIMAL(width, scale) in bind()
// Write backing integer via as_mut_slice_with_len::<i128>(n_rows) for HUGEINT-backed
// or as_mut_slice_with_len::<i64> for BIGINT-backed, etc.
```

Anti-patterns:
- Do NOT keep build_varchar_cast_sql for any types — remove entirely
- Do NOT forget duckdb_destroy_logical_type() after using logical type handles
- Do NOT assume DECIMAL internal type is always HUGEINT
- Do NOT use row_idx * max_length to index LIST child — use entry.offset
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Extend TypedValue enum + add read_typed_from_vector() binary-read dispatch</name>
  <files>src/query/table_function.rs</files>
  <behavior>
    - TypedValue has new variants: Bool(bool), I8(i8), I16(i16), U8(u8), U16(u16), U32(u32), U64(u64), F32(f32), I128(i128), List(Vec&lt;TypedValue&gt;)
    - `read_typed_from_vector(chunk, col_idx, row_idx, type_id, logical_type_opt)` returns TypedValue::Null for NULL rows (validity mask check), TypedValue::Bool for BOOLEAN, TypedValue::I8 for TINYINT, TypedValue::I16 for SMALLINT, TypedValue::I32 for INTEGER and DATE, TypedValue::I64 for BIGINT/TIMESTAMP/TIMESTAMP_S/MS/NS/TIMESTAMP_TZ/TIME, TypedValue::U8 for UTINYINT, TypedValue::U16 for USMALLINT, TypedValue::U32 for UINTEGER, TypedValue::U64 for UBIGINT, TypedValue::F32 for FLOAT, TypedValue::F64 for DOUBLE, TypedValue::I128 (lower 64 bits) for HUGEINT/UHUGEINT output as BIGINT, TypedValue::Str for VARCHAR (calls read_varchar_from_vector), TypedValue::Str for ENUM (decodes via duckdb_enum_dictionary_value), TypedValue::List for LIST with scalar elements
    - DECIMAL: reads backing integer via duckdb_decimal_internal_type dispatch (not always i128), returns as TypedValue::I128 (or I64/I32/I16 matching backing type for correct bit count), output declaration in bind() as DECIMAL(width, scale)
    - NULL validity mask uses the same bit-array pattern as existing read_varchar_from_vector
    - Logical type handles are destroyed with duckdb_destroy_logical_type() after use
  </behavior>
  <action>
    Extend the `TypedValue` enum with new variants: `Bool(bool)`, `I8(i8)`, `I16(i16)`, `U8(u8)`, `U16(u16)`, `U32(u32)`, `U64(u64)`, `F32(f32)`, `I128(i128)`, `List(Vec<TypedValue>)`.

    Add a new `unsafe fn read_typed_from_vector(chunk: ffi::duckdb_data_chunk, col_idx: usize, row_idx: usize, type_id: u32, logical_type: ffi::duckdb_logical_type) -> TypedValue` function immediately after `read_varchar_from_vector`. This is the central dispatch point replacing `parse_typed_from_str`.

    The function structure:
    1. Get vector: `ffi::duckdb_data_chunk_get_vector(chunk, col_idx as ffi::idx_t)`
    2. NULL check using validity mask (copy pattern from read_varchar_from_vector, returning TypedValue::Null)
    3. Get data ptr: `ffi::duckdb_vector_get_data(vector)`
    4. Match on type_id constants (same `use ffi::DUCKDB_TYPE_*` pattern as existing parse_typed_from_str):
       - BOOLEAN: `*data_ptr.cast::<u8>().add(row_idx) != 0` → TypedValue::Bool
       - TINYINT: `*data_ptr.cast::<i8>().add(row_idx)` → TypedValue::I8
       - SMALLINT: `*data_ptr.cast::<i16>().add(row_idx)` → TypedValue::I16
       - INTEGER: `*data_ptr.cast::<i32>().add(row_idx)` → TypedValue::I32
       - BIGINT: `*data_ptr.cast::<i64>().add(row_idx)` → TypedValue::I64
       - UTINYINT: `*data_ptr.cast::<u8>().add(row_idx)` → TypedValue::U8
       - USMALLINT: `*data_ptr.cast::<u16>().add(row_idx)` → TypedValue::U16
       - UINTEGER: `*data_ptr.cast::<u32>().add(row_idx)` → TypedValue::U32
       - UBIGINT: `*data_ptr.cast::<u64>().add(row_idx)` → TypedValue::U64
       - FLOAT: `*data_ptr.cast::<f32>().add(row_idx)` → TypedValue::F32
       - DOUBLE: `*data_ptr.cast::<f64>().add(row_idx)` → TypedValue::F64
       - DATE: `*data_ptr.cast::<i32>().add(row_idx)` → TypedValue::I32 (days since epoch, same as current DATE handling but binary instead of string-parsed)
       - TIMESTAMP | TIMESTAMP_S | TIMESTAMP_MS | TIMESTAMP_NS | TIMESTAMP_TZ | TIME: `*data_ptr.cast::<i64>().add(row_idx)` → TypedValue::I64
       - HUGEINT: read 16-byte slot, lower u64 cast to i64 → TypedValue::I64 (output declared as BIGINT in type_from_duckdb_type_u32 — matches Phase 12 fix)
       - UHUGEINT: same approach → TypedValue::U64
       - DECIMAL: call `ffi::duckdb_decimal_internal_type(logical_type)` to get backing type (SMALLINT/INTEGER/BIGINT/HUGEINT), read appropriate width, return as TypedValue::I128 (for HUGEINT backing), or TypedValue::I64/I32/I16 for smaller backings. The output column is declared as DECIMAL(width,scale) in bind() so DuckDB interprets the integer with the correct scale.
       - UUID: read 16 bytes as duckdb_uhugeint, format as UUID string → TypedValue::Str (formatting: 8-4-4-4-12 hex groups; use format!("{uuid_str}") after bit manipulation)
       - ENUM: call `ffi::duckdb_enum_dictionary_value(logical_type, ordinal as ffi::idx_t)` — first get ordinal by reading u8/u16/u32 based on `ffi::duckdb_enum_dictionary_size(logical_type)`, then decode string, call `ffi::duckdb_free(dict_val_ptr)`, return TypedValue::Str
       - LIST with scalar element types: read `duckdb_list_entry { offset, length }` from parent vector, get child vector via `duckdb_list_vector_get_child(vector)`, determine child type_id from `logical_type` via `duckdb_list_type_child_type(logical_type)` then `duckdb_get_type_id()`, read child elements recursively using the scalar binary read pattern, return TypedValue::List(Vec<TypedValue>). For VARCHAR child elements use read_varchar_from_vector.
       - VARCHAR: call `read_varchar_from_vector` on same vector → TypedValue::Str (note: VARCHAR is type_id=17 not 0)
       - INVALID (0) | STRUCT | MAP | INTERVAL | BLOB | BIT | VARINT | fallback: call `read_varchar_from_vector` → TypedValue::Str (VARCHAR fallback path)

    Use RAII for logical_type handles: create a small inline guard or ensure duckdb_destroy_logical_type is called. A simple approach is `struct LogicalTypeOwned(ffi::duckdb_logical_type)` with `impl Drop { fn drop(&mut self) { unsafe { ffi::duckdb_destroy_logical_type(&mut self.0); } } }` at the top of the "Type mapping helpers" section.

    Add `#[allow(clippy::cast_possible_truncation)]` on read_typed_from_vector since we intentionally truncate HUGEINT to i64.

    Write unit tests in the `#[cfg(test)]` section of table_function.rs (or a new inline module) for at least:
    - `read_typed_from_vector` called on a real in-memory DuckDB result chunk returns correct TypedValue for BOOLEAN, INTEGER, BIGINT, DOUBLE, DATE, TIMESTAMP — use `Connection::open_in_memory()` + a SELECT query + `execute_sql_raw` + `duckdb_result_get_chunk`.
    - These unit tests use the `#[cfg(not(feature = "extension"))]` guard (only run with bundled feature, same pattern as catalog.rs tests).
  </action>
  <verify>
    <automated>cargo test --lib 2>&1 | tail -5</automated>
  </verify>
  <done>TypedValue has all new variants; read_typed_from_vector() exists, returns TypedValue::Null for NULLs and correct typed values for scalars; unit tests pass; cargo clippy -- -D warnings passes</done>
</task>

<task type="auto">
  <name>Task 2: Rewrite func() with binary reads + update bind() for DECIMAL/LIST/ENUM + delete dead code</name>
  <files>src/query/table_function.rs</files>
  <action>
    This task has three sub-goals: (A) rewrite func() to use binary reads, (B) extend bind() with logical type metadata for DECIMAL/LIST/ENUM, and (C) delete build_varchar_cast_sql, parse_typed_from_str, date_str_to_epoch_days.

    **A. Rewrite func()**

    Replace the `build_varchar_cast_sql` + string-collection loop + `parse_typed_from_str` pipeline with a binary-read pipeline:

    1. Execute the raw expanded_sql DIRECTLY (not the varchar_sql wrapper): `execute_sql_raw(state.conn, &bind_data.expanded_sql)`
    2. For each chunk, for each column, for each row: call `read_typed_from_vector(chunk, col_idx, row_idx, type_id, logical_type)` where logical_type is obtained from `ffi::duckdb_result_get_chunk` → `ffi::duckdb_data_chunk_get_vector` → `ffi::duckdb_vector_get_column_type` (or keep the existing approach of getting type_id from bind_data.column_type_ids and logical_type from `ffi::duckdb_column_logical_type(&mut result, col_idx)`).
    3. Collect TypedValue per column across all chunks (column-major, same as before): `Vec<Vec<TypedValue>>` indexed by col then row.
    4. Extend `write_typed_column()` to handle all new TypedValue variants:
       - TypedValue::Bool → `out_vec.as_mut_slice_with_len::<u8>(n_rows)`, write `*b as u8`
       - TypedValue::I8 → write to i32 slot (TINYINT output declared as TINYINT but FlatVector writes via i32 slot) — actually for TINYINT the output column is declared as LogicalTypeId::Tinyint and the slot width is 1 byte; use `as_mut_slice_with_len::<i8>()` to be correct. Check duckdb-rs FlatVector for i8 support; if not, cast via i32.
       - TypedValue::I16 → `as_mut_slice_with_len::<i16>(n_rows)` for SMALLINT
       - TypedValue::I32 → existing i32 path (INTEGER, DATE)
       - TypedValue::I64 → existing i64 path (BIGINT, TIMESTAMP)
       - TypedValue::U8 → `as_mut_slice_with_len::<u8>(n_rows)` for UTINYINT
       - TypedValue::U16 → `as_mut_slice_with_len::<u16>(n_rows)`
       - TypedValue::U32 → `as_mut_slice_with_len::<u32>(n_rows)`
       - TypedValue::U64 → `as_mut_slice_with_len::<u64>(n_rows)`
       - TypedValue::F32 → `as_mut_slice_with_len::<f32>(n_rows)` for FLOAT
       - TypedValue::F64 → existing f64 path (DOUBLE)
       - TypedValue::I128 → `as_mut_slice_with_len::<i128>(n_rows)` for HUGEINT-backed DECIMAL
       - TypedValue::Str → existing insert path for VARCHAR/fallback
       - TypedValue::List → use DataChunkHandle::list_vector(col_idx); build flat child slice + set_entry per row; the exact duckdb-rs API is `output.list_vector(col_idx)` → ListVector, then ListVector::set_entry(row, offset, length) and ListVector::set_child(&child_slice). For mixed-type lists, write each element type to child separately; for homogeneous lists (which covers all Phase 13 cases — ARRAY_AGG returns same-typed elements), write the flat child.
       - TypedValue::Null → `out_vec.set_null(i)` in all typed paths

    Get logical_type handles for each column at func() time:
    ```rust
    let logical_type = ffi::duckdb_column_logical_type(&mut result, col_idx as ffi::idx_t);
    // use for DECIMAL/LIST/ENUM dispatch in read_typed_from_vector
    ffi::duckdb_destroy_logical_type(&mut { logical_type });
    ```
    Or use LogicalTypeOwned RAII wrapper introduced in Task 1.

    **B. Extend bind() for DECIMAL/LIST/ENUM logical types**

    In `type_from_duckdb_type_u32()`, DECIMAL and LIST currently return VARCHAR. This must change:
    - Remove `DECIMAL | LIST` from the VARCHAR fallback arm in `type_from_duckdb_type_u32`.
    - The existing function signature `fn type_from_duckdb_type_u32(t: u32) -> LogicalTypeHandle` only has the type_id, not the logical type. Two options:
      Option A: Add a new function `fn type_from_logical_type(logical_type: ffi::duckdb_logical_type, type_id: u32) -> LogicalTypeHandle` that handles DECIMAL/LIST by querying metadata, and falls back to type_from_duckdb_type_u32 for all other types.
      Option B: Pass additional metadata from the bind LIMIT-0 result into type_from_duckdb_type_u32.
    Use Option A — add `unsafe fn declare_output_type(logical_type: ffi::duckdb_logical_type, type_id: u32) -> LogicalTypeHandle` that:
      - For DECIMAL: returns `LogicalTypeHandle::decimal(ffi::duckdb_decimal_width(logical_type), ffi::duckdb_decimal_scale(logical_type))`
      - For LIST: gets child logical type via `ffi::duckdb_list_type_child_type(logical_type)`, gets child type_id via `ffi::duckdb_get_type_id(child_logical_type)`, returns `LogicalTypeHandle::list(&LogicalTypeHandle::from(LogicalTypeId::from(child_type_id)))`, destroys child logical type handle
      - For ENUM: returns `LogicalTypeHandle::from(LogicalTypeId::Varchar)` (users see string values)
      - For all others: delegates to `type_from_duckdb_type_u32(type_id)`

    In `bind()`, the code path that calls `type_from_duckdb_type_u32(type_id)` in step 7 ("Declare typed output columns") must also have access to the logical type from the LIMIT-0 result. Currently `bind()` has a fallback path that runs LIMIT-0. For the primary path (DDL-time inference stored as u32 type_ids), re-run LIMIT-0 at bind time ONLY for DECIMAL/LIST/ENUM columns:
    - After computing column_names/type_ids (step 6), check if any column has type_id in {DECIMAL, LIST, ENUM}.
    - If yes, run a LIMIT-0 query on the expanded SQL to get logical types: call `execute_sql_raw(state.conn, &format!("{} LIMIT 0", &expanded_sql))`, then for each such column call `duckdb_column_logical_type` to get the handle, use it in `declare_output_type()`, destroy it.
    - If no DECIMAL/LIST/ENUM columns, skip the LIMIT-0 at bind time (keep existing optimization).
    - This avoids model schema changes while correctly declaring output types.

    Also update `type_from_duckdb_type_u32()` to add BOOLEAN to the native-type arm (it should return `LogicalTypeId::Boolean` not fall through to `LogicalTypeHandle::from(LogicalTypeId::from(t))`). Actually the existing `_ => LogicalTypeHandle::from(LogicalTypeId::from(t))` arm already handles BOOLEAN correctly since BOOLEAN type_id (1) maps to LogicalTypeId::Boolean. Verify this is correct.

    **C. Delete dead code**

    Remove the following functions entirely:
    - `build_varchar_cast_sql()`
    - `parse_typed_from_str()`
    - `date_str_to_epoch_days()`

    Also remove the `use crate::expand::quote_ident;` import that was only used by `build_varchar_cast_sql` (if it is now unused).

    After deletion, ensure `cargo build --features extension` and `cargo test` both compile cleanly. The old `TypedValue` variants (I64, I32, F64, Str) are still present as the new enum is additive — existing write_typed_column arms may need updating to handle all old variants that now have new siblings.

    Run `cargo test` and `cargo clippy -- -D warnings` as the verification command.
  </action>
  <verify>
    <automated>cargo test 2>&1 | tail -10 && cargo clippy -- -D warnings 2>&1 | tail -5</automated>
  </verify>
  <done>func() uses binary reads directly; build_varchar_cast_sql/parse_typed_from_str/date_str_to_epoch_days are gone from the file; bind() declares DECIMAL(width,scale) and LIST(child_type) output types; cargo test passes (all existing tests pass); cargo clippy -- -D warnings passes</done>
</task>

</tasks>

<verification>
After both tasks:
1. `grep -n "build_varchar_cast_sql\|parse_typed_from_str\|date_str_to_epoch_days" src/query/table_function.rs` — must return 0 matches
2. `grep -n "read_typed_from_vector" src/query/table_function.rs` — must return multiple matches (definition + calls in func)
3. `grep -n "duckdb_decimal_width\|duckdb_decimal_scale" src/query/table_function.rs` — must return matches in bind() path
4. `cargo test` — all tests pass
5. `cargo clippy -- -D warnings` — 0 warnings
</verification>

<success_criteria>
1. `cargo test` passes with no regressions
2. `grep "build_varchar_cast_sql\|parse_typed_from_str\|date_str_to_epoch_days" src/query/table_function.rs` returns nothing
3. TypedValue enum contains Bool, I8, I16, U8, U16, U32, U64, F32, I128, List variants
4. func() calls read_typed_from_vector() instead of parse_typed_from_str
5. bind() calls declare_output_type() which uses duckdb_decimal_width/scale for DECIMAL columns
6. cargo clippy -- -D warnings passes
</success_criteria>

<output>
After completion, create `.planning/phases/13-type-mapping-and-property-based-tests-for-typed-column-dispatch/13-01-SUMMARY.md`
</output>
