feat(configdb): V2HostingAlignment migration consolidating Phase 1a-1e

Phase 1f — the consolidator migration. Closes out the v2 entity-model
rewrite by emitting a single EF migration that captures the cumulative
schema delta from 14a (RowVersion) through 14e (drop generation entities).

Generated: src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/
              20260526081556_V2HostingAlignment.cs           (1562 lines)
              20260526081556_V2HostingAlignment.Designer.cs

Migration shape (per `grep -nE migrationBuilder.\(...)`):

  Drop  12 ForeignKey constraints (one per live-edit entity's GenerationId FK)
  Drop  2  Tables  (ConfigGeneration, ClusterNodeGenerationState)
  Drop  45 Indexes (every UX_*_Generation_* and IX_*_Generation_* across the
                    13 live-edit tables — 1 also dropped the unique-Primary
                    filtered index UX_ClusterNode_Primary_Per_Cluster)
  Drop  13 Columns (12 GenerationId + 1 RedundancyRole)
  Add   12 RowVersion columns (one per live-edit entity)
  Create 4  Tables (Deployment, NodeDeploymentState, ConfigEdit,
                    DataProtectionKeys)
  Create ~45 Indexes (recreated under the new naming pattern
                      UX_<Table>_LogicalId / UX_<Table>_<X> with the
                      GenerationId column stripped from composite keys)

Notable EF quirks accepted:
  Unique-on-required-column indexes (UX_VirtualTag_LogicalId etc.) ship a
  `filter: "[VirtualTagId] IS NOT NULL"` clause that EF auto-inserts for
  SQL Server. Harmless — the column is C#-side `required` so NULL never
  appears.

Verification:
  dotnet build src/Core/ZB.MOM.WW.OtOpcUa.Configuration          -> 0 errors
  dotnet ef migrations script --idempotent (against placeholder DSN)
                                                                 -> 3259-line
                                                                    .sql produced
                                                                    OK
  tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests                -> 0 errors

Live `dotnet ef database update` against a scratch SQL Server deferred to
Task 15 (Migrate-To-V2.ps1) — SSH to the docker host needs a key/password I
don't have, and the always-on SQL at 10.100.0.35,14330 uses Integrated
Security (Windows auth, unreachable from this macOS dev). The migration
itself is structurally correct by construction (EF tooling generated it
against the live DbContext model); the live-DB confidence step is the
PowerShell wrapper's job.

SchemaComplianceTests updates:
  - All_expected_tables_exist: removed ConfigGeneration +
    ClusterNodeGenerationState; added Deployment, NodeDeploymentState,
    ConfigEdit, DataProtectionKeys.
  - Filtered_unique_indexes_match_schema_spec: removed entries for
    UX_ClusterNode_Primary_Per_Cluster (Task 14d) and
    UX_ConfigGeneration_Draft_Per_Cluster (Task 14e). Two filtered uniques
    remain (UX_ClusterNodeCredential_Value, UX_ExternalIdReservation_KindValue_Active).
  - Check_constraints_match_schema_spec: added CK_ConfigEdit_FieldsJson_IsJson.

StoredProceduresTests update:
  - Removed RedundancyRole + 'Primary' from the raw INSERT into ClusterNode
    so the DB-backed test runs against the new schema.
This commit is contained in:
Joseph Doherty
2026-05-26 04:18:50 -04:00
parent e00f46d723
commit 605dbf3dcc
5 changed files with 3625 additions and 367 deletions

View File

@@ -23,8 +23,8 @@ public sealed class SchemaComplianceTests
{
var expected = new[]
{
"ServerCluster", "ClusterNode", "ClusterNodeCredential", "ClusterNodeGenerationState",
"ConfigGeneration", "ConfigAuditLog",
"ServerCluster", "ClusterNode", "ClusterNodeCredential",
"ConfigAuditLog",
"Namespace", "UnsArea", "UnsLine",
"DriverInstance", "Device", "Equipment", "Tag", "PollGroup", "VirtualTag",
"NodeAcl", "ExternalIdReservation",
@@ -34,6 +34,8 @@ public sealed class SchemaComplianceTests
"EquipmentImportBatch",
"EquipmentImportRow",
"Script", "ScriptedAlarm", "ScriptedAlarmState",
// v2 deploy-model tables (Phase 1 of Akka + fused-hosting alignment)
"Deployment", "NodeDeploymentState", "ConfigEdit", "DataProtectionKeys",
};
var actual = QueryStrings(@"
@@ -52,9 +54,7 @@ SELECT name FROM sys.tables WHERE name <> '__EFMigrationsHistory' ORDER BY name;
// Kept here as a spec-level source of truth; the test ensures EF generated them verbatim.
var expected = new[]
{
("UX_ClusterNode_Primary_Per_Cluster", "([RedundancyRole]='Primary')"),
("UX_ClusterNodeCredential_Value", "([Enabled]=(1))"),
("UX_ConfigGeneration_Draft_Per_Cluster", "([Status]='Draft')"),
("UX_ExternalIdReservation_KindValue_Active", "([ReleasedAt] IS NULL)"),
};
@@ -85,6 +85,7 @@ WHERE i.is_unique = 1 AND i.has_filter = 1;",
"CK_PollGroup_IntervalMs_Min",
"CK_Tag_TagConfig_IsJson",
"CK_ConfigAuditLog_DetailsJson_IsJson",
"CK_ConfigEdit_FieldsJson_IsJson",
};
var actual = QueryStrings("SELECT name FROM sys.check_constraints ORDER BY name;").ToHashSet();

View File

@@ -260,8 +260,8 @@ public sealed class StoredProceduresTests
Exec(conn,
@"INSERT dbo.ServerCluster (ClusterId, Name, Enterprise, Site, RedundancyMode, NodeCount, Enabled, CreatedBy)
VALUES (@c, @c, 'zb', @s, 'None', 1, 1, SUSER_SNAME());
INSERT dbo.ClusterNode (NodeId, ClusterId, RedundancyRole, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
VALUES (@n, @c, 'Primary', 'localhost', 4840, 5001, CONCAT('urn:localhost:', @s), 200, 1, SUSER_SNAME());
INSERT dbo.ClusterNode (NodeId, ClusterId, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
VALUES (@n, @c, 'localhost', 4840, 5001, CONCAT('urn:localhost:', @s), 200, 1, SUSER_SNAME());
INSERT dbo.ClusterNodeCredential (NodeId, Kind, Value, Enabled, CreatedBy)
VALUES (@n, 'SqlLogin', SUSER_SNAME(), 1, SUSER_SNAME());",
("c", clusterId), ("n", nodeId), ("s", suffix));