elsa-core/src/modules/Elsa.Persistence.VNext.Sqlite/SqliteSchemaVersionRunner.cs
Sipke Schoorstra c16995014c
Add Persistence vNext provider-neutral POC
Adds the Persistence vNext proof of concept, including provider-neutral schema/document abstractions, relational and document provider POCs, Elsa integration, runtime-defined entities, physicalization planning, workflow runtime evaluation, and focused tests.
2026-06-02 21:05:21 +02:00

90 lines
4.3 KiB
C#

using Elsa.Persistence.VNext.Contracts;
using Elsa.Persistence.VNext.Relational;
using Microsoft.Data.Sqlite;
namespace Elsa.Persistence.VNext.Sqlite;
public class SqliteSchemaVersionRunner : IPersistenceSchemaMigrator<SqliteConnection>
{
private const string HistoryTable = "ElsaSchemaVersions";
private readonly RelationalSchemaPlanner _planner;
private readonly SqliteSchemaSqlRenderer _renderer;
public SqliteSchemaVersionRunner() : this(new RelationalSchemaPlanner(new SqliteTypeMapper()), new SqliteSchemaSqlRenderer())
{
}
public SqliteSchemaVersionRunner(RelationalSchemaPlanner planner, SqliteSchemaSqlRenderer renderer)
{
_planner = planner;
_renderer = renderer;
}
public async Task<PersistenceSchemaMigrationResult> MigrateAsync(SqliteConnection connection, PersistenceSchema schema, CancellationToken cancellationToken = default)
{
if (connection.State != System.Data.ConnectionState.Open)
await connection.OpenAsync(cancellationToken);
await using var transaction = (SqliteTransaction)await connection.BeginTransactionAsync(cancellationToken);
await EnsureHistoryTableAsync(connection, transaction, cancellationToken);
var currentVersion = await GetCurrentVersionAsync(connection, transaction, schema.Name, cancellationToken);
if (currentVersion >= schema.Version)
{
await transaction.CommitAsync(cancellationToken);
return new PersistenceSchemaMigrationResult(schema.Name, currentVersion, schema.Version, false, []);
}
var statements = _renderer.Render(_planner.Plan(schema));
foreach (var statement in statements)
await ExecuteAsync(connection, transaction, statement, cancellationToken);
await RecordVersionAsync(connection, transaction, schema, cancellationToken);
await transaction.CommitAsync(cancellationToken);
return new PersistenceSchemaMigrationResult(schema.Name, currentVersion, schema.Version, true, statements);
}
private static Task EnsureHistoryTableAsync(SqliteConnection connection, SqliteTransaction transaction, CancellationToken cancellationToken)
{
const string sql = $"""
CREATE TABLE IF NOT EXISTS "{HistoryTable}" (
"SchemaName" TEXT NOT NULL,
"Version" INTEGER NOT NULL,
"AppliedAt" TEXT NOT NULL,
CONSTRAINT "PK_ElsaSchemaVersions" PRIMARY KEY ("SchemaName", "Version")
);
""";
return ExecuteAsync(connection, transaction, sql, cancellationToken);
}
private static async Task<int> GetCurrentVersionAsync(SqliteConnection connection, SqliteTransaction transaction, string schemaName, CancellationToken cancellationToken)
{
await using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = $"""SELECT COALESCE(MAX("Version"), 0) FROM "{HistoryTable}" WHERE "SchemaName" = $schemaName;""";
command.Parameters.AddWithValue("$schemaName", schemaName);
var result = await command.ExecuteScalarAsync(cancellationToken);
return Convert.ToInt32(result);
}
private static async Task RecordVersionAsync(SqliteConnection connection, SqliteTransaction transaction, PersistenceSchema schema, CancellationToken cancellationToken)
{
await using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = $"""INSERT INTO "{HistoryTable}" ("SchemaName", "Version", "AppliedAt") VALUES ($schemaName, $version, $appliedAt);""";
command.Parameters.AddWithValue("$schemaName", schema.Name);
command.Parameters.AddWithValue("$version", schema.Version);
command.Parameters.AddWithValue("$appliedAt", DateTimeOffset.UtcNow.ToString("O"));
await command.ExecuteNonQueryAsync(cancellationToken);
}
private static async Task ExecuteAsync(SqliteConnection connection, SqliteTransaction transaction, string sql, CancellationToken cancellationToken)
{
await using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = sql;
await command.ExecuteNonQueryAsync(cancellationToken);
}
}