tursodatabase/turso · error · InvalidOperationException

Transaction-control SQL is not supported in a SqliteBatch. U

Error message

Transaction-control SQL is not supported in a SqliteBatch. Use SqliteConnection.BeginTransaction and SqliteTransaction instead.

What it means

SqliteBatch does not allow BEGIN/COMMIT/ROLLBACK (transaction-control statements) inside batch command text; transactions must be managed through SqliteConnection.BeginTransaction and SqliteTransaction so the batch executor can coordinate them. BuildBatch rejects any parsed statement flagged IsTransactionControl.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteBatch.cs:306

        };
        var statements = new List<ManagedSqliteStatement>();
        var managedCommands = new List<global::Turso.TursoBatchCommand>();
        var mappings = new List<BatchCommandMapping>(_batchCommands.Count);
        try
        {
            foreach (var batchCommand in _batchCommands.Items)
            {
                using var command = new SqliteCommand(batchCommand.CommandText, connection, _transaction);
                foreach (SqliteParameter parameter in batchCommand.Parameters)
                    command.Parameters.Add(parameter);
                command.ValidateManagedParameterValues();

                var commandStatements = ManagedSqliteStatementParser.Parse(batchCommand.CommandText);
                if (commandStatements.Count == 0)
                    throw new InvalidOperationException("Batch command text must contain a SQL statement.");
                if (commandStatements.Any(static statement => statement.IsTransactionControl))
                {
                    throw new InvalidOperationException(
                        "Transaction-control SQL is not supported in a SqliteBatch. "
                        + "Use SqliteConnection.BeginTransaction and SqliteTransaction instead.");
                }
                if (connection.IsReadOnly
                    && commandStatements.Any(SqliteCommand.IsWriteStatement))
                {
                    throw new SqliteException(
                        Properties.Resources.SqliteNativeError(8, "attempt to write a readonly database"),
                        8);
                }

                var firstManagedIndex = managedCommands.Count;
                foreach (var statement in commandStatements)
                {
                    string sql;
                    if (preparing)
                    {
                        sql = SqliteCommand.RewriteFacadeStatement(statement.Sql, connection);

View on GitHub (pinned to 6c72522679)

Solutions

  1. Remove BEGIN/COMMIT/ROLLBACK statements from the batch text and wrap execution in connection.BeginTransaction()/tx.Commit() instead
  2. Assign batch.Transaction = (SqliteTransaction)tx so all statements run in that transaction
  3. Keep transaction control in application code, not in batch SQL

Example fix

// before
batch.Commands.Add(new SqliteBatchCommand { CommandText = "BEGIN; INSERT...; COMMIT;" });
// after
using var tx = connection.BeginTransaction();
batch.Transaction = tx;
batch.Commands.Add(new SqliteBatchCommand { CommandText = "INSERT..." });
await batch.ExecuteReaderAsync();
tx.Commit();
Defensive patterns

Strategy: validation

Validate before calling

var disallowed = new[]{"BEGIN","COMMIT","ROLLBACK","END","SAVEPOINT","RELEASE"};
bool hasTxControl = sql.Split(';').Any(s => disallowed.Contains(s.Trim().Split(' ')[0], StringComparer.OrdinalIgnoreCase));

Type guard

static bool IsTransactionControlSql(string sql) =>
    sql.Split(';').Any(s => new[]{"BEGIN","COMMIT","ROLLBACK","END","SAVEPOINT","RELEASE"}
        .Contains(s.Trim().Split(' ').FirstOrDefault() ?? "", StringComparer.OrdinalIgnoreCase));

Try / catch

try { await batch.ExecuteReaderAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Transaction-control")) { /* rewrite SQL without BEGIN/COMMIT, use SqliteTransaction */ }

Prevention

When it happens

Trigger: Including 'BEGIN', 'BEGIN TRANSACTION', 'COMMIT', 'ROLLBACK', 'END', 'SAVEPOINT'/'RELEASE' handling flagged as transaction control in any SqliteBatchCommand.CommandText, then executing the batch.

Common situations: Porting a Microsoft.Data.Sqlite batch script that wrapped work in BEGIN/COMMIT; sharing SQL scripts between psql-style tooling and the batch API; code that appends COMMIT after DML.

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/6c0139d7c6a74079. Report an issue: GitHub.