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

  1. Re-read the rowid immediately before opening the blob (SELECT ... WHERE ..., last_insert_rowid(), or INSERT ... RETURNING) instead of using a cached rowid
  2. Verify the row still exists with a cheap `SELECT 1 FROM <table> WHERE rowid = $id` before constructing SqliteBlob
  3. Close all SqliteBlob streams before deleting or reinserting rows in the same table
  4. 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

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


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/d582d46179f83356. Report an issue: GitHub.