using Microsoft.Data.Sqlite;
namespace ClawdDotNet.Core.Storage;
///
/// Zentrale Stelle für den Zugang zur Instanz-Datenbank.
///
/// Die frühere Fassung öffnete pro Aufruf eine Verbindung ohne weitere Einstellungen —
/// ohne WAL und ohne Wartezeit bei Sperren. Sobald mehrere Agenten gleichzeitig
/// schreiben, quittiert SQLite das mit "database is locked". Das sah nach einer Grenze
/// von SQLite aus, war aber nur fehlende Konfiguration.
///
/// - WAL erlaubt beliebig viele Leser parallel zu einem Schreiber.
/// - busy_timeout lässt einen Schreiber kurz warten, statt sofort zu scheitern.
/// - Connection-Pooling vermeidet den Aufbau je Aufruf.
///
public sealed class SqliteStorage
{
private readonly string _connectionString;
private readonly SemaphoreSlim _writeGate = new(1, 1);
public string DatabasePath { get; }
public SqliteStorage(string databasePath)
{
DatabasePath = databasePath;
var directory = Path.GetDirectoryName(Path.GetFullPath(databasePath));
if (!string.IsNullOrEmpty(directory))
Directory.CreateDirectory(directory);
_connectionString = new SqliteConnectionStringBuilder
{
DataSource = databasePath,
Mode = SqliteOpenMode.ReadWriteCreate,
Cache = SqliteCacheMode.Shared,
Pooling = true,
DefaultTimeout = 30
}.ToString();
Initialize();
}
public SqliteConnection OpenConnection()
{
var connection = new SqliteConnection(_connectionString);
connection.Open();
ApplyPragmas(connection);
return connection;
}
public async Task OpenConnectionAsync(CancellationToken ct)
{
var connection = new SqliteConnection(_connectionString);
await connection.OpenAsync(ct);
ApplyPragmas(connection);
return connection;
}
///
/// Serialisiert Schreibvorgänge im Prozess. SQLite lässt ohnehin nur einen Schreiber
/// zu — die Warteschlange hier ist verlässlicher als das Zurückweisen durch die
/// Datenbank und macht Fehlerbilder reproduzierbar.
///
public async Task WriteAsync(Func> action, CancellationToken ct)
{
await _writeGate.WaitAsync(ct);
try
{
await using var connection = await OpenConnectionAsync(ct);
return await action(connection);
}
finally
{
_writeGate.Release();
}
}
public async Task WriteAsync(Func action, CancellationToken ct)
=> await WriteAsync