tursodatabase/turso · error · NullReferenceException
statement is invalid
Error message
statement is invalid
What it means
TursoStatementHandle guards a single native statement pointer. ThrowIfInvalid throws NullReferenceException when that pointer is zero — i.e., after ReleaseHandle/StatementFinalize ran or the handle was never bound. Every statement API (BindParameter, Read, GetValue, GetName, RowsAffected, GetParameterCount) calls it first.
Source
Thrown at bindings/dotnet/src/Turso.Raw/Public/Handles/TursoStatementHandle.cs:21
namespace Turso.Raw.Public.Handles;
public class TursoStatementHandle() : SafeHandle(IntPtr.Zero, true)
{
protected override bool ReleaseHandle()
{
_ = TursoInterop.StatementFinalize(handle, out var errorPtr);
if (errorPtr != IntPtr.Zero)
TursoInterop.FreeString(errorPtr);
TursoInterop.StatementDeinit(handle);
handle = IntPtr.Zero;
return true;
}
public void ThrowIfInvalid()
{
if (IsInvalid)
throw new NullReferenceException("statement is invalid");
}
public static TursoStatementHandle FromPtr(IntPtr ptr)
{
var handle = new TursoStatementHandle();
handle.SetHandle(ptr);
return handle;
}
public override bool IsInvalid => handle == IntPtr.Zero;
}
View on GitHub (pinned to 244cde92a7)
Solutions
- Tie statement lifetime to the connection: dispose statements before the database handle closes (e.g. nested usings).
- Check statement.IsInvalid before reuse, and re-prepare from a valid database handle when true.
- Never share one statement across concurrent operations; prepare per scope.
- Return values, not statements, from helpers so callers cannot hold dead handles.
Example fix
// before
var stmt = TursoBindings.PrepareStatement(db, sql);
stmt.Dispose();
TursoBindings.Read(stmt); // NullReferenceException: statement is invalid
// after
using var stmt = TursoBindings.PrepareStatement(db, sql);
while (TursoBindings.Read(stmt))
{
var value = TursoBindings.GetValue(stmt, 0);
} Defensive patterns
Strategy: type-guard
Validate before calling
// check before every use of the statement handle
if (stmt.IsInvalid)
stmt = TursoBindings.PrepareStatement(db, sql); Type guard
static bool IsUsable(TursoStatementHandle s) => s is not null && !s.IsInvalid;
Try / catch
try { TursoBindings.Read(stmt); }
catch (NullReferenceException) when (stmt.IsInvalid) { stmt = TursoBindings.PrepareStatement(db, sql); /* re-run */ } Prevention
- Scope statements with `using` inside the connection's scope.
- Dispose statements before closing the database handle.
- Do not share statements across threads or tasks; prepare per scope.
When it happens
Trigger: Calling any statement API after finalizing/disposing the statement; stepping or binding a statement that was prepared on a database handle that has since been closed; double-dispose where the second user still holds the reference.
Common situations: Statements cached beyond the owning connection's lifetime; concurrent code paths where one thread finalizes while another reads; helper wrappers that dispose statements eagerly inside a using block that callers escape via captured references.
Related errors
- database is invalid
- Parameter index {index} is too large.
- Unable to read native value bytes.
- Turso native call failed with status {status}.
- Turso sync ${operation} failed: ${message}
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/a5d945d6ec4ff5a4.
Report an issue: GitHub.