{"record":{"id":"a5d945d6ec4ff5a4","repo":"tursodatabase/turso","slug":"statement-is-invalid","errorCode":null,"errorMessage":"statement is invalid","messagePattern":"statement is invalid","errorType":"exception","errorClass":"NullReferenceException","httpStatus":null,"severity":"error","filePath":"bindings/dotnet/src/Turso.Raw/Public/Handles/TursoStatementHandle.cs","lineNumber":21,"sourceCode":"namespace Turso.Raw.Public.Handles;\n\npublic class TursoStatementHandle() : SafeHandle(IntPtr.Zero, true)\n{\n    protected override bool ReleaseHandle()\n    {\n        _ = TursoInterop.StatementFinalize(handle, out var errorPtr);\n        if (errorPtr != IntPtr.Zero)\n            TursoInterop.FreeString(errorPtr);\n\n        TursoInterop.StatementDeinit(handle);\n        handle = IntPtr.Zero;\n        return true;\n    }\n\n    public void ThrowIfInvalid()\n    {\n        if (IsInvalid)\n            throw new NullReferenceException(\"statement is invalid\");\n    }\n\n    public static TursoStatementHandle FromPtr(IntPtr ptr)\n    {\n        var handle = new TursoStatementHandle();\n        handle.SetHandle(ptr);\n        return handle;\n    }\n\n    public override bool IsInvalid => handle == IntPtr.Zero;\n\n}\n","sourceCodeStart":3,"sourceCodeEnd":34,"githubUrl":"https://github.com/tursodatabase/turso/blob/244cde92a7df7f9b8b8b7a4075c35a12977e303e/bindings/dotnet/src/Turso.Raw/Public/Handles/TursoStatementHandle.cs#L3-L34","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar stmt = TursoBindings.PrepareStatement(db, sql);\nstmt.Dispose();\nTursoBindings.Read(stmt); // NullReferenceException: statement is invalid\n\n// after\nusing var stmt = TursoBindings.PrepareStatement(db, sql);\nwhile (TursoBindings.Read(stmt))\n{\n    var value = TursoBindings.GetValue(stmt, 0);\n}","handlingStrategy":"type-guard","validationCode":"// check before every use of the statement handle\nif (stmt.IsInvalid)\n    stmt = TursoBindings.PrepareStatement(db, sql);","typeGuard":"static bool IsUsable(TursoStatementHandle s) => s is not null && !s.IsInvalid;","tryCatchPattern":"try { TursoBindings.Read(stmt); }\ncatch (NullReferenceException) when (stmt.IsInvalid) { stmt = TursoBindings.PrepareStatement(db, sql); /* re-run */ }","preventionTips":["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."],"tags":["dotnet","native-interop","handle-lifecycle","use-after-dispose"],"backgroundTag":"use-after-dispose","analyzedSha":"244cde92a7df7f9b8b8b7a4075c35a12977e303e","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}