tursodatabase/turso · error · SqliteException

SqliteCommand.ToSqliteException(ex, sql)

Error message

SqliteCommand.ToSqliteException(ex, sql)

What it means

When a savepoint-control statement issued by SqliteTransaction (Save, Rollback, Release) fails at the engine level, the underlying Turso.Raw.Public.TursoException is converted via SqliteCommand.ToSqliteException(ex, sql) into a SqliteException carrying the failing SQL text. This wrapper exists so callers using the Microsoft.Data.Sqlite-compatible surface see a SqliteException instead of a raw Turso exception. The original native error code and message are preserved; the SQL statement is attached for diagnosis.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteTransaction.cs:341

    {
        if (_managedTransaction is null)
        {
            Execute(sql);
            return;
        }

        try
        {
            using var command = new global::Turso.TursoCommand(_connection!.ManagedConnection)
            {
                CommandText = sql,
                Transaction = _managedTransaction,
            };
            command.ExecuteNonQuery();
        }
        catch (Turso.Raw.Public.TursoException ex)
        {
            throw SqliteCommand.ToSqliteException(ex, sql);
        }
    }

    private async Task ExecuteTransactionCommandAsync(string sql, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        if (_managedTransaction is null)
        {
            Execute(sql);
            return;
        }

        try
        {
            await using var command = new global::Turso.TursoCommand(_connection!.ManagedConnection)
            {
                CommandText = sql,
                Transaction = _managedTransaction,

View on GitHub (pinned to 6c72522679)

Solutions

  1. Inspect SqliteException.SqliteErrorCode and Message to identify the underlying native error (busy, misuse, I/O) and address that root cause.
  2. Verify the connection is open and the transaction has not already been completed before calling Save/Rollback/Release (ThrowIfCompleted guards the managed state, but the native side can still be stale).
  3. Ensure savepoint/release calls are properly nested and each name is released only once on a single thread.
  4. If the error is transient (busy/locked), retry the operation after the competing transaction completes or increase the busy timeout on the connection.

Example fix

// before: releasing a savepoint without checking state
transaction.Save();
doWork();
transaction.Release();

// after: guard state and handle transient failures
if (transaction.Connection == null) throw new InvalidOperationException("transaction completed");
transaction.Save();
doWork();
try { transaction.Release(); }
catch (SqliteException ex) when (ex.SqliteErrorCode == 5 /* SQLITE_BUSY */)
{
    // retry after the competing writer finishes
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (transaction == null || transaction.Connection == null || transaction.Connection.State != ConnectionState.Open)
    throw new InvalidOperationException("Cannot use a completed transaction's connection.");

Try / catch

try
{
    transaction.Release();
}
catch (SqliteException ex)
{
    // ex.SqliteErrorCode and ex.Message carry the wrapped native error and the failing SQL
    if (ex.SqliteErrorCode == 5) { /* SQLITE_BUSY: retry */ }
    else throw;
}

Prevention

When it happens

Trigger: Calling SqliteTransaction.Save(), SqliteTransaction.Rollback(), or SqliteTransaction.Release() on a transaction backed by a managed Turso transaction (_managedTransaction != null) where ExecuteNonQuery of the SAVEPOINT / ROLLBACK TO ... RELEASE statement raises TursoException - e.g. the connection dropped, the savepoint name is no longer valid, the transaction was already rolled back at the native layer, or an I/O error occurred while stepping the statement.

Common situations: Saving or releasing a savepoint after the underlying connection was closed or reset by the network/server; nesting savepoints incorrectly so a RELEASE references a name that was already released; concurrent access to the same connection from another thread causing the native transaction to be invalidated; database file locked or disk I/O failure during the statement step.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-09-06). Data as JSON: /api/errors/903be635224ec264. Report an issue: GitHub.