LocalDb adoption Phase 1 + 2: consolidate the site database, delete the bespoke replicators #23
@@ -108,9 +108,9 @@
|
||||
<PackageVersion Include="ZB.MOM.WW.Secrets.Abstractions" Version="0.2.3" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Secrets.Ui" Version="0.2.3" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Secrets.Replicator.SqlServer" Version="0.2.3" />
|
||||
<PackageVersion Include="ZB.MOM.WW.LocalDb" Version="0.1.0" />
|
||||
<PackageVersion Include="ZB.MOM.WW.LocalDb.Replication" Version="0.1.0" />
|
||||
<PackageVersion Include="ZB.MOM.WW.LocalDb.Contracts" Version="0.1.0" />
|
||||
<PackageVersion Include="ZB.MOM.WW.LocalDb" Version="0.1.1" />
|
||||
<PackageVersion Include="ZB.MOM.WW.LocalDb.Replication" Version="0.1.1" />
|
||||
<PackageVersion Include="ZB.MOM.WW.LocalDb.Contracts" Version="0.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Host;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the directory holding the consolidated site database (<c>LocalDb:Path</c>)
|
||||
/// exists before LocalDb opens the file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// SQLite creates the database file on demand but never its parent directory, and
|
||||
/// <c>SqliteLocalDb</c> opens the file eagerly in its constructor — so a missing directory
|
||||
/// is a hard boot failure ("SQLite Error 14: unable to open database file"), not a degraded
|
||||
/// start. Neither the LocalDb library nor its options validation does this.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The guarantee previously lived in <c>StoreAndForwardStorage.EnsureDatabaseDirectoryExists</c>,
|
||||
/// which created the directory for the store's own SQLite file. Once that file was folded
|
||||
/// into the consolidated database the store stopped owning a path, so the guarantee had to
|
||||
/// be re-established at the layer that now configures the path: the site host.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It matters because the default site configuration uses the <i>relative</i> path
|
||||
/// <c>./data/site-localdb.db</c>. The docker rig never noticed the gap only because its
|
||||
/// volume mount happens to create <c>/app/data</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class SiteLocalDbDirectory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the directory containing <c>LocalDb:Path</c> when it does not already exist.
|
||||
/// No-op when the key is unset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Best-effort by design: if the directory cannot be created (permissions, read-only
|
||||
/// mount) this stays silent and lets LocalDb raise the real error, which names the
|
||||
/// actual path and is a better diagnostic than anything thrown from here.
|
||||
/// </remarks>
|
||||
/// <param name="config">Configuration carrying <c>LocalDb:Path</c>.</param>
|
||||
public static void Ensure(IConfiguration config)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(config);
|
||||
|
||||
var path = config["LocalDb:Path"];
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(Path.GetFullPath(path));
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or ArgumentException)
|
||||
{
|
||||
// Deliberately swallowed — see the remarks above.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,17 +82,13 @@ public static class SiteServiceRegistration
|
||||
//
|
||||
// Design: scadaproj docs/plans/2026-07-19-scadabridge-localdb-design.md
|
||||
//
|
||||
// Create the parent directory FIRST. SQLite creates the database file on demand
|
||||
// but not its directory, and neither the LocalDb library nor its options
|
||||
// validation does this — SqliteLocalDb's constructor opens the file eagerly, so a
|
||||
// missing directory is a hard boot failure ("SQLite Error 14: unable to open
|
||||
// database file"), not a degraded start. The default site path is the relative
|
||||
// "./data/site-localdb.db", so this bites any site node whose data directory has
|
||||
// not been pre-created; the docker rig only escapes it because the volume mount
|
||||
// creates /app/data. StoreAndForwardStorage used to do this for its own file
|
||||
// (EnsureDatabaseDirectoryExists) and that guarantee has to keep existing
|
||||
// somewhere now that LocalDb owns the file.
|
||||
SiteLocalDbDirectory.Ensure(config);
|
||||
// The parent directory of LocalDb:Path is created by the library as of 0.1.1.
|
||||
// ScadaBridge carried a SiteLocalDbDirectory shim for it during Phase 2, because
|
||||
// SQLite creates the database file on demand but not its directory and
|
||||
// SqliteLocalDb opens the file eagerly — a missing directory was a hard boot
|
||||
// failure, and the default site path is the relative "./data/site-localdb.db".
|
||||
// The shim is gone; SiteLocalDbDirectoryTests still pins the outcome here, since
|
||||
// what this host needs is the guarantee, not any particular owner of it.
|
||||
services.AddZbLocalDb(config, db => SiteLocalDbSetup.OnReady(db, config));
|
||||
|
||||
// The replication engine, likewise unconditional but INERT by default: with no
|
||||
|
||||
@@ -10,12 +10,14 @@ namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This guarantee used to live in <c>StoreAndForwardStorage.EnsureDatabaseDirectoryExists</c>,
|
||||
/// which created the parent directory for its own SQLite file. LocalDb Phase 2 moved that
|
||||
/// file into the consolidated database — but the LocalDb library does <b>not</b> create the
|
||||
/// directory, and <c>SqliteLocalDb</c> opens the file eagerly in its constructor, so a
|
||||
/// missing directory is a hard boot failure ("SQLite Error 14: unable to open database
|
||||
/// file") rather than a degraded start.
|
||||
/// The guarantee has now had three owners: <c>StoreAndForwardStorage</c> created the
|
||||
/// directory for its own SQLite file, LocalDb Phase 2 folded that file into the
|
||||
/// consolidated database and the Host took it over as a <c>SiteLocalDbDirectory</c> shim,
|
||||
/// and LocalDb 0.1.1 took it into the library itself. This test survived all three moves
|
||||
/// unchanged in intent, because what a site node needs is the OUTCOME — resolving
|
||||
/// <c>ILocalDb</c> from the site registration must not fail on a fresh machine — not any
|
||||
/// particular implementer of it. It is deliberately written against the registration path
|
||||
/// rather than the mechanism, so a future move costs nothing here.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is not hypothetical: the default site configuration points at the <i>relative</i>
|
||||
@@ -44,9 +46,9 @@ public class SiteLocalDbDirectoryTests
|
||||
.Build();
|
||||
|
||||
// The registration path under test. Resolving ILocalDb is what actually opens
|
||||
// the file, so this fails with SQLite Error 14 if the directory step regresses.
|
||||
// the file, so this fails with SQLite Error 14 if the directory step regresses
|
||||
// — wherever it lives.
|
||||
var services = new ServiceCollection();
|
||||
SiteLocalDbDirectory.Ensure(config);
|
||||
services.AddZbLocalDb(config);
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
|
||||
@@ -642,12 +642,12 @@ public class StoreAndForwardStorageTests : IAsyncLifetime, IDisposable
|
||||
// * WAL is genuinely LocalDb's — it sets the journal mode on the database it owns.
|
||||
// The test below still asserts it, now against the LocalDb-backed store.
|
||||
//
|
||||
// * Directory creation is NOT LocalDb's. The library does not create the parent
|
||||
// directory and opens the file eagerly, so a missing directory is a hard boot
|
||||
// failure. The guarantee had to be re-established explicitly in the Host
|
||||
// (SiteServiceRegistration.EnsureLocalDbDirectoryExists), and it is pinned there
|
||||
// by SiteLocalDbDirectoryTests — the layer that now owns it. Asserting it here
|
||||
// would be asserting a guarantee this class no longer provides.
|
||||
// * Directory creation was NOT LocalDb's when Phase 2 landed: the library did not
|
||||
// create the parent directory yet opened the file eagerly, so a missing directory
|
||||
// was a hard boot failure, and the Host re-established the guarantee with a
|
||||
// SiteLocalDbDirectory shim. LocalDb 0.1.1 took the guarantee back into the
|
||||
// library, so the shim is gone — but either way it is not this class's to assert,
|
||||
// and SiteLocalDbDirectoryTests pins the outcome for the site host.
|
||||
|
||||
[Fact]
|
||||
public async Task Initialize_LeavesTheDatabaseInWalJournalMode()
|
||||
|
||||
Reference in New Issue
Block a user