5. SQLite describe stays dry-run-only; computed columns and parameter types are out of scope
Status
Accepted
Context
M10 added SqlBound.Sqlite as the second introspection provider, which is what forced
the provider-neutral IQueryDescriber abstraction out of the
SQL-Server-only design M7 shipped. Reaching SQL Server's describe fidelity with SQLite turned out
not to be possible with an equivalently safe mechanism, for two independent reasons:
- SQLite has no static parameter typing.
sp_describe_undeclared_parametershas no SQLite equivalent — there is no server-side type inference to query, because SQLite parameters simply don't carry a declared type at the engine level. sqlite3_column_decltypeonly resolves direct table column references. For a computed expression, function call, or aggregate (COUNT(*), arithmetic,CAST), it returns nothing — there is no declared type to report, because the SQL Server equivalent (a suggested type inferred from the expression) doesn't exist as a queryable property of a SQLite prepared statement.
For (2), a workaround exists in principle: sqlite3_prepare_v2 never executes the statement (a
DELETE compiles without deleting anything), so it would be possible to fall back to actually
running the statement once — binding every parameter to NULL — and read
sqlite3_column_type() on the resulting row for a computed column's runtime type. Two problems
rule this out for the current milestone:
- It is unsafe for
[SqlExecute]statements with aRETURNINGclause. Unlikesqlite3_prepare_v2,sqlite3_step()genuinely executes the statement. ADELETE ... RETURNINGwith a computedRETURNINGcolumn would delete real rows duringprepare— the exact hazard ADR 0001 designed the CLI-only split to avoid. - It is unreliable even when safe. A
SELECTwith a highly selectiveWHEREclause commonly returns zero rows when every parameter is bound toNULL, leaving no row to inspect and no fallback for the fallback.
Decision
SqliteQueryDescriber stays strictly dry-run, matching SQL Server's safety model exactly:
- A parameter's
ClrTypeTextisnullin the snapshot (SqlBound.Introspection.DescribedParameterand the JSON schema both allow this). The analyzer'sSQLB110(parameter type mismatch) has nothing to compare against and is skipped for that parameter;SQLB108/SQLB109still work off parameter names alone. - A column whose
sqlite3_column_decltypeis empty throwsSqlBoundDescribeExceptionwith a message naming the column and explaining why (it is a computed expression, not a missing feature). The query must be restructured to select only direct table columns, or described some other way, until a future milestone (if ever) adds an execute-based fallback gated onsqlite3_stmt_readonly().
Consequences
Positive
- No new unsafe code path:
prepareagainst SQLite can never execute a statement that mutates data, exactly likeprepareagainst SQL Server. - The limitation is explicit and fails loudly at
preparetime with an actionable message, rather than silently guessing a type that might be wrong.
Negative / trade-offs
COUNT(*)/scalar-aggregate queries and any query with a computedSELECTcolumn cannot be verified against SQLite today. This is a real gap relative to SQL Server's provider.SQLB110can never fire for a SQLite-sourced parameter — a parameter's C# type is effectively unverified for SQLite, trusted from the method's own declared signature.
Follow-up
- An execute-based fallback for computed columns, gated on
sqlite3_stmt_readonly()and documented as a distinct, less-safe opt-in, is a candidate for a future milestone if this limitation proves painful in practice.