e771a11a30
The Runtime.Deployment namespace shadowed the Deployment EF entity type for every file under ZB.MOM.WW.OtOpcUa.Runtime.Tests.*: C# name lookup walks the enclosing namespace chain and finds the namespace before a using-imported type, so 19 pre-existing call sites failed with CS0118. Caught only by a full-solution build - the Host and its own tests compiled fine.
71 lines
3.2 KiB
C#
71 lines
3.2 KiB
C#
using Microsoft.Data.Sqlite;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache;
|
|
|
|
/// <summary>
|
|
/// DDL for the node-local deployment-artifact cache: the chunked artifact table plus the
|
|
/// per-cluster current-deployment pointer.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Deliberately depends on nothing but <see cref="SqliteConnection"/> so it can be applied
|
|
/// to any connection — the host's <c>LocalDbSetup.OnReady</c> in production, and a bare
|
|
/// connection in a test — without dragging the DI graph along.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Why the artifact is chunked base64 TEXT and not one BLOB row.</b> Two independent
|
|
/// constraints force it. <c>RegisterReplicated</c> rejects BLOB columns outright; and the
|
|
/// replication engine batches by <i>row count</i> against gRPC's 4 MB default message cap,
|
|
/// so a single multi-megabyte artifact row would simply never be deliverable — at any
|
|
/// batch size. A 128 KiB raw chunk is ≈ 171 KB once base64-encoded, which keeps a
|
|
/// worst-case batch comfortably inside the cap.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Why no autoincrement PKs.</b> Convergence is last-writer-wins keyed on the primary
|
|
/// key. Two nodes independently allocating rowid 7 for different rows would silently
|
|
/// overwrite one another. Every key here is TEXT or a composite of caller-supplied values.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class DeploymentCacheSchema
|
|
{
|
|
/// <summary>Table holding the artifact bytes, split into base64 chunks.</summary>
|
|
public const string ArtifactsTable = "deployment_artifacts";
|
|
|
|
/// <summary>Table holding one current-deployment pointer per cluster.</summary>
|
|
public const string PointerTable = "deployment_pointer";
|
|
|
|
/// <summary>
|
|
/// Creates both cache tables if they do not already exist. Idempotent.
|
|
/// </summary>
|
|
/// <param name="connection">
|
|
/// An already-open connection. <c>ILocalDb.CreateConnection()</c> hands out open,
|
|
/// pragma-configured connections — do not call <c>Open()</c> on one.
|
|
/// </param>
|
|
public static void Apply(SqliteConnection connection)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(connection);
|
|
|
|
using var cmd = connection.CreateCommand();
|
|
cmd.CommandText = """
|
|
CREATE TABLE IF NOT EXISTS deployment_artifacts (
|
|
deployment_id TEXT NOT NULL,
|
|
chunk_index INTEGER NOT NULL,
|
|
cluster_id TEXT NOT NULL,
|
|
revision_hash TEXT NOT NULL,
|
|
chunk_count INTEGER NOT NULL,
|
|
chunk_base64 TEXT NOT NULL,
|
|
cached_at_utc TEXT NOT NULL,
|
|
PRIMARY KEY (deployment_id, chunk_index)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS deployment_pointer (
|
|
cluster_id TEXT NOT NULL PRIMARY KEY,
|
|
deployment_id TEXT NOT NULL,
|
|
revision_hash TEXT NOT NULL,
|
|
artifact_sha256 TEXT NOT NULL,
|
|
applied_at_utc TEXT NOT NULL
|
|
);
|
|
""";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|