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
- Open the connection in read-write mode (remove Mode=ReadOnly / read-only flag) for batches containing writes
- Split write statements out of read-only batches and run them on a writable connection
- 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
- Check connection.IsReadOnly before building write batches
- Maintain separate read-only and read-write connections and route by statement type
- Audit connection strings for Mode=ReadOnly in write code paths
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
- 8
- Turso batch execution is currently supported only for remote
- Writing is not supported.
- TursoBatchCommand only supports CommandType.Text.
- Batch command must be a TursoBatchCommand.
AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-09-06).
Data as JSON: /api/errors/6c1d56d383bfd665.
Report an issue: GitHub.