refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,943 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class InitialSchema : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ApiKeys",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
KeyValue = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
IsEnabled = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiKeys", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ApiMethods",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Script = table.Column<string>(type: "nvarchar(max)", nullable: false),
ApprovedApiKeyIds = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
TimeoutSeconds = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiMethods", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AuditLogEntries",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
User = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Action = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
EntityType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EntityId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EntityName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
AfterStateJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
Timestamp = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditLogEntries", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DatabaseConnectionDefinitions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ConnectionString = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: false),
MaxRetries = table.Column<int>(type: "int", nullable: false),
RetryDelay = table.Column<TimeSpan>(type: "time", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DatabaseConnectionDefinitions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DataConnections",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Protocol = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Configuration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DataConnections", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DataProtectionKeys",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
FriendlyName = table.Column<string>(type: "nvarchar(max)", nullable: true),
Xml = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ExternalSystemDefinitions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EndpointUrl = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
AuthType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
AuthConfiguration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
MaxRetries = table.Column<int>(type: "int", nullable: false),
RetryDelay = table.Column<TimeSpan>(type: "time", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ExternalSystemDefinitions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "LdapGroupMappings",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
LdapGroupName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Role = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LdapGroupMappings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "NotificationLists",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NotificationLists", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SharedScripts",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SharedScripts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sites",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
SiteIdentifier = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sites", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SmtpConfigurations",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Host = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Port = table.Column<int>(type: "int", nullable: false),
AuthType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Credentials = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
TlsMode = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
FromAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
ConnectionTimeoutSeconds = table.Column<int>(type: "int", nullable: false),
MaxConcurrentConnections = table.Column<int>(type: "int", nullable: false),
MaxRetries = table.Column<int>(type: "int", nullable: false),
RetryDelay = table.Column<TimeSpan>(type: "time", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SmtpConfigurations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SystemArtifactDeploymentRecords",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ArtifactType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
DeployedBy = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DeployedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
PerSiteStatus = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SystemArtifactDeploymentRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Templates",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
ParentTemplateId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Templates", x => x.Id);
table.ForeignKey(
name: "FK_Templates_Templates_ParentTemplateId",
column: x => x.ParentTemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ExternalSystemMethods",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ExternalSystemDefinitionId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
HttpMethod = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
Path = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ExternalSystemMethods", x => x.Id);
table.ForeignKey(
name: "FK_ExternalSystemMethods_ExternalSystemDefinitions_ExternalSystemDefinitionId",
column: x => x.ExternalSystemDefinitionId,
principalTable: "ExternalSystemDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "NotificationRecipients",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
NotificationListId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EmailAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NotificationRecipients", x => x.Id);
table.ForeignKey(
name: "FK_NotificationRecipients_NotificationLists_NotificationListId",
column: x => x.NotificationListId,
principalTable: "NotificationLists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Areas",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SiteId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ParentAreaId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Areas", x => x.Id);
table.ForeignKey(
name: "FK_Areas_Areas_ParentAreaId",
column: x => x.ParentAreaId,
principalTable: "Areas",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Areas_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "SiteDataConnectionAssignments",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SiteId = table.Column<int>(type: "int", nullable: false),
DataConnectionId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteDataConnectionAssignments", x => x.Id);
table.ForeignKey(
name: "FK_SiteDataConnectionAssignments_DataConnections_DataConnectionId",
column: x => x.DataConnectionId,
principalTable: "DataConnections",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SiteDataConnectionAssignments_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "SiteScopeRules",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
LdapGroupMappingId = table.Column<int>(type: "int", nullable: false),
SiteId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteScopeRules", x => x.Id);
table.ForeignKey(
name: "FK_SiteScopeRules_LdapGroupMappings_LdapGroupMappingId",
column: x => x.LdapGroupMappingId,
principalTable: "LdapGroupMappings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SiteScopeRules_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateAlarms",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
PriorityLevel = table.Column<int>(type: "int", nullable: false),
IsLocked = table.Column<bool>(type: "bit", nullable: false),
TriggerType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
TriggerConfiguration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
OnTriggerScriptId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateAlarms", x => x.Id);
table.ForeignKey(
name: "FK_TemplateAlarms_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateAttributes",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Value = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
DataType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
IsLocked = table.Column<bool>(type: "bit", nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
DataSourceReference = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateAttributes", x => x.Id);
table.ForeignKey(
name: "FK_TemplateAttributes_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateCompositions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
ComposedTemplateId = table.Column<int>(type: "int", nullable: false),
InstanceName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateCompositions", x => x.Id);
table.ForeignKey(
name: "FK_TemplateCompositions_Templates_ComposedTemplateId",
column: x => x.ComposedTemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TemplateCompositions_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateScripts",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
IsLocked = table.Column<bool>(type: "bit", nullable: false),
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
TriggerType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
TriggerConfiguration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
MinTimeBetweenRuns = table.Column<TimeSpan>(type: "time", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateScripts", x => x.Id);
table.ForeignKey(
name: "FK_TemplateScripts_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Instances",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
SiteId = table.Column<int>(type: "int", nullable: false),
AreaId = table.Column<int>(type: "int", nullable: true),
UniqueName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
State = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Instances", x => x.Id);
table.ForeignKey(
name: "FK_Instances_Areas_AreaId",
column: x => x.AreaId,
principalTable: "Areas",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_Instances_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Instances_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "DeployedConfigSnapshots",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
DeploymentId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
RevisionHash = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
ConfigurationJson = table.Column<string>(type: "nvarchar(max)", nullable: false),
DeployedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeployedConfigSnapshots", x => x.Id);
table.ForeignKey(
name: "FK_DeployedConfigSnapshots_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "DeploymentRecords",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
DeploymentId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
RevisionHash = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
DeployedBy = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DeployedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
CompletedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
ErrorMessage = table.Column<string>(type: "nvarchar(max)", nullable: true),
RowVersion = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeploymentRecords", x => x.Id);
table.ForeignKey(
name: "FK_DeploymentRecords_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "InstanceAttributeOverrides",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
AttributeName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
OverrideValue = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InstanceAttributeOverrides", x => x.Id);
table.ForeignKey(
name: "FK_InstanceAttributeOverrides_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "InstanceConnectionBindings",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
AttributeName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DataConnectionId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_InstanceConnectionBindings", x => x.Id);
table.ForeignKey(
name: "FK_InstanceConnectionBindings_DataConnections_DataConnectionId",
column: x => x.DataConnectionId,
principalTable: "DataConnections",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_InstanceConnectionBindings_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.InsertData(
table: "LdapGroupMappings",
columns: new[] { "Id", "LdapGroupName", "Role" },
values: new object[] { 1, "SCADA-Admins", "Admin" });
migrationBuilder.CreateIndex(
name: "IX_ApiKeys_KeyValue",
table: "ApiKeys",
column: "KeyValue",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiKeys_Name",
table: "ApiKeys",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiMethods_Name",
table: "ApiMethods",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Areas_ParentAreaId",
table: "Areas",
column: "ParentAreaId");
migrationBuilder.CreateIndex(
name: "IX_Areas_SiteId_ParentAreaId_Name",
table: "Areas",
columns: new[] { "SiteId", "ParentAreaId", "Name" },
unique: true,
filter: "[ParentAreaId] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_Action",
table: "AuditLogEntries",
column: "Action");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_EntityId",
table: "AuditLogEntries",
column: "EntityId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_EntityType",
table: "AuditLogEntries",
column: "EntityType");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_Timestamp",
table: "AuditLogEntries",
column: "Timestamp");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_User",
table: "AuditLogEntries",
column: "User");
migrationBuilder.CreateIndex(
name: "IX_DatabaseConnectionDefinitions_Name",
table: "DatabaseConnectionDefinitions",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DataConnections_Name",
table: "DataConnections",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeployedConfigSnapshots_DeploymentId",
table: "DeployedConfigSnapshots",
column: "DeploymentId");
migrationBuilder.CreateIndex(
name: "IX_DeployedConfigSnapshots_InstanceId",
table: "DeployedConfigSnapshots",
column: "InstanceId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeploymentRecords_DeployedAt",
table: "DeploymentRecords",
column: "DeployedAt");
migrationBuilder.CreateIndex(
name: "IX_DeploymentRecords_DeploymentId",
table: "DeploymentRecords",
column: "DeploymentId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeploymentRecords_InstanceId",
table: "DeploymentRecords",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_ExternalSystemDefinitions_Name",
table: "ExternalSystemDefinitions",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ExternalSystemMethods_ExternalSystemDefinitionId_Name",
table: "ExternalSystemMethods",
columns: new[] { "ExternalSystemDefinitionId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_InstanceAttributeOverrides_InstanceId_AttributeName",
table: "InstanceAttributeOverrides",
columns: new[] { "InstanceId", "AttributeName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_InstanceConnectionBindings_DataConnectionId",
table: "InstanceConnectionBindings",
column: "DataConnectionId");
migrationBuilder.CreateIndex(
name: "IX_InstanceConnectionBindings_InstanceId_AttributeName",
table: "InstanceConnectionBindings",
columns: new[] { "InstanceId", "AttributeName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Instances_AreaId",
table: "Instances",
column: "AreaId");
migrationBuilder.CreateIndex(
name: "IX_Instances_SiteId_UniqueName",
table: "Instances",
columns: new[] { "SiteId", "UniqueName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Instances_TemplateId",
table: "Instances",
column: "TemplateId");
migrationBuilder.CreateIndex(
name: "IX_LdapGroupMappings_LdapGroupName",
table: "LdapGroupMappings",
column: "LdapGroupName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NotificationLists_Name",
table: "NotificationLists",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NotificationRecipients_NotificationListId",
table: "NotificationRecipients",
column: "NotificationListId");
migrationBuilder.CreateIndex(
name: "IX_SharedScripts_Name",
table: "SharedScripts",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SiteDataConnectionAssignments_DataConnectionId",
table: "SiteDataConnectionAssignments",
column: "DataConnectionId");
migrationBuilder.CreateIndex(
name: "IX_SiteDataConnectionAssignments_SiteId_DataConnectionId",
table: "SiteDataConnectionAssignments",
columns: new[] { "SiteId", "DataConnectionId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Sites_Name",
table: "Sites",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Sites_SiteIdentifier",
table: "Sites",
column: "SiteIdentifier",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SiteScopeRules_LdapGroupMappingId_SiteId",
table: "SiteScopeRules",
columns: new[] { "LdapGroupMappingId", "SiteId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SiteScopeRules_SiteId",
table: "SiteScopeRules",
column: "SiteId");
migrationBuilder.CreateIndex(
name: "IX_SystemArtifactDeploymentRecords_DeployedAt",
table: "SystemArtifactDeploymentRecords",
column: "DeployedAt");
migrationBuilder.CreateIndex(
name: "IX_TemplateAlarms_TemplateId_Name",
table: "TemplateAlarms",
columns: new[] { "TemplateId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TemplateAttributes_TemplateId_Name",
table: "TemplateAttributes",
columns: new[] { "TemplateId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TemplateCompositions_ComposedTemplateId",
table: "TemplateCompositions",
column: "ComposedTemplateId");
migrationBuilder.CreateIndex(
name: "IX_TemplateCompositions_TemplateId_InstanceName",
table: "TemplateCompositions",
columns: new[] { "TemplateId", "InstanceName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Templates_Name",
table: "Templates",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Templates_ParentTemplateId",
table: "Templates",
column: "ParentTemplateId");
migrationBuilder.CreateIndex(
name: "IX_TemplateScripts_TemplateId_Name",
table: "TemplateScripts",
columns: new[] { "TemplateId", "Name" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiKeys");
migrationBuilder.DropTable(
name: "ApiMethods");
migrationBuilder.DropTable(
name: "AuditLogEntries");
migrationBuilder.DropTable(
name: "DatabaseConnectionDefinitions");
migrationBuilder.DropTable(
name: "DataProtectionKeys");
migrationBuilder.DropTable(
name: "DeployedConfigSnapshots");
migrationBuilder.DropTable(
name: "DeploymentRecords");
migrationBuilder.DropTable(
name: "ExternalSystemMethods");
migrationBuilder.DropTable(
name: "InstanceAttributeOverrides");
migrationBuilder.DropTable(
name: "InstanceConnectionBindings");
migrationBuilder.DropTable(
name: "NotificationRecipients");
migrationBuilder.DropTable(
name: "SharedScripts");
migrationBuilder.DropTable(
name: "SiteDataConnectionAssignments");
migrationBuilder.DropTable(
name: "SiteScopeRules");
migrationBuilder.DropTable(
name: "SmtpConfigurations");
migrationBuilder.DropTable(
name: "SystemArtifactDeploymentRecords");
migrationBuilder.DropTable(
name: "TemplateAlarms");
migrationBuilder.DropTable(
name: "TemplateAttributes");
migrationBuilder.DropTable(
name: "TemplateCompositions");
migrationBuilder.DropTable(
name: "TemplateScripts");
migrationBuilder.DropTable(
name: "ExternalSystemDefinitions");
migrationBuilder.DropTable(
name: "Instances");
migrationBuilder.DropTable(
name: "NotificationLists");
migrationBuilder.DropTable(
name: "DataConnections");
migrationBuilder.DropTable(
name: "LdapGroupMappings");
migrationBuilder.DropTable(
name: "Areas");
migrationBuilder.DropTable(
name: "Templates");
migrationBuilder.DropTable(
name: "Sites");
}
}
}
@@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddSiteNodeAddresses : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "NodeAAddress",
table: "Sites",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "NodeBAddress",
table: "Sites",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "NodeAAddress",
table: "Sites");
migrationBuilder.DropColumn(
name: "NodeBAddress",
table: "Sites");
}
}
}
@@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddGrpcNodeAddresses : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "GrpcNodeAAddress",
table: "Sites",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "GrpcNodeBAddress",
table: "Sites",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GrpcNodeAAddress",
table: "Sites");
migrationBuilder.DropColumn(
name: "GrpcNodeBAddress",
table: "Sites");
}
}
}
@@ -0,0 +1,184 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddSiteIdToDataConnections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Step 1: Drop old unique index on Name (allows duplicate names across sites)
migrationBuilder.DropIndex(
name: "IX_DataConnections_Name",
table: "DataConnections");
// Step 2: Add nullable SiteId column
migrationBuilder.AddColumn<int>(
name: "SiteId",
table: "DataConnections",
type: "int",
nullable: true);
// Step 3: Migrate data from SiteDataConnectionAssignments
migrationBuilder.Sql(@"
-- Phase A: Assign the first site to each existing DataConnection
UPDATE dc
SET dc.SiteId = a.SiteId
FROM DataConnections dc
INNER JOIN (
SELECT DataConnectionId, MIN(SiteId) AS SiteId
FROM SiteDataConnectionAssignments
GROUP BY DataConnectionId
) a ON dc.Id = a.DataConnectionId
WHERE dc.SiteId IS NULL;
-- Phase B: For connections assigned to additional sites, create copies
-- and update InstanceConnectionBindings to point to the new copy
DECLARE @AssignSiteId INT, @AssignConnId INT, @NewConnId INT;
DECLARE @OrigName NVARCHAR(200), @OrigProtocol NVARCHAR(50), @OrigConfig NVARCHAR(4000);
DECLARE assignment_cursor CURSOR FOR
SELECT a.SiteId, a.DataConnectionId
FROM SiteDataConnectionAssignments a
INNER JOIN DataConnections dc ON a.DataConnectionId = dc.Id
WHERE dc.SiteId <> a.SiteId;
OPEN assignment_cursor;
FETCH NEXT FROM assignment_cursor INTO @AssignSiteId, @AssignConnId;
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @OrigName = Name, @OrigProtocol = Protocol, @OrigConfig = Configuration
FROM DataConnections WHERE Id = @AssignConnId;
INSERT INTO DataConnections (SiteId, Name, Protocol, Configuration)
VALUES (@AssignSiteId, @OrigName, @OrigProtocol, @OrigConfig);
SET @NewConnId = SCOPE_IDENTITY();
-- Update bindings for instances on this site to point to the new connection
UPDATE icb
SET icb.DataConnectionId = @NewConnId
FROM InstanceConnectionBindings icb
INNER JOIN Instances i ON icb.InstanceId = i.Id
WHERE icb.DataConnectionId = @AssignConnId
AND i.SiteId = @AssignSiteId;
FETCH NEXT FROM assignment_cursor INTO @AssignSiteId, @AssignConnId;
END
CLOSE assignment_cursor;
DEALLOCATE assignment_cursor;
-- Phase C: Handle any DataConnections not assigned to any site
-- (assign to the first site as a fallback)
UPDATE dc
SET dc.SiteId = (SELECT TOP 1 Id FROM Sites ORDER BY Id)
FROM DataConnections dc
WHERE dc.SiteId IS NULL;
");
// Step 4: Make SiteId non-nullable
migrationBuilder.AlterColumn<int>(
name: "SiteId",
table: "DataConnections",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
// Step 5: Add composite unique index and FK
migrationBuilder.CreateIndex(
name: "IX_DataConnections_SiteId_Name",
table: "DataConnections",
columns: new[] { "SiteId", "Name" },
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_DataConnections_Sites_SiteId",
table: "DataConnections",
column: "SiteId",
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
// Step 6: Drop SiteDataConnectionAssignments table
migrationBuilder.DropTable(
name: "SiteDataConnectionAssignments");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Recreate SiteDataConnectionAssignments table
migrationBuilder.CreateTable(
name: "SiteDataConnectionAssignments",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
DataConnectionId = table.Column<int>(type: "int", nullable: false),
SiteId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteDataConnectionAssignments", x => x.Id);
table.ForeignKey(
name: "FK_SiteDataConnectionAssignments_DataConnections_DataConnectionId",
column: x => x.DataConnectionId,
principalTable: "DataConnections",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SiteDataConnectionAssignments_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_SiteDataConnectionAssignments_DataConnectionId",
table: "SiteDataConnectionAssignments",
column: "DataConnectionId");
migrationBuilder.CreateIndex(
name: "IX_SiteDataConnectionAssignments_SiteId_DataConnectionId",
table: "SiteDataConnectionAssignments",
columns: new[] { "SiteId", "DataConnectionId" },
unique: true);
// Migrate data back
migrationBuilder.Sql(@"
INSERT INTO SiteDataConnectionAssignments (SiteId, DataConnectionId)
SELECT SiteId, Id FROM DataConnections;
");
// Remove FK and composite index
migrationBuilder.DropForeignKey(
name: "FK_DataConnections_Sites_SiteId",
table: "DataConnections");
migrationBuilder.DropIndex(
name: "IX_DataConnections_SiteId_Name",
table: "DataConnections");
// Restore unique index on Name
migrationBuilder.CreateIndex(
name: "IX_DataConnections_Name",
table: "DataConnections",
column: "Name",
unique: true);
// Drop SiteId column
migrationBuilder.DropColumn(
name: "SiteId",
table: "DataConnections");
}
}
}
@@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddPrimaryBackupDataConnections : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "Configuration",
table: "DataConnections",
newName: "PrimaryConfiguration");
migrationBuilder.AddColumn<string>(
name: "BackupConfiguration",
table: "DataConnections",
type: "nvarchar(4000)",
maxLength: 4000,
nullable: true);
migrationBuilder.AddColumn<int>(
name: "FailoverRetryCount",
table: "DataConnections",
type: "int",
nullable: false,
defaultValue: 3);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BackupConfiguration",
table: "DataConnections");
migrationBuilder.DropColumn(
name: "FailoverRetryCount",
table: "DataConnections");
migrationBuilder.RenameColumn(
name: "PrimaryConfiguration",
table: "DataConnections",
newName: "Configuration");
}
}
}
@@ -0,0 +1,80 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddTemplateFolders : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "FolderId",
table: "Templates",
type: "int",
nullable: true);
migrationBuilder.CreateTable(
name: "TemplateFolders",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ParentFolderId = table.Column<int>(type: "int", nullable: true),
SortOrder = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateFolders", x => x.Id);
table.ForeignKey(
name: "FK_TemplateFolders_TemplateFolders_ParentFolderId",
column: x => x.ParentFolderId,
principalTable: "TemplateFolders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Templates_FolderId",
table: "Templates",
column: "FolderId");
migrationBuilder.CreateIndex(
name: "IX_TemplateFolders_ParentFolderId_Name",
table: "TemplateFolders",
columns: new[] { "ParentFolderId", "Name" },
unique: true,
filter: "[ParentFolderId] IS NOT NULL");
migrationBuilder.AddForeignKey(
name: "FK_Templates_TemplateFolders_FolderId",
table: "Templates",
column: "FolderId",
principalTable: "TemplateFolders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Templates_TemplateFolders_FolderId",
table: "Templates");
migrationBuilder.DropTable(
name: "TemplateFolders");
migrationBuilder.DropIndex(
name: "IX_Templates_FolderId",
table: "Templates");
migrationBuilder.DropColumn(
name: "FolderId",
table: "Templates");
}
}
}
@@ -0,0 +1,83 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddDerivedTemplateFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsInherited",
table: "TemplateScripts",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "LockedInDerived",
table: "TemplateScripts",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "IsDerived",
table: "Templates",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "OwnerCompositionId",
table: "Templates",
type: "int",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsInherited",
table: "TemplateAttributes",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "LockedInDerived",
table: "TemplateAttributes",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsInherited",
table: "TemplateScripts");
migrationBuilder.DropColumn(
name: "LockedInDerived",
table: "TemplateScripts");
migrationBuilder.DropColumn(
name: "IsDerived",
table: "Templates");
migrationBuilder.DropColumn(
name: "OwnerCompositionId",
table: "Templates");
migrationBuilder.DropColumn(
name: "IsInherited",
table: "TemplateAttributes");
migrationBuilder.DropColumn(
name: "LockedInDerived",
table: "TemplateAttributes");
}
}
}
@@ -0,0 +1,117 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class MigrateCompositionsToDerived : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Re-shape every pre-Phase-2 TemplateComposition so it points at a
// newly created derived template ("<parent>.<slot>") that inherits
// from the original base. Attribute and script rows are copied with
// IsInherited=1; the composition's ComposedTemplateId is repointed.
//
// Idempotent: only rows whose target is still IsDerived=0 are touched.
// Aborts the migration if any derived name would collide with an
// existing template, so the operator can resolve manually.
migrationBuilder.Sql(@"
SET NOCOUNT ON;
DECLARE @collisions NVARCHAR(MAX) = (
SELECT STRING_AGG(owner.Name + N'.' + c.InstanceName, N', ')
FROM TemplateCompositions c
INNER JOIN Templates base_t ON base_t.Id = c.ComposedTemplateId
INNER JOIN Templates owner ON owner.Id = c.TemplateId
INNER JOIN Templates existing ON existing.Name = owner.Name + N'.' + c.InstanceName
WHERE base_t.IsDerived = 0
);
IF @collisions IS NOT NULL
BEGIN
DECLARE @msg NVARCHAR(MAX) =
N'MigrateCompositionsToDerived: cannot create derived templates — these names already exist: '
+ @collisions
+ N'. Rename the conflicting templates and retry the migration.';
THROW 50000, @msg, 1;
END
DECLARE @CompId INT, @BaseId INT, @OwnerName NVARCHAR(200), @SlotName NVARCHAR(200);
DECLARE @NewId INT, @NewName NVARCHAR(200);
DECLARE map_cursor CURSOR FAST_FORWARD FOR
SELECT c.Id, c.ComposedTemplateId, owner.Name, c.InstanceName
FROM TemplateCompositions c
INNER JOIN Templates base_t ON base_t.Id = c.ComposedTemplateId
INNER JOIN Templates owner ON owner.Id = c.TemplateId
WHERE base_t.IsDerived = 0;
OPEN map_cursor;
FETCH NEXT FROM map_cursor INTO @CompId, @BaseId, @OwnerName, @SlotName;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @NewName = @OwnerName + N'.' + @SlotName;
INSERT INTO Templates (Name, Description, ParentTemplateId, FolderId, IsDerived, OwnerCompositionId)
SELECT @NewName, b.Description, b.Id, NULL, 1, @CompId
FROM Templates b
WHERE b.Id = @BaseId;
SET @NewId = SCOPE_IDENTITY();
INSERT INTO TemplateAttributes
(TemplateId, Name, Value, DataType, IsLocked, Description, DataSourceReference, IsInherited, LockedInDerived)
SELECT @NewId, a.Name, a.Value, a.DataType, a.IsLocked, a.Description, a.DataSourceReference, 1, 0
FROM TemplateAttributes a
WHERE a.TemplateId = @BaseId;
INSERT INTO TemplateScripts
(TemplateId, Name, Code, IsLocked, TriggerType, TriggerConfiguration, ParameterDefinitions, ReturnDefinition, MinTimeBetweenRuns, IsInherited, LockedInDerived)
SELECT @NewId, s.Name, s.Code, s.IsLocked, s.TriggerType, s.TriggerConfiguration, s.ParameterDefinitions, s.ReturnDefinition, s.MinTimeBetweenRuns, 1, 0
FROM TemplateScripts s
WHERE s.TemplateId = @BaseId;
UPDATE TemplateCompositions
SET ComposedTemplateId = @NewId
WHERE Id = @CompId;
FETCH NEXT FROM map_cursor INTO @CompId, @BaseId, @OwnerName, @SlotName;
END
CLOSE map_cursor;
DEALLOCATE map_cursor;
");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Reverse: repoint each composition back to the derived template's
// base, then drop the derived templates (with their copied rows).
migrationBuilder.Sql(@"
SET NOCOUNT ON;
UPDATE c
SET c.ComposedTemplateId = d.ParentTemplateId
FROM TemplateCompositions c
INNER JOIN Templates d ON d.Id = c.ComposedTemplateId
WHERE d.IsDerived = 1
AND d.OwnerCompositionId = c.Id
AND d.ParentTemplateId IS NOT NULL;
DELETE a FROM TemplateAttributes a
INNER JOIN Templates t ON t.Id = a.TemplateId
WHERE t.IsDerived = 1;
DELETE s FROM TemplateScripts s
INNER JOIN Templates t ON t.Id = s.TemplateId
WHERE t.IsDerived = 1;
DELETE FROM Templates WHERE IsDerived = 1;
");
}
}
}
@@ -0,0 +1,196 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class MigrateParametersToJsonSchema : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Convert legacy flat-shape parameter / return JSON in TemplateScripts,
// SharedScripts, and ApiMethods to JSON Schema.
//
// Parameters [{name,type,required,itemType?}]
// → {"type":"object","properties":{<name>:{"type":<jsType>}},"required":[...]}
//
// Return {type,itemType?}
// → {"type":<jsType>} or {"type":"array","items":{"type":<inner>}}
//
// Idempotent: only rows whose value starts with '[' (parameters) or that
// contain the legacy 'List' sentinel (return) are touched. Already-converted
// rows are skipped.
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.fn_LegacyTypeToJsonSchemaType', 'FN') IS NOT NULL
DROP FUNCTION dbo.fn_LegacyTypeToJsonSchemaType;
");
migrationBuilder.Sql(@"
CREATE FUNCTION dbo.fn_LegacyTypeToJsonSchemaType(@legacy NVARCHAR(50))
RETURNS NVARCHAR(50)
AS
BEGIN
RETURN
CASE LOWER(ISNULL(@legacy, 'string'))
WHEN 'boolean' THEN 'boolean'
WHEN 'bool' THEN 'boolean'
WHEN 'integer' THEN 'integer'
WHEN 'int' THEN 'integer'
WHEN 'int32' THEN 'integer'
WHEN 'int64' THEN 'integer'
WHEN 'float' THEN 'number'
WHEN 'double' THEN 'number'
WHEN 'decimal' THEN 'number'
WHEN 'number' THEN 'number'
WHEN 'string' THEN 'string'
WHEN 'datetime' THEN 'string'
WHEN 'object' THEN 'object'
WHEN 'list' THEN 'array'
WHEN 'array' THEN 'array'
ELSE 'string'
END;
END;
");
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.fn_LegacyParametersToJsonSchema', 'FN') IS NOT NULL
DROP FUNCTION dbo.fn_LegacyParametersToJsonSchema;
");
migrationBuilder.Sql(@"
CREATE FUNCTION dbo.fn_LegacyParametersToJsonSchema(@legacy NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
IF @legacy IS NULL OR LTRIM(@legacy) = '' RETURN NULL;
IF LEFT(LTRIM(@legacy), 1) <> '[' RETURN @legacy; -- already schema-shaped
DECLARE @props NVARCHAR(MAX) = (
SELECT STRING_AGG(
CONCAT(
'""',
STRING_ESCAPE(JSON_VALUE(p.value, '$.name'), 'json'),
'"":',
CASE
WHEN LOWER(ISNULL(JSON_VALUE(p.value, '$.type'), 'string')) IN ('list', 'array')
THEN CONCAT(
'{""type"":""array"",""items"":{""type"":""',
dbo.fn_LegacyTypeToJsonSchemaType(JSON_VALUE(p.value, '$.itemType')),
'""}}')
ELSE CONCAT(
'{""type"":""',
dbo.fn_LegacyTypeToJsonSchemaType(JSON_VALUE(p.value, '$.type')),
'""}')
END),
',')
WITHIN GROUP (ORDER BY p.[key])
FROM OPENJSON(@legacy) p
WHERE JSON_VALUE(p.value, '$.name') IS NOT NULL
AND JSON_VALUE(p.value, '$.name') <> ''
);
DECLARE @required NVARCHAR(MAX) = (
SELECT STRING_AGG(
CONCAT('""', STRING_ESCAPE(JSON_VALUE(p.value, '$.name'), 'json'), '""'),
',')
WITHIN GROUP (ORDER BY p.[key])
FROM OPENJSON(@legacy) p
WHERE JSON_VALUE(p.value, '$.name') IS NOT NULL
AND JSON_VALUE(p.value, '$.name') <> ''
AND LOWER(ISNULL(JSON_VALUE(p.value, '$.required'), 'true')) <> 'false'
);
RETURN
'{""type"":""object"",""properties"":{' + ISNULL(@props, '') + '}'
+ CASE WHEN @required IS NULL OR @required = '' THEN ''
ELSE ',""required"":[' + @required + ']'
END
+ '}';
END;
");
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.fn_LegacyReturnToJsonSchema', 'FN') IS NOT NULL
DROP FUNCTION dbo.fn_LegacyReturnToJsonSchema;
");
migrationBuilder.Sql(@"
CREATE FUNCTION dbo.fn_LegacyReturnToJsonSchema(@legacy NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
IF @legacy IS NULL OR LTRIM(@legacy) = '' RETURN NULL;
IF LEFT(LTRIM(@legacy), 1) <> '{' RETURN @legacy;
DECLARE @legacyType NVARCHAR(50) = JSON_VALUE(@legacy, '$.type');
IF @legacyType IS NULL RETURN @legacy;
-- Already JSON Schema (lowercase types, no itemType legacy sentinel): leave it.
IF @legacyType IN ('boolean','integer','number','string','object','array')
AND JSON_VALUE(@legacy, '$.itemType') IS NULL
RETURN @legacy;
IF LOWER(@legacyType) = 'list'
BEGIN
DECLARE @inner NVARCHAR(50) =
dbo.fn_LegacyTypeToJsonSchemaType(JSON_VALUE(@legacy, '$.itemType'));
RETURN CONCAT('{""type"":""array"",""items"":{""type"":""', @inner, '""}}');
END;
RETURN CONCAT('{""type"":""', dbo.fn_LegacyTypeToJsonSchemaType(@legacyType), '""}');
END;
");
migrationBuilder.Sql(@"
UPDATE TemplateScripts
SET ParameterDefinitions = dbo.fn_LegacyParametersToJsonSchema(ParameterDefinitions)
WHERE ParameterDefinitions IS NOT NULL
AND LEFT(LTRIM(ParameterDefinitions), 1) = '[';
UPDATE TemplateScripts
SET ReturnDefinition = dbo.fn_LegacyReturnToJsonSchema(ReturnDefinition)
WHERE ReturnDefinition IS NOT NULL
AND LEFT(LTRIM(ReturnDefinition), 1) = '{';
UPDATE SharedScripts
SET ParameterDefinitions = dbo.fn_LegacyParametersToJsonSchema(ParameterDefinitions)
WHERE ParameterDefinitions IS NOT NULL
AND LEFT(LTRIM(ParameterDefinitions), 1) = '[';
UPDATE SharedScripts
SET ReturnDefinition = dbo.fn_LegacyReturnToJsonSchema(ReturnDefinition)
WHERE ReturnDefinition IS NOT NULL
AND LEFT(LTRIM(ReturnDefinition), 1) = '{';
UPDATE ApiMethods
SET ParameterDefinitions = dbo.fn_LegacyParametersToJsonSchema(ParameterDefinitions)
WHERE ParameterDefinitions IS NOT NULL
AND LEFT(LTRIM(ParameterDefinitions), 1) = '[';
UPDATE ApiMethods
SET ReturnDefinition = dbo.fn_LegacyReturnToJsonSchema(ReturnDefinition)
WHERE ReturnDefinition IS NOT NULL
AND LEFT(LTRIM(ReturnDefinition), 1) = '{';
");
migrationBuilder.Sql(@"
DROP FUNCTION IF EXISTS dbo.fn_LegacyParametersToJsonSchema;
DROP FUNCTION IF EXISTS dbo.fn_LegacyReturnToJsonSchema;
DROP FUNCTION IF EXISTS dbo.fn_LegacyTypeToJsonSchemaType;
");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Lossy: JSON Schema can express fields (descriptions, defaults, enums,
// nested objects) that the legacy flat shape cannot represent. Reverse
// migration is not supported.
throw new System.NotSupportedException(
"Reverse migration from JSON Schema to legacy flat shape is not supported because the conversion is lossy.");
}
}
}
@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddInstanceAlarmOverrides : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "InstanceAlarmOverrides",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
AlarmCanonicalName = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false),
TriggerConfigurationOverride = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
PriorityLevelOverride = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InstanceAlarmOverrides", x => x.Id);
table.ForeignKey(
name: "FK_InstanceAlarmOverrides_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_InstanceAlarmOverrides_InstanceId_AlarmCanonicalName",
table: "InstanceAlarmOverrides",
columns: new[] { "InstanceId", "AlarmCanonicalName" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "InstanceAlarmOverrides");
}
}
}
@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddDerivedAlarmFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsInherited",
table: "TemplateAlarms",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "LockedInDerived",
table: "TemplateAlarms",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsInherited",
table: "TemplateAlarms");
migrationBuilder.DropColumn(
name: "LockedInDerived",
table: "TemplateAlarms");
}
}
}
@@ -0,0 +1,82 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class EncryptSecretColumns : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Credentials",
table: "SmtpConfigurations",
type: "nvarchar(max)",
maxLength: 8000,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(4000)",
oldMaxLength: 4000,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "AuthConfiguration",
table: "ExternalSystemDefinitions",
type: "nvarchar(max)",
maxLength: 8000,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(4000)",
oldMaxLength: 4000,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "ConnectionString",
table: "DatabaseConnectionDefinitions",
type: "nvarchar(max)",
maxLength: 8000,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(4000)",
oldMaxLength: 4000);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Credentials",
table: "SmtpConfigurations",
type: "nvarchar(4000)",
maxLength: 4000,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldMaxLength: 8000,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "AuthConfiguration",
table: "ExternalSystemDefinitions",
type: "nvarchar(4000)",
maxLength: 4000,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldMaxLength: 8000,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "ConnectionString",
table: "DatabaseConnectionDefinitions",
type: "nvarchar(4000)",
maxLength: 4000,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldMaxLength: 8000);
}
}
}
@@ -0,0 +1,58 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class BoundGrpcNodeAddressLength : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "GrpcNodeBAddress",
table: "Sites",
type: "nvarchar(500)",
maxLength: 500,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "GrpcNodeAAddress",
table: "Sites",
type: "nvarchar(500)",
maxLength: 500,
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "GrpcNodeBAddress",
table: "Sites",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(500)",
oldMaxLength: 500,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "GrpcNodeAAddress",
table: "Sites",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(500)",
oldMaxLength: 500,
oldNullable: true);
}
}
}
@@ -0,0 +1,77 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// ConfigurationDatabase-012: replaces the plaintext <c>KeyValue</c> column with a
/// <c>KeyHash</c> column holding a deterministic HMAC-SHA256 hash of the key.
/// </summary>
/// <remarks>
/// A hash is one-way: existing plaintext keys cannot be converted to hashes
/// without the originals. This migration therefore deletes all existing API-key
/// rows. <strong>Every existing API key must be re-issued</strong> after this
/// migration is applied — create new keys via the CLI / Management API / Central
/// UI, distribute the one-time plaintext to callers, and approve them on methods.
/// </remarks>
public partial class HashApiKeyValue : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Existing keys hold only plaintext, which cannot be hashed back. They
// must be re-issued, so remove them before the column change to keep the
// new unique KeyHash index satisfiable.
migrationBuilder.Sql("DELETE FROM ApiKeys;");
migrationBuilder.DropIndex(
name: "IX_ApiKeys_KeyValue",
table: "ApiKeys");
migrationBuilder.DropColumn(
name: "KeyValue",
table: "ApiKeys");
migrationBuilder.AddColumn<string>(
name: "KeyHash",
table: "ApiKeys",
type: "nvarchar(256)",
maxLength: 256,
nullable: false,
defaultValue: "");
migrationBuilder.CreateIndex(
name: "IX_ApiKeys_KeyHash",
table: "ApiKeys",
column: "KeyHash",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_ApiKeys_KeyHash",
table: "ApiKeys");
migrationBuilder.DropColumn(
name: "KeyHash",
table: "ApiKeys");
migrationBuilder.AddColumn<string>(
name: "KeyValue",
table: "ApiKeys",
type: "nvarchar(500)",
maxLength: 500,
nullable: false,
defaultValue: "");
migrationBuilder.CreateIndex(
name: "IX_ApiKeys_KeyValue",
table: "ApiKeys",
column: "KeyValue",
unique: true);
}
}
}
@@ -0,0 +1,81 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Moves composition-derived templates to AVEVA-style contained names: a
/// derived template stores only its slot name (e.g. <c>Pump</c>), not the
/// dotted qualified path (<c>Motor Controller.Pump</c>). The qualified name
/// is computed on read by walking the OwnerComposition chain. The unique
/// index on Template.Name becomes filtered to base templates only —
/// derived templates' uniqueness is the (TemplateId, InstanceName) index on
/// TemplateComposition.
/// </summary>
public partial class ContainedDerivedTemplateNames : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Drop the global unique index first: derived rows are about to be
// renamed to contained names that may duplicate one another or a
// base template.
migrationBuilder.DropIndex(
name: "IX_Templates_Name",
table: "Templates");
// Collapse every derived template's dotted name to its contained
// name — the owning composition slot's InstanceName.
migrationBuilder.Sql(@"
UPDATE t
SET t.Name = c.InstanceName
FROM Templates t
INNER JOIN TemplateCompositions c ON c.Id = t.OwnerCompositionId
WHERE t.IsDerived = 1;");
// Recreate the uniqueness guarantee for base templates only.
migrationBuilder.CreateIndex(
name: "IX_Templates_Name",
table: "Templates",
column: "Name",
unique: true,
filter: "[IsDerived] = 0");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Templates_Name",
table: "Templates");
// Rebuild the dotted qualified names so the global unique index can
// be restored — derived templates' contained names are not globally
// unique. The recursive CTE walks the OwnerComposition chain down
// from each base template.
migrationBuilder.Sql(@"
WITH q AS (
SELECT t.Id, CAST(t.Name AS NVARCHAR(MAX)) AS Qualified
FROM Templates t
WHERE t.IsDerived = 0
UNION ALL
SELECT t.Id, CAST(q.Qualified + N'.' + c.InstanceName AS NVARCHAR(MAX))
FROM Templates t
INNER JOIN TemplateCompositions c ON c.Id = t.OwnerCompositionId
INNER JOIN q ON q.Id = c.TemplateId
)
UPDATE t
SET t.Name = q.Qualified
FROM Templates t
INNER JOIN q ON q.Id = t.Id
WHERE t.IsDerived = 1;");
migrationBuilder.CreateIndex(
name: "IX_Templates_Name",
table: "Templates",
column: "Name",
unique: true);
}
}
}
@@ -0,0 +1,72 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddNotificationsTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Type",
table: "NotificationLists",
type: "nvarchar(32)",
maxLength: 32,
nullable: false,
defaultValue: "Email");
migrationBuilder.CreateTable(
name: "Notifications",
columns: table => new
{
NotificationId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Type = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
ListName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Subject = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
Body = table.Column<string>(type: "nvarchar(max)", nullable: false),
TypeData = table.Column<string>(type: "nvarchar(max)", nullable: true),
Status = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
RetryCount = table.Column<int>(type: "int", nullable: false),
LastError = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ResolvedTargets = table.Column<string>(type: "nvarchar(max)", nullable: true),
SourceSiteId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
SourceInstanceId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
SourceScript = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
SiteEnqueuedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
LastAttemptAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
NextAttemptAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
DeliveredAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Notifications", x => x.NotificationId);
});
migrationBuilder.CreateIndex(
name: "IX_Notifications_SourceSiteId_CreatedAt",
table: "Notifications",
columns: new[] { "SourceSiteId", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_Notifications_Status_NextAttemptAt",
table: "Notifications",
columns: new[] { "Status", "NextAttemptAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Notifications");
migrationBuilder.DropColumn(
name: "Type",
table: "NotificationLists");
}
}
}
@@ -0,0 +1,201 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Bundle C (#23 M1): creates the centralized AuditLog table with monthly
/// partitioning and the two access-control roles documented in alog.md §4.
///
/// Structure:
/// 1. Partition function <c>pf_AuditLog_Month</c> (RANGE RIGHT) with 24
/// monthly boundaries covering 2026-01-01 through 2027-12-01 UTC.
/// 2. Partition scheme <c>ps_AuditLog_Month</c> mapping every partition to
/// [PRIMARY] (dev/test parity; production may relocate via filegroups).
/// 3. <c>AuditLog</c> table created via raw SQL so it is created directly
/// on the partition scheme. The clustered PK is composite
/// {EventId, OccurredAtUtc} — required because partition-aligned PKs
/// must include the partition column.
/// 4. Five reconciliation/query indexes from alog.md §4, plus the
/// UX_AuditLog_EventId unique index that preserves single-column
/// EventId uniqueness for InsertIfNotExistsAsync (M1-T8). All
/// non-clustered indexes are partition-aligned on
/// <c>ps_AuditLog_Month(OccurredAtUtc)</c>.
/// 5. Two database roles:
/// - <c>scadabridge_audit_writer</c>: INSERT + SELECT on AuditLog, with
/// explicit DENY on UPDATE and DELETE so additive role membership
/// (e.g. later db_datawriter) cannot accidentally re-enable mutation.
/// - <c>scadabridge_audit_purger</c>: SELECT on AuditLog and ALTER on
/// SCHEMA::dbo so the purger can run ALTER PARTITION FUNCTION SWITCH
/// and SWITCH PARTITION when sliding the retention window.
/// </summary>
public partial class AddAuditLogTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// 1) Partition function (monthly boundaries Jan 2026 Dec 2027 UTC).
// RANGE RIGHT — the boundary value belongs to the right-hand partition,
// matching the convention used by SQL Server partition-switch tooling.
migrationBuilder.Sql(@"
CREATE PARTITION FUNCTION pf_AuditLog_Month (datetime2(7))
AS RANGE RIGHT FOR VALUES (
'2026-01-01T00:00:00', '2026-02-01T00:00:00', '2026-03-01T00:00:00', '2026-04-01T00:00:00',
'2026-05-01T00:00:00', '2026-06-01T00:00:00', '2026-07-01T00:00:00', '2026-08-01T00:00:00',
'2026-09-01T00:00:00', '2026-10-01T00:00:00', '2026-11-01T00:00:00', '2026-12-01T00:00:00',
'2027-01-01T00:00:00', '2027-02-01T00:00:00', '2027-03-01T00:00:00', '2027-04-01T00:00:00',
'2027-05-01T00:00:00', '2027-06-01T00:00:00', '2027-07-01T00:00:00', '2027-08-01T00:00:00',
'2027-09-01T00:00:00', '2027-10-01T00:00:00', '2027-11-01T00:00:00', '2027-12-01T00:00:00'
);");
// 2) Partition scheme mapping every partition to [PRIMARY].
migrationBuilder.Sql(@"
CREATE PARTITION SCHEME ps_AuditLog_Month
AS PARTITION pf_AuditLog_Month ALL TO ([PRIMARY]);");
// 3) Create the table directly on the partition scheme. Column shapes
// are copied from AuditLogEntityTypeConfiguration so the live schema
// matches the EF model exactly. The clustered PK is composite to
// satisfy SQL Server's rule that partition-aligned clustered indexes
// must include the partition column.
migrationBuilder.Sql(@"
CREATE TABLE dbo.AuditLog (
EventId uniqueidentifier NOT NULL,
OccurredAtUtc datetime2(7) NOT NULL,
IngestedAtUtc datetime2(7) NULL,
Channel varchar(32) NOT NULL,
Kind varchar(32) NOT NULL,
CorrelationId uniqueidentifier NULL,
SourceSiteId varchar(64) NULL,
SourceInstanceId varchar(128) NULL,
SourceScript varchar(128) NULL,
Actor varchar(128) NULL,
Target varchar(256) NULL,
Status varchar(32) NOT NULL,
HttpStatus int NULL,
DurationMs int NULL,
ErrorMessage nvarchar(1024) NULL,
ErrorDetail nvarchar(max) NULL,
RequestSummary nvarchar(max) NULL,
ResponseSummary nvarchar(max) NULL,
PayloadTruncated bit NOT NULL,
Extra nvarchar(max) NULL,
ForwardState varchar(32) NULL,
CONSTRAINT PK_AuditLog PRIMARY KEY CLUSTERED (EventId, OccurredAtUtc)
ON ps_AuditLog_Month(OccurredAtUtc)
) ON ps_AuditLog_Month(OccurredAtUtc);");
// 4) Reconciliation/query indexes from alog.md §4. All non-clustered
// indexes are partition-aligned on ps_AuditLog_Month(OccurredAtUtc)
// so partition-switch operations only touch a single partition. The
// filtered indexes carry their NOT NULL predicates as documented.
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_OccurredAtUtc
ON dbo.AuditLog (OccurredAtUtc DESC)
ON ps_AuditLog_Month(OccurredAtUtc);");
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_Site_Occurred
ON dbo.AuditLog (SourceSiteId ASC, OccurredAtUtc DESC)
ON ps_AuditLog_Month(OccurredAtUtc);");
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_CorrelationId
ON dbo.AuditLog (CorrelationId)
WHERE CorrelationId IS NOT NULL
ON ps_AuditLog_Month(OccurredAtUtc);");
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_Channel_Status_Occurred
ON dbo.AuditLog (Channel ASC, Status ASC, OccurredAtUtc DESC)
ON ps_AuditLog_Month(OccurredAtUtc);");
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_Target_Occurred
ON dbo.AuditLog (Target ASC, OccurredAtUtc DESC)
WHERE Target IS NOT NULL
ON ps_AuditLog_Month(OccurredAtUtc);");
// The EventId uniqueness index supports InsertIfNotExistsAsync
// (M1-T8). It is INTENTIONALLY non-aligned (placed on [PRIMARY]
// rather than ps_AuditLog_Month).
//
// SQL Server's rule for unique partition-aligned indexes is that the
// partition column must be a SUBSET of the index key. Including
// OccurredAtUtc in the key would change the uniqueness semantics
// from "EventId is globally unique" to "(EventId, OccurredAtUtc)
// is unique", which is the same guarantee the composite PK already
// provides — it would not give us single-column EventId uniqueness.
//
// Trade-off: a non-aligned index disables ALTER TABLE … SWITCH
// PARTITION on AuditLog. The M1 purge story (M2/M3) uses an
// explicit rebuild path that drops and re-creates this index
// around the switch, so the aligned-indexes pattern is preserved
// for partition switching at purge time.
migrationBuilder.Sql(@"
CREATE UNIQUE NONCLUSTERED INDEX UX_AuditLog_EventId
ON dbo.AuditLog (EventId)
ON [PRIMARY];");
// 5) DB roles. Both definitions are idempotent so the migration is
// safe to re-apply against a database that already has the role.
// The DENY UPDATE / DENY DELETE on the writer role is deliberate —
// a future db_datawriter membership cannot quietly re-enable
// mutation because DENY outranks GRANT.
migrationBuilder.Sql(@"
IF DATABASE_PRINCIPAL_ID('scadabridge_audit_writer') IS NULL
EXEC sp_executesql N'CREATE ROLE scadabridge_audit_writer';
GRANT INSERT ON dbo.AuditLog TO scadabridge_audit_writer;
GRANT SELECT ON dbo.AuditLog TO scadabridge_audit_writer;
DENY UPDATE ON dbo.AuditLog TO scadabridge_audit_writer;
DENY DELETE ON dbo.AuditLog TO scadabridge_audit_writer;");
migrationBuilder.Sql(@"
IF DATABASE_PRINCIPAL_ID('scadabridge_audit_purger') IS NULL
EXEC sp_executesql N'CREATE ROLE scadabridge_audit_purger';
GRANT SELECT ON dbo.AuditLog TO scadabridge_audit_purger;
GRANT ALTER ON SCHEMA::dbo TO scadabridge_audit_purger;");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Drop in reverse dependency order so each statement's prerequisites
// still exist when it runs. Each DROP is guarded so a partial Up()
// (or a re-applied Down()) cannot fail on missing objects.
migrationBuilder.Sql(@"
IF DATABASE_PRINCIPAL_ID('scadabridge_audit_purger') IS NOT NULL
EXEC sp_executesql N'DROP ROLE scadabridge_audit_purger';
IF DATABASE_PRINCIPAL_ID('scadabridge_audit_writer') IS NOT NULL
EXEC sp_executesql N'DROP ROLE scadabridge_audit_writer';");
// Indexes are dropped implicitly when the table goes away, but
// dropping them explicitly first keeps the Down() statement self-
// describing and mirrors the Up() shape.
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'UX_AuditLog_EventId' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX UX_AuditLog_EventId ON dbo.AuditLog;
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_Target_Occurred' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_Target_Occurred ON dbo.AuditLog;
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_Channel_Status_Occurred' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_Channel_Status_Occurred ON dbo.AuditLog;
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_CorrelationId' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_CorrelationId ON dbo.AuditLog;
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_Site_Occurred' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_Site_Occurred ON dbo.AuditLog;
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_OccurredAtUtc' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_OccurredAtUtc ON dbo.AuditLog;");
migrationBuilder.Sql(@"
IF OBJECT_ID('dbo.AuditLog', 'U') IS NOT NULL
DROP TABLE dbo.AuditLog;");
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM sys.partition_schemes WHERE name = 'ps_AuditLog_Month')
DROP PARTITION SCHEME ps_AuditLog_Month;
IF EXISTS (SELECT 1 FROM sys.partition_functions WHERE name = 'pf_AuditLog_Month')
DROP PARTITION FUNCTION pf_AuditLog_Month;");
}
}
}
@@ -0,0 +1,56 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddSiteCallsTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SiteCalls",
columns: table => new
{
TrackedOperationId = table.Column<string>(type: "varchar(36)", unicode: false, maxLength: 36, nullable: false),
Channel = table.Column<string>(type: "varchar(32)", unicode: false, maxLength: 32, nullable: false),
Target = table.Column<string>(type: "varchar(256)", unicode: false, maxLength: 256, nullable: false),
SourceSite = table.Column<string>(type: "varchar(64)", unicode: false, maxLength: 64, nullable: false),
Status = table.Column<string>(type: "varchar(32)", unicode: false, maxLength: 32, nullable: false),
RetryCount = table.Column<int>(type: "int", nullable: false),
LastError = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
HttpStatus = table.Column<int>(type: "int", nullable: true),
CreatedAtUtc = table.Column<DateTime>(type: "datetime2", nullable: false),
UpdatedAtUtc = table.Column<DateTime>(type: "datetime2", nullable: false),
TerminalAtUtc = table.Column<DateTime>(type: "datetime2", nullable: true),
IngestedAtUtc = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteCalls", x => x.TrackedOperationId);
});
migrationBuilder.CreateIndex(
name: "IX_SiteCalls_Source_Created",
table: "SiteCalls",
columns: new[] { "SourceSite", "CreatedAtUtc" },
descending: new[] { false, true });
migrationBuilder.CreateIndex(
name: "IX_SiteCalls_Status_Updated",
table: "SiteCalls",
columns: new[] { "Status", "UpdatedAtUtc" },
descending: new[] { false, true });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SiteCalls");
}
}
}
@@ -0,0 +1,57 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the universal <c>ExecutionId</c> correlation column to the centralized
/// <c>AuditLog</c> table (#23). <c>ExecutionId</c> identifies the originating
/// script execution / inbound request and is distinct from the per-operation
/// <c>CorrelationId</c>.
///
/// The change is purely additive:
/// 1. <c>ExecutionId uniqueidentifier NULL</c> is added with no default, so the
/// operation is a metadata-only <c>ALTER TABLE … ADD</c> — it does NOT
/// rewrite the monthly-partitioned <c>AuditLog</c> table, and historical
/// rows stay <c>NULL</c> (no backfill).
/// 2. <c>IX_AuditLog_Execution</c> is created via raw SQL so it lands on the
/// <c>ps_AuditLog_Month(OccurredAtUtc)</c> partition scheme, matching every
/// other <c>IX_AuditLog_*</c> index. Keeping it partition-aligned preserves
/// the partition-switch purge path (see AuditLogRepository.SwitchOutPartitionAsync).
/// </summary>
public partial class AddAuditLogExecutionId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "ExecutionId",
table: "AuditLog",
type: "uniqueidentifier",
nullable: true);
// Raw SQL so the index is created on the partition scheme — EF's
// CreateIndex cannot express the ON ps_AuditLog_Month(OccurredAtUtc)
// clause. Mirrors IX_AuditLog_CorrelationId (filtered, aligned).
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_Execution
ON dbo.AuditLog (ExecutionId)
WHERE ExecutionId IS NOT NULL
ON ps_AuditLog_Month(OccurredAtUtc);");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_Execution' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_Execution ON dbo.AuditLog;");
migrationBuilder.DropColumn(
name: "ExecutionId",
table: "AuditLog");
}
}
}
@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the <c>OriginExecutionId</c> correlation column to the central
/// <c>Notifications</c> table (#21). It carries the originating script execution's
/// <c>ExecutionId</c> from the site so the dispatcher can echo it onto the
/// <c>NotifyDeliver</c> audit rows (#23), linking them to the site's <c>NotifySend</c>
/// row for the same run.
///
/// The change is purely additive: <c>OriginExecutionId uniqueidentifier NULL</c> is
/// added with no default, so the operation is a metadata-only <c>ALTER TABLE … ADD</c>.
/// Unlike <c>AuditLog</c>, the <c>Notifications</c> table is NOT partitioned, so a
/// plain <c>ADD</c> is fine. No index is created — the column is never a query
/// predicate, only copied onto audit events. Historical rows stay <c>NULL</c>.
/// </summary>
public partial class AddNotificationOriginExecutionId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "OriginExecutionId",
table: "Notifications",
type: "uniqueidentifier",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OriginExecutionId",
table: "Notifications");
}
}
}
@@ -0,0 +1,59 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the <c>ParentExecutionId</c> correlation column to the centralized
/// <c>AuditLog</c> table (#23). <c>ParentExecutionId</c> carries the
/// <c>ExecutionId</c> of the execution that spawned this run, letting a
/// spawned execution point back at its spawner — a sibling to the universal
/// per-run <c>ExecutionId</c>.
///
/// The change is purely additive:
/// 1. <c>ParentExecutionId uniqueidentifier NULL</c> is added with no default,
/// so the operation is a metadata-only <c>ALTER TABLE … ADD</c> — it does
/// NOT rewrite the monthly-partitioned <c>AuditLog</c> table, and
/// historical rows stay <c>NULL</c> (no backfill).
/// 2. <c>IX_AuditLog_ParentExecution</c> is created via raw SQL so it lands on
/// the <c>ps_AuditLog_Month(OccurredAtUtc)</c> partition scheme, matching
/// every other <c>IX_AuditLog_*</c> index. Keeping it partition-aligned
/// preserves the partition-switch purge path (see
/// AuditLogRepository.SwitchOutPartitionAsync).
/// </summary>
public partial class AddAuditLogParentExecutionId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "ParentExecutionId",
table: "AuditLog",
type: "uniqueidentifier",
nullable: true);
// Raw SQL so the index is created on the partition scheme — EF's
// CreateIndex cannot express the ON ps_AuditLog_Month(OccurredAtUtc)
// clause. Mirrors IX_AuditLog_Execution (filtered, aligned).
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_ParentExecution
ON dbo.AuditLog (ParentExecutionId)
WHERE ParentExecutionId IS NOT NULL
ON ps_AuditLog_Month(OccurredAtUtc);");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_ParentExecution' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_ParentExecution ON dbo.AuditLog;");
migrationBuilder.DropColumn(
name: "ParentExecutionId",
table: "AuditLog");
}
}
}
@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the <c>OriginParentExecutionId</c> correlation column to the central
/// <c>Notifications</c> table (#21). It carries the originating routed script
/// execution's <c>ParentExecutionId</c> from the site so the dispatcher can echo it
/// onto the <c>NotifyDeliver</c> audit rows (#23), linking them to the routed run's
/// parent. Sibling of <c>OriginExecutionId</c>.
///
/// The change is purely additive: <c>OriginParentExecutionId uniqueidentifier NULL</c>
/// is added with no default, so the operation is a metadata-only
/// <c>ALTER TABLE … ADD</c>. Unlike <c>AuditLog</c>, the <c>Notifications</c> table is
/// NOT partitioned, so a plain <c>ADD</c> is fine. No index is created — the column is
/// never a query predicate, only copied onto audit events. Historical rows stay
/// <c>NULL</c>.
/// </summary>
public partial class AddNotificationOriginParentExecutionId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "OriginParentExecutionId",
table: "Notifications",
type: "uniqueidentifier",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OriginParentExecutionId",
table: "Notifications");
}
}
}
@@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the <c>SourceNode</c> column to the centralized <c>AuditLog</c> table (#23,
/// SourceNode-stamping). <c>SourceNode</c> identifies the cluster node that produced the
/// audit row (e.g. <c>node-a</c>, <c>central-a</c>) — ASCII-only, so <c>varchar(64)</c>
/// not <c>nvarchar</c>. <c>NULL</c> is valid (reconciled rows from a retired node,
/// central direct-write rows pre-this-feature).
///
/// The change is purely additive:
/// 1. <c>SourceNode varchar(64) NULL</c> is added with no default, so the operation
/// is a metadata-only <c>ALTER TABLE … ADD</c> — it does NOT rewrite the
/// monthly-partitioned <c>AuditLog</c> table, and historical rows stay <c>NULL</c>.
/// 2. <c>IX_AuditLog_Node_Occurred (SourceNode, OccurredAtUtc)</c> is created via raw
/// SQL so it lands on the <c>ps_AuditLog_Month(OccurredAtUtc)</c> partition scheme,
/// matching every other <c>IX_AuditLog_*</c> index. Keeping it partition-aligned
/// preserves the partition-switch purge path (see
/// AuditLogRepository.SwitchOutPartitionAsync).
/// </summary>
public partial class AddAuditLogSourceNode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SourceNode",
table: "AuditLog",
type: "varchar(64)",
unicode: false,
maxLength: 64,
nullable: true);
// Raw SQL so the index is created on the partition scheme — EF's
// CreateIndex cannot express the ON ps_AuditLog_Month(OccurredAtUtc)
// clause. Mirrors IX_AuditLog_ParentExecution (aligned, unfiltered here:
// NULL SourceNode is a legitimate query target, e.g. "rows produced
// before stamping shipped" — no HasFilter on this index).
migrationBuilder.Sql(@"
CREATE NONCLUSTERED INDEX IX_AuditLog_Node_Occurred
ON dbo.AuditLog (SourceNode, OccurredAtUtc)
ON ps_AuditLog_Month(OccurredAtUtc);");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_AuditLog_Node_Occurred' AND object_id = OBJECT_ID('dbo.AuditLog'))
DROP INDEX IX_AuditLog_Node_Occurred ON dbo.AuditLog;");
migrationBuilder.DropColumn(
name: "SourceNode",
table: "AuditLog");
}
}
}
@@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the <c>SourceNode</c> column to the central <c>Notifications</c> table (#21,
/// SourceNode-stamping). <c>SourceNode</c> identifies the cluster node that produced the
/// notification (e.g. <c>node-a</c>, <c>central-a</c>) — ASCII-only, so <c>varchar(64)</c>
/// not <c>nvarchar</c>. <c>NULL</c> is valid for rows that pre-date this feature.
///
/// The change is purely additive: <c>SourceNode varchar(64) NULL</c> is added with no
/// default, so the operation is a metadata-only <c>ALTER TABLE … ADD</c>. Unlike
/// <c>AuditLog</c>, the <c>Notifications</c> table is NOT partitioned, so a plain
/// <c>ADD</c> is fine. No index — Notification Outbox KPIs are per-site, not per-node,
/// on this table; <c>SourceNode</c> is only echoed onto <c>NotifyDeliver</c> audit rows
/// (#23) for cross-row correlation. Historical rows stay <c>NULL</c>.
/// </summary>
public partial class AddNotificationSourceNode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SourceNode",
table: "Notifications",
type: "varchar(64)",
unicode: false,
maxLength: 64,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SourceNode",
table: "Notifications");
}
}
}
@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <summary>
/// Adds the <c>SourceNode</c> column to the central <c>SiteCalls</c> table (#22,
/// SourceNode-stamping). <c>SourceNode</c> identifies the cluster node that produced the
/// row (e.g. <c>node-a</c>, <c>central-a</c>) — ASCII-only, so <c>varchar(64)</c> not
/// <c>nvarchar</c>. <c>NULL</c> is valid for rows that pre-date this feature and for
/// reconciled rows from a retired node.
///
/// The change is purely additive: <c>SourceNode varchar(64) NULL</c> is added with no
/// default, so the operation is a metadata-only <c>ALTER TABLE … ADD</c>. The
/// <c>SiteCalls</c> table is NOT partitioned (operational state, not audit), so a plain
/// <c>ADD</c> is fine. No index — Site Call Audit KPIs are per-site, not per-node, on
/// this table; <c>SourceNode</c> is operational metadata, never a query predicate here.
/// Historical rows stay <c>NULL</c>.
/// </summary>
public partial class AddSiteCallSourceNode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SourceNode",
table: "SiteCalls",
type: "varchar(64)",
unicode: false,
maxLength: 64,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SourceNode",
table: "SiteCalls");
}
}
}
@@ -0,0 +1,38 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddBundleImportIdToAuditLog : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "BundleImportId",
table: "AuditLogEntries",
type: "uniqueidentifier",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_BundleImportId",
table: "AuditLogEntries",
column: "BundleImportId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_AuditLogEntries_BundleImportId",
table: "AuditLogEntries");
migrationBuilder.DropColumn(
name: "BundleImportId",
table: "AuditLogEntries");
}
}
}