diff --git a/docs/plans/2026-05-23-audit-source-node.md b/docs/plans/2026-05-23-audit-source-node.md index 4c6e278..bd21313 100644 --- a/docs/plans/2026-05-23-audit-source-node.md +++ b/docs/plans/2026-05-23-audit-source-node.md @@ -469,13 +469,22 @@ migrationBuilder.AddColumn( maxLength: 64, nullable: true); -migrationBuilder.CreateIndex( - name: "IX_AuditLog_Node_Occurred", - table: "AuditLog", - columns: new[] { "SourceNode", "OccurredAtUtc" }); +// IMPORTANT: AuditLog is partitioned on ps_AuditLog_Month(OccurredAtUtc). +// `migrationBuilder.CreateIndex(...)` lands the index on [PRIMARY], which breaks +// `ALTER TABLE … SWITCH PARTITION` (the purge mechanism). Match the pattern used +// by the other `IX_AuditLog_*` indexes (see 20260520142214_AddAuditLogTable.cs +// and 20260521184044_AddAuditLogExecutionId.cs) — raw SQL with the partition +// scheme spelled out. Keep the fluent `HasIndex(...).HasDatabaseName(...)` in +// the EF configuration so the model snapshot stays in sync. +migrationBuilder.Sql(@" +CREATE NONCLUSTERED INDEX IX_AuditLog_Node_Occurred +ON dbo.AuditLog (SourceNode, OccurredAtUtc) +ON ps_AuditLog_Month(OccurredAtUtc);"); ``` -`Down()` drops the index then the column. +`Down()` drops the index (`IF EXISTS DROP INDEX … ON dbo.AuditLog`, raw SQL) then the column. + +You will *also* need to extend `AuditLogRepository.SwitchOutPartitionAsync`'s staging-table CREATE to include `SourceNode varchar(64) NULL` in the final ordinal position. `SWITCH PARTITION` rejects schema mismatches between live and staging — without this, the PartitionPurge integration tests fail. **Step 3: Update EF configuration** diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs index 56bff18..e092b65 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs @@ -76,7 +76,8 @@ public class AuditLogEntityTypeConfiguration : IEntityTypeConfiguration e.SourceNode) .HasColumnType("varchar(64)") - .HasMaxLength(64); + .HasMaxLength(64) + .IsUnicode(false); // Bounded unicode message column. builder.Property(e => e.ErrorMessage) diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs index c8872a3..6c2c693 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs @@ -54,7 +54,8 @@ public class NotificationOutboxConfiguration : IEntityTypeConfiguration n.SourceNode) .HasColumnType("varchar(64)") - .HasMaxLength(64); + .HasMaxLength(64) + .IsUnicode(false); // OriginExecutionId (Audit Log #23): nullable uniqueidentifier carried from the // site so the dispatcher can echo it onto NotifyDeliver audit rows. No index — diff --git a/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs b/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs index 206ad6e..a0203ab 100644 --- a/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs +++ b/src/ScadaLink.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs @@ -66,7 +66,8 @@ public class SiteCallEntityTypeConfiguration : IEntityTypeConfiguration s.SourceNode) .HasColumnType("varchar(64)") - .HasMaxLength(64); + .HasMaxLength(64) + .IsUnicode(false); // Indexes — names locked for reconciliation/migration discoverability. // Source_Created backs "calls from this site" (Central UI Site Calls page, diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.Designer.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.Designer.cs index 860a383..af73714 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.Designer.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.Designer.cs @@ -118,6 +118,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.cs index c6a7fd5..0499ece 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201754_AddAuditLogSourceNode.cs @@ -30,6 +30,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations name: "SourceNode", table: "AuditLog", type: "varchar(64)", + unicode: false, maxLength: 64, nullable: true); diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.Designer.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.Designer.cs index 0772cf8..d4ea3b8 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.Designer.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.Designer.cs @@ -118,6 +118,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") @@ -825,6 +826,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.cs index 722329b..2510ff6 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523201950_AddNotificationSourceNode.cs @@ -26,6 +26,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations name: "SourceNode", table: "Notifications", type: "varchar(64)", + unicode: false, maxLength: 64, nullable: true); } diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.Designer.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.Designer.cs index 4a5bbae..fe3c141 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.Designer.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.Designer.cs @@ -118,6 +118,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") @@ -267,6 +268,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceSite") @@ -829,6 +831,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.cs index 8db59c3..9dd92c2 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/20260523202131_AddSiteCallSourceNode.cs @@ -27,6 +27,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations name: "SourceNode", table: "SiteCalls", type: "varchar(64)", + unicode: false, maxLength: 64, nullable: true); } diff --git a/src/ScadaLink.ConfigurationDatabase/Migrations/ScadaLinkDbContextModelSnapshot.cs b/src/ScadaLink.ConfigurationDatabase/Migrations/ScadaLinkDbContextModelSnapshot.cs index 0ff9638..d78df93 100644 --- a/src/ScadaLink.ConfigurationDatabase/Migrations/ScadaLinkDbContextModelSnapshot.cs +++ b/src/ScadaLink.ConfigurationDatabase/Migrations/ScadaLinkDbContextModelSnapshot.cs @@ -115,6 +115,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") @@ -264,6 +265,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceSite") @@ -826,6 +828,7 @@ namespace ScadaLink.ConfigurationDatabase.Migrations b.Property("SourceNode") .HasMaxLength(64) + .IsUnicode(false) .HasColumnType("varchar(64)"); b.Property("SourceScript") diff --git a/src/ScadaLink.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs b/src/ScadaLink.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs index cfe0478..e14d46a 100644 --- a/src/ScadaLink.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs +++ b/src/ScadaLink.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs @@ -171,6 +171,11 @@ WHERE TrackedOperationId = {idText} // and compose with the keyset cursor, so a StuckOnly page is honest: // never under-filled with a non-null next cursor. Mirrors how // NotificationOutboxRepository.QueryAsync applies NotificationOutboxFilter.StuckCutoff. + // + // SELECT-list maintenance: EF Core's FromSqlInterpolated requires every + // entity-tracked column to appear in the result set. Adding a new column + // to the SiteCall entity means extending the list below too — otherwise + // every read trips "The required column 'X' was not present" at runtime. FormattableString sql = $@" SELECT TOP ({paging.PageSize}) TrackedOperationId, Channel, Target, SourceSite, SourceNode, Status, RetryCount,