tursodatabase/turso · error · SqliteException
1
1
Error message
SQLite Error 1: 'no such rowid: {rowId}'. What it means
Thrown by SqliteBlob when the backing row cannot be found. GetBlobValue runs `SELECT <col> FROM <table> WHERE rowid = $rowid`; a NULL/DBNull result means no row with that rowid exists, so the binding raises SqliteException with native error code 1 and message "no such rowid: <rowId>". It mirrors Microsoft.Data.Sqlite, where opening or using a blob stream against a missing row fails.
Source
Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteBlob.cs:145
using var command = _connection.CreateCommand();
command.CommandText = "UPDATE " + QuoteIdentifier(_tableName) + " SET " + QuoteIdentifier(_columnName) + " = $value WHERE rowid = $rowid;";
command.Parameters.Add("$value", SqliteType.Blob).Value = GetStream().ToArray();
command.Parameters.Add("$rowid", SqliteType.Integer).Value = _rowId;
command.ExecuteNonQuery();
}
private static byte[] GetBlobValue(SqliteConnection connection, string tableName, string columnName, long rowId)
{
using var command = connection.CreateCommand();
command.CommandText = "SELECT " + QuoteIdentifier(columnName) + " FROM " + QuoteIdentifier(tableName) + " WHERE rowid = $rowid;";
command.Parameters.Add("$rowid", SqliteType.Integer).Value = rowId;
var value = command.ExecuteScalar();
return value switch
{
byte[] bytes => bytes,
string text => Encoding.UTF8.GetBytes(text),
null or DBNull => throw new SqliteException(Properties.Resources.SqliteNativeError(1, "no such rowid: " + rowId), 1),
_ => Encoding.UTF8.GetBytes(Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty)
};
}
private static void ValidateBuffer(byte[] buffer, int offset, int count)
{
ArgumentNullException.ThrowIfNull(buffer);
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), offset, message: null);
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), count, message: null);
if (offset > buffer.Length || count > buffer.Length - offset)
throw new ArgumentException(Properties.Resources.InvalidOffsetAndCount);
}
private static string QuoteIdentifier(string identifier)
=> "\"" + identifier.Replace("\"", "\"\"", StringComparison.Ordinal) + "\"";
}View on GitHub (pinned to bad083fafb)
Solutions
- Re-read the rowid immediately before opening the blob (SELECT ... WHERE ..., last_insert_rowid(), or INSERT ... RETURNING) instead of using a cached rowid
- Verify the row still exists with a cheap `SELECT 1 FROM <table> WHERE rowid = $id` before constructing SqliteBlob
- Close all SqliteBlob streams before deleting or reinserting rows in the same table
- Wrap blob usage in try/catch for SqliteException with SqliteErrorCode == 1 and treat it as a concurrent modification: re-fetch the rowid and retry once
Example fix
// before
var blob = new SqliteBlob(conn, "files", "data", rowId); // rowId captured earlier
var buf = new byte[blob.Length];
blob.Read(buf, 0, buf.Length);
// after
using var check = conn.CreateCommand();
check.CommandText = "SELECT 1 FROM files WHERE rowid = $id";
check.Parameters.Add("$id", SqliteType.Integer).Value = rowId;
if (check.ExecuteScalar() is null)
throw new InvalidOperationException($"row {rowId} no longer exists; re-fetch rowid");
using var blob = new SqliteBlob(conn, "files", "data", rowId); Defensive patterns
Strategy: try-catch
Validate before calling
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT 1 FROM " + table + " WHERE rowid = $id";
cmd.Parameters.Add("$id", SqliteType.Integer).Value = rowId;
bool rowExists = cmd.ExecuteScalar() is not null; Try / catch
try
{
using var blob = new SqliteBlob(conn, table, column, rowId);
// ... read/write
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 1 && ex.Message.Contains("no such rowid"))
{
// row vanished (deleted/rewritten concurrently): re-fetch rowid and retry once, or fail with context
rowId = FetchFreshRowid(conn, table, key);
} Prevention
- Never cache rowids across transactions; fetch the rowid in the same transaction that opens the blob
- Close all SqliteBlob streams before DELETE/INSERT that could reuse rowids in the target table
- Use INSERT ... RETURNING or last_insert_rowid() to obtain rowids atomically
- Treat SqliteErrorCode 1 with 'no such rowid' as a concurrent-modification signal, not a corrupt state
When it happens
Trigger: Constructing `new SqliteBlob(connection, tableName, columnName, rowId)` or reading/writing an already-open SqliteBlob after the row was DELETEd, after a delete+reinsert made the saved rowid stale, or from another connection that removed the row while the blob stream was open. Also triggered by passing a rowid of 0, a rowid from a different table, or a rowid against a WITHOUT ROWID table.
Common situations: Holding a long-lived SqliteBlob stream across DML that rewrites rows; caching rowids from a previous transaction or session; concurrent writers under WAL/MVCC removing rows; code ported from a path where the row was guaranteed to exist.
Related errors
- SQLite only supports CONCUR_READ_ONLY cursors
- Exception while binding blob value at position " + position
- createBlob not supported
- Cannot convert to binary stream: {type}
- invalid rowid in server response: {e}
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/d582d46179f83356.
Report an issue: GitHub.