tursodatabase/turso · error · InvalidOperationException

Transaction-control SQL is not supported on a managed Sqlite

Error message

Transaction-control SQL is not supported on a managed SqliteConnection. Use SqliteConnection.BeginTransaction and SqliteTransaction instead.

What it means

GetManagedStatements rejects SQL containing transaction-control statements (BEGIN/COMMIT/ROLLBACK/SAVEPOINT etc.) executed as raw command text on a managed SqliteConnection, unless the statement is an allowed savepoint via CanExecuteSavepointStatement. Transactions must be managed through the ADO.NET API (BeginTransaction/SqliteTransaction) so the facade can track transaction state.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteCommand.cs:483

        try
        {
            AddManagedParameters(command, statement);
            return command;
        }
        catch
        {
            command.Dispose();
            throw;
        }
    }

    private IReadOnlyList<ManagedSqliteStatement> GetManagedStatements()
    {
        var statements = ManagedSqliteStatementParser.Parse(CommandText);
        if (statements.Any(statement => statement.IsTransactionControl && !CanExecuteSavepointStatement(statement)))
        {
            throw new InvalidOperationException(
                "Transaction-control SQL is not supported on a managed SqliteConnection. "
                + "Use SqliteConnection.BeginTransaction and SqliteTransaction instead.");
        }

        if (statements.Count > 1
            && Connection!.IsDirectRemote
            && !Connection.ManagedReadYourWrites
            && Transaction is null)
        {
            throw new NotSupportedException(
                "Multi-statement SqliteCommand execution requires Read Your Writes=True "
                + "or an explicit SqliteTransaction on a direct remote connection.");
        }

        return statements;
    }

    private bool CanExecuteSavepointStatement(ManagedSqliteStatement statement)

View on GitHub (pinned to 6c72522679)

Solutions

  1. Remove BEGIN/COMMIT/ROLLBACK from CommandText and use connection.BeginTransaction() with tx.Commit()/tx.Rollback() instead
  2. If savepoints are needed, use the savepoint forms permitted by CanExecuteSavepointStatement within a SqliteTransaction
  3. Split script files so transaction-control lines are handled by the API, not passed as SQL text

Example fix

// before
conn.Execute("BEGIN");
conn.Execute("INSERT INTO t VALUES (1)");
conn.Execute("COMMIT");
// after
using var tx = conn.BeginTransaction();
conn.Execute("INSERT INTO t VALUES (1)", transaction: tx);
tx.Commit();
Defensive patterns

Strategy: validation

Validate before calling

var ctl = new[]{"BEGIN","COMMIT","ROLLBACK","SAVEPOINT","RELEASE","END"};
if (commandText.Split(';').Any(s => ctl.Contains(s.Trim().Split(' ')[0], StringComparer.OrdinalIgnoreCase)))
    throw new InvalidOperationException("Use SqliteTransaction instead of transaction-control SQL.");

Try / catch

try { cmd.ExecuteNonQuery(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Transaction-control SQL"))
{
    using var tx = connection.BeginTransaction();
    cmd.Transaction = tx;
    cmd.ExecuteNonQuery();
    tx.Commit();
}

Prevention

When it happens

Trigger: Executing CommandText = "BEGIN" / "COMMIT" / "ROLLBACK" (or disallowed SAVEPOINT/RELEASE forms) via ExecuteNonQuery on a SqliteCommand over a managed connection.

Common situations: Porting code from raw sqlite3 APIs or other drivers where explicit BEGIN/COMMIT SQL is the norm; script files run verbatim containing transaction statements; dynamically generated SQL that prepends BEGIN.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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