tursodatabase/turso · error · SqliteException

8

8

Error message

SQLite Error 8: 'attempt to write a readonly database'.

What it means

When the connection is opened in read-only mode (connection.IsReadOnly), BuildBatch simulates SQLite's SQLITE_READONLY error (code 8) for any batch containing a write statement (INSERT/UPDATE/DELETE/DDL detected by SqliteCommand.IsWriteStatement), failing the batch before anything is sent.

Source

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

            {
                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);
                    }
                    else if (command.TryHandleFacadeStatement(statement.Sql, out sql))
                    {
                        continue;
                    }

                    using var managedCommand = command.CreateManagedCommand(statement, sql);

View on GitHub (pinned to 6c72522679)

Solutions

  1. Open the connection in read-write mode (remove Mode=ReadOnly / read-only flag) for batches containing writes
  2. Split write statements out of read-only batches and run them on a writable connection
  3. Check connection.IsReadOnly before building the batch and route accordingly

Example fix

// before
var conn = new SqliteConnection("...;Mode=ReadOnly");
await batch.ExecuteReaderAsync(); // SqliteException code 8
// after
var conn = new SqliteConnection("..."); // read-write
await batch.ExecuteReaderAsync();
Defensive patterns

Strategy: validation

Validate before calling

if (connection.IsReadOnly && SqliteCommand.IsWriteStatement(firstWriteStatement))
    throw new InvalidOperationException("Cannot run write statements on a read-only connection");

Type guard

static bool CanWriteOn(SqliteConnection c) => !c.IsReadOnly;

Try / catch

try { await batch.ExecuteReaderAsync(); }
catch (SqliteException ex) when (ex.SqliteErrorCode == 8) { /* reroute writes to a read-write connection */ }

Prevention

When it happens

Trigger: Executing a SqliteBatch containing INSERT/UPDATE/DELETE/CREATE/DROP against a connection opened with read-only mode (e.g. Mode=ReadOnly connection string or read-only flag on remote/replica connections).

Common situations: Read-only replicas serving analytics queries where a maintenance DML script is accidentally run; connection string misconfiguration; wrong connection instance injected for a write path.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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