---
phase: 17.1-python-vtab-crash-investigation
plan: 02
subsystem: query
tags: [duckdb, vtab, crash-fix, zero-copy, type-safety, vector-reference]

# Dependency graph
requires:
  - phase: 17.1-python-vtab-crash-investigation
    plan: 01
    provides: Crash reproduction test script (13 tests, all passing) + diagnosis that CV-1 is most likely risk
provides:
  - Defensive runtime type validation before duckdb_vector_reference_vector
  - Comprehensive execution SQL wrapper casting ALL column types
  - TypeMismatch error variant for recoverable crash prevention
affects: [18-verification, future-type-additions]

# Tech tracking
tech-stack:
  added: []
  patterns: [always-cast-execution-sql, runtime-type-guard-before-zero-copy]

key-files:
  created: []
  modified:
    - src/query/table_function.rs
    - src/query/error.rs

key-decisions:
  - "Always wrap execution SQL with explicit type casts for all columns (not conditional) -- DuckDB optimizes away no-op casts"
  - "DECIMAL and LIST types pass through unmodified -- bare DECIMAL cast loses precision/scale, these types are handled via logical type metadata at bind time"
  - "Runtime type check returns recoverable error instead of allowing SIGABRT from duckdb_vector_reference_vector"

patterns-established:
  - "type_id_to_cast_sql: centralized type-to-SQL-name mapping for execution SQL wrapper"
  - "LogicalTypeOwned RAII guard for runtime type checks in func() hot path"
  - "QueryError::TypeMismatch: structured error for vector type mismatch diagnostics"

requirements-completed: [CRASH-03, CRASH-04, CRASH-05]

# Metrics
duration: 6min
completed: 2026-03-08
---

# Phase 17.1 Plan 02: Crash Fix Summary

**Defensive runtime type validation for zero-copy vector transfer with comprehensive execution SQL cast wrapper covering 20+ DuckDB types**

## Performance

- **Duration:** 6 min
- **Started:** 2026-03-08T01:18:38Z
- **Completed:** 2026-03-08T01:24:59Z
- **Tasks:** 2
- **Files modified:** 2

## Accomplishments
- Expanded `build_execution_sql` to always wrap with explicit casts for ALL column types (20+ types mapped), eliminating conditional wrapping that could miss type mismatches
- Added runtime type validation guard in `func()` before `duckdb_vector_reference_vector` -- compares source/destination vector type IDs and returns recoverable `QueryError::TypeMismatch` instead of hard SIGABRT crash
- Full test suite passes: 130 Rust tests, 3 SQL logic tests, 6 DuckLake CI tests, 13 Python crash reproduction tests

## Task Commits

Each task was committed atomically:

1. **Task 1: Fix crash root cause based on Plan 01 diagnosis** - `f618eac` (fix)
2. **Task 2: Verify crash fix and full regression suite** - `043024b` (fix)

## Files Created/Modified
- `src/query/table_function.rs` - Added `type_id_to_cast_sql()` mapping function (20+ types), modified `build_execution_sql()` to always wrap with casts, added runtime type check guard in `func()` zero-copy loop
- `src/query/error.rs` - Added `QueryError::TypeMismatch` variant with column index, name, source/dest type IDs

## Decisions Made
- **Always-wrap strategy:** The execution SQL wrapper now ALWAYS generates a `SELECT ... FROM (expanded) __sv_inner` with explicit casts for every column. Previously it was conditional (only wrapping when BIGINT/STRUCT/MAP/INVALID columns were present). DuckDB optimizes away no-op casts, so this has negligible overhead but maximum safety.
- **DECIMAL passthrough:** Bare `DECIMAL` casts default to `DECIMAL(18,3)` in DuckDB, which changes precision/scale and corrupts values. DECIMAL columns are passed through unmodified since their types are correctly handled via logical type metadata at bind time. Same for LIST (requires child type metadata).
- **Error over crash:** Runtime type mismatches now return a descriptive `QueryError::TypeMismatch` error rather than allowing DuckDB's internal assertion to fire as SIGABRT. This converts a hard crash into a user-visible error with diagnostic information (column index, name, type IDs).

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] DECIMAL cast corrupted values (10x multiplier)**
- **Found during:** Task 2 (sqllogictest verification)
- **Issue:** The initial implementation cast DECIMAL columns with bare `::DECIMAL`, which defaults to `DECIMAL(18,3)`. This changed the scale from 2 to 3, causing values like 350.00 to appear as 3500.00.
- **Fix:** Changed `type_id_to_cast_sql` to return `None` for DECIMAL (and LIST/unknown types), causing them to pass through unmodified in the execution SQL wrapper. Bind-time logical type metadata already handles these correctly.
- **Files modified:** `src/query/table_function.rs`
- **Verification:** `just test-all` passes (specifically `phase4_query.test` line 89 now returns correct DECIMAL values)
- **Committed in:** `043024b` (Task 2 commit)

---

**Total deviations:** 1 auto-fixed (1 bug fix)
**Impact on plan:** Essential fix discovered during verification. The DECIMAL passthrough is the correct approach since precision/scale metadata cannot be expressed in a bare SQL cast.

## Issues Encountered
- The sqllogictest `phase4_query.test` caught the DECIMAL cast regression immediately, demonstrating the value of the multi-layer test strategy (Rust tests pass but integration tests catch type-level issues).

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness
- Phase 17.1 crash investigation is complete: no crashes reproduced, defensive measures added
- Extension is ready for Phase 18 verification or continued development
- The runtime type guard and comprehensive cast wrapper provide safety net for future type additions (new DuckDB types, DuckLake/Iceberg type mappings)

---
*Phase: 17.1-python-vtab-crash-investigation*
*Completed: 2026-03-08*
