---
phase: 60-race-guards-validation-hardening
plan: 01
type: retroactive-reconstruction
wave: 1
depends_on: [59-architectural-unification]
autonomous: false
requirements: [RACE-01, FFI-UTF8-01, PARSE-TIGHT-01, PARSER-OPT-PIN-01, ERR-MSG-WORKAROUND-01]
source_commits:
  - 2fa8ec2  # feat(v0.8.1): close milestone — race guards, FALLBACK error workaround, full test/doc coverage  (partial — race-guard + validation hardening portions)
  - f25fcda  # fix(v0.8.1): address PR #29 ultrareview — IF NOT EXISTS race + stale FFI doc refs
files_modified:
  - src/parse.rs
  - src/catalog.rs
  - cpp/src/shim.cpp
  - test/sql/peg_compat.test
  - test/sql/v080_transactional_ddl.test
  - test/integration/test_concurrent_ddl.py
note: Phases 60 and 61 share commit 2fa8ec2 — that single commit closed multiple workstreams together. The split into two phases is logical (race/validation vs LRU/RAII/tests/docs), not commit-aligned.
---

# Phase 60: Race Guards & Validation Hardening

> Reconstructed retroactively on 2026-05-05. Source: portions of commit `2fa8ec2` plus follow-up `f25fcda`.

## Goal

Close the race window introduced by the dual-connection rewrite path (catalog read on one connection, DML on the caller's connection); harden FFI input handling; emit actionable validation errors despite DuckDB's `FALLBACK_OVERRIDE` swallowing `DISPLAY_EXTENSION_ERROR`.

## Approach

### Race guards (DROP / ALTER non-`IF EXISTS`)

Phase 58's pre-DML existence check ran on the catalog connection (committed state). If a concurrent commit dropped the view between the check and the rewritten DELETE/UPDATE, the guard saw the row, generated DML, and the DML quietly affected zero rows — the user got a silent no-op when they expected `semantic view '<name>' does not exist`.

Fix: synthesise a snapshot-consistent existence check on the **caller's** connection as the first statement of the rewrite. DuckDB 1.10.502 rejects the cleaner CTE-with-DML form, so the actual shape is two statements:

```sql
SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM semantic_layer._definitions WHERE name = ?)
            THEN error('semantic view ''<name>'' was concurrently dropped')
            ELSE TRUE END;
DELETE/UPDATE FROM semantic_layer._definitions WHERE name = ? RETURNING ...;
```

`IF EXISTS` variants keep the silent-no-op contract by skipping the guard entirely.

### Validation error workaround (caret regression mitigation)

Phase 59 retired the fallback that previously caught `DISPLAY_EXTENSION_ERROR`. To get error text reaching the user at all, `sql_throwing` (in `src/parse.rs`) synthesises:

```sql
SELECT error('<msg>')::VARCHAR AS error
```

and returns it as `PARSE_SUCCESSFUL`. DuckDB executes the statement; `error()` raises an `Invalid Input Error` with the message. **Caret rendering is lost** — errors arrive as runtime exceptions, not parse-time `ParserException::SyntaxError`. This is an explicit known limitation (TECH-DEBT item 22) until Phase 62 lands.

`test/integration/test_caret_position.py` is relaxed to text-only assertions for this phase; the column-position assertions are restored in Phase 62.

### FFI UTF-8 hardening

`sv_parser_override_rust` previously used `from_utf8_unchecked` on the C++ string buffer — malformed input was undefined behaviour. Now uses checked `from_utf8`; failure cleanly defers to the default parser (return code 2 = "not ours").

### `parse_table_function_call` tightening

The internal helper that parses inline function calls (used by error-message synthesis and YAML-FILE detection) previously accepted `foo(,)`, `foo('a',)` (trailing comma), and `foo('a' 'b')` (missing comma between args). All three are now rejected at parse time.

### `ParserOptions` size assert

A static assert pins `sizeof(ParserOptions) == 32` against DuckDB 1.10.502. Silent layout drift previously surfaced as garbage parser errors at position 0; future DuckDB bumps now fail fast at compile time.

### `IF NOT EXISTS` race documentation (f25fcda)

`CREATE SEMANTIC VIEW IF NOT EXISTS` is silent-no-op only against rows visible in the caller's MVCC snapshot. Two parallel processes that each see the row absent both attempt the INSERT; the loser sees a `ConstraintException` PK violation at commit. Documented in CHANGELOG Known Limitations + TECH-DEBT item 23. The same shape plain `CREATE` produces under contention; multi-process bootstraps should catch and treat as success.

## Tests

- `test/sql/peg_compat.test` — re-issues `SET allow_parser_override_extension='FALLBACK'` after `disable_peg_parser` (which silently resets the override setting to default — TECH-DEBT item 21).
- `test/sql/v080_transactional_ddl.test` — concurrent-DROP race shape, validation error message reaches the user.
- `test/integration/test_concurrent_ddl.py` — multi-process / multi-thread CREATE under contention; verifies the documented PK-violation shape for IF NOT EXISTS race.
