v3 batch1 WP1: greenfield DbContext + V3Initial squash + seeds + tests
DbContext (OtOpcUaConfigDbContext): - Drop Namespace/EquipmentImportBatch/EquipmentImportRow DbSets + Configure methods. - Add RawFolder/TagGroup/UnsTagReference DbSets + Configure methods with sibling-name filtered unique indexes over nullable parents (two-filtered pattern per level). - Reshape DriverInstance (RawFolderId + UX_DriverInstance_Folder_Name/_ClusterRoot_Name), Tag (required DeviceId, nullable TagGroupId, UX_Tag_Group_Name/_Device_Name, IX_Tag_Device), Equipment (drop DriverInstanceId/DeviceId + IX_Equipment_Driver), Device (UX_Device_Driver_Name). Migration squash -> V3Initial: - Delete the whole Migrations/ folder; scaffold one V3Initial (applies clean to a fresh DB). - Port the hand-written procs + AuthorizationGrants into V3Initial.StoredProcedures.cs. The v1 generation-model procs (ConfigGeneration/GenerationId, dead since V2HostingAlignment) are rewritten as v3-schema-valid retirement stubs that preserve the two-role EXECUTE-grant surface + AuthorizationTests behaviors; sp_ComputeGenerationDiff now references the v3 tables; sp_ReleaseExternalIdReservation ported verbatim. Compile-fixes for the build gate (Wave-C-owned, minimal + TODO(v3 WP4) markers): - DraftSnapshot/DraftSnapshotFactory: drop the retired Namespaces list. - DraftValidator: delete namespace-binding + Galaxy-FullName rules; collision rule now VirtualTag-only. Seeds -> v3 schema: - seed-clusters.sql summary SELECTs; all 5 scripts/smoke/seed-*.sql rewritten (DriverInstance RawFolderId, Device+DeviceConfig endpoint, Tag DeviceId, UnsTagReference; no ConfigGeneration/ Namespace/sp_PublishGeneration). Verified: all seeds apply clean against a fresh V3Initial DB. Tests (Configuration.Tests, 107 pass): - SchemaComplianceTests rewritten to v3 tables + filtered indexes. - New RawSchemaIntegrityTests: DB-enforced sibling-name uniqueness per raw level + UnsTagReference (Equipment,Tag) uniqueness. - Delete DraftValidatorGalaxyFullNameCorpusTests (WP2/WP6 replaces); fix DraftValidatorTests + DraftSnapshotFactoryTests to v3 shapes; repoint scripting-migration existence test to V3Initial.
This commit is contained in:
Generated
-1208
File diff suppressed because it is too large
Load Diff
@@ -1,811 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialSchema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConfigAuditLog",
|
||||
columns: table => new
|
||||
{
|
||||
AuditId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Timestamp = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
Principal = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
EventType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
NodeId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: true),
|
||||
DetailsJson = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConfigAuditLog", x => x.AuditId);
|
||||
table.CheckConstraint("CK_ConfigAuditLog_DetailsJson_IsJson", "DetailsJson IS NULL OR ISJSON(DetailsJson) = 1");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExternalIdReservation",
|
||||
columns: table => new
|
||||
{
|
||||
ReservationId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
Kind = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
EquipmentUuid = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
FirstPublishedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
FirstPublishedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
LastPublishedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
ReleasedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
ReleasedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
ReleaseReason = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExternalIdReservation", x => x.ReservationId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerCluster",
|
||||
columns: table => new
|
||||
{
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Enterprise = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Site = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
NodeCount = table.Column<byte>(type: "tinyint", nullable: false),
|
||||
RedundancyMode = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
Notes = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
ModifiedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
ModifiedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ServerCluster", x => x.ClusterId);
|
||||
table.CheckConstraint("CK_ServerCluster_RedundancyMode_NodeCount", "((NodeCount = 1 AND RedundancyMode = 'None') OR (NodeCount = 2 AND RedundancyMode IN ('Warm', 'Hot')))");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClusterNode",
|
||||
columns: table => new
|
||||
{
|
||||
NodeId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
RedundancyRole = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
Host = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
|
||||
OpcUaPort = table.Column<int>(type: "int", nullable: false),
|
||||
DashboardPort = table.Column<int>(type: "int", nullable: false),
|
||||
ApplicationUri = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
ServiceLevelBase = table.Column<byte>(type: "tinyint", nullable: false),
|
||||
DriverConfigOverridesJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
LastSeenAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClusterNode", x => x.NodeId);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClusterNode_ServerCluster_ClusterId",
|
||||
column: x => x.ClusterId,
|
||||
principalTable: "ServerCluster",
|
||||
principalColumn: "ClusterId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConfigGeneration",
|
||||
columns: table => new
|
||||
{
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Status = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
ParentGenerationId = table.Column<long>(type: "bigint", nullable: true),
|
||||
PublishedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
PublishedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
Notes = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConfigGeneration", x => x.GenerationId);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConfigGeneration_ConfigGeneration_ParentGenerationId",
|
||||
column: x => x.ParentGenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConfigGeneration_ServerCluster_ClusterId",
|
||||
column: x => x.ClusterId,
|
||||
principalTable: "ServerCluster",
|
||||
principalColumn: "ClusterId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClusterNodeCredential",
|
||||
columns: table => new
|
||||
{
|
||||
CredentialId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
NodeId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Kind = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
RotatedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()"),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClusterNodeCredential", x => x.CredentialId);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClusterNodeCredential_ClusterNode_NodeId",
|
||||
column: x => x.NodeId,
|
||||
principalTable: "ClusterNode",
|
||||
principalColumn: "NodeId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClusterNodeGenerationState",
|
||||
columns: table => new
|
||||
{
|
||||
NodeId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CurrentGenerationId = table.Column<long>(type: "bigint", nullable: true),
|
||||
LastAppliedAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
LastAppliedStatus = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: true),
|
||||
LastAppliedError = table.Column<string>(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
|
||||
LastSeenAt = table.Column<DateTime>(type: "datetime2(3)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClusterNodeGenerationState", x => x.NodeId);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClusterNodeGenerationState_ClusterNode_NodeId",
|
||||
column: x => x.NodeId,
|
||||
principalTable: "ClusterNode",
|
||||
principalColumn: "NodeId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClusterNodeGenerationState_ConfigGeneration_CurrentGenerationId",
|
||||
column: x => x.CurrentGenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Device",
|
||||
columns: table => new
|
||||
{
|
||||
DeviceRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
DeviceConfig = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Device", x => x.DeviceRowId);
|
||||
table.CheckConstraint("CK_Device_DeviceConfig_IsJson", "ISJSON(DeviceConfig) = 1");
|
||||
table.ForeignKey(
|
||||
name: "FK_Device_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DriverInstance",
|
||||
columns: table => new
|
||||
{
|
||||
DriverInstanceRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
NamespaceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
DriverType = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
DriverConfig = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DriverInstance", x => x.DriverInstanceRowId);
|
||||
table.CheckConstraint("CK_DriverInstance_DriverConfig_IsJson", "ISJSON(DriverConfig) = 1");
|
||||
table.ForeignKey(
|
||||
name: "FK_DriverInstance_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_DriverInstance_ServerCluster_ClusterId",
|
||||
column: x => x.ClusterId,
|
||||
principalTable: "ServerCluster",
|
||||
principalColumn: "ClusterId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Equipment",
|
||||
columns: table => new
|
||||
{
|
||||
EquipmentRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
EquipmentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
EquipmentUuid = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
UnsLineId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
MachineCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
ZTag = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
SAPID = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
Manufacturer = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
Model = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
SerialNumber = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
HardwareRevision = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: true),
|
||||
SoftwareRevision = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: true),
|
||||
YearOfConstruction = table.Column<short>(type: "smallint", nullable: true),
|
||||
AssetLocation = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
ManufacturerUri = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
DeviceManualUri = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
EquipmentClassRef = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Equipment", x => x.EquipmentRowId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Equipment_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Namespace",
|
||||
columns: table => new
|
||||
{
|
||||
NamespaceRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
NamespaceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Kind = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
NamespaceUri = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
Notes = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Namespace", x => x.NamespaceRowId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Namespace_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Namespace_ServerCluster_ClusterId",
|
||||
column: x => x.ClusterId,
|
||||
principalTable: "ServerCluster",
|
||||
principalColumn: "ClusterId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "NodeAcl",
|
||||
columns: table => new
|
||||
{
|
||||
NodeAclRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
NodeAclId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
LdapGroup = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
ScopeKind = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
ScopeId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
PermissionFlags = table.Column<int>(type: "int", nullable: false),
|
||||
Notes = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_NodeAcl", x => x.NodeAclRowId);
|
||||
table.ForeignKey(
|
||||
name: "FK_NodeAcl_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PollGroup",
|
||||
columns: table => new
|
||||
{
|
||||
PollGroupRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
PollGroupId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
IntervalMs = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PollGroup", x => x.PollGroupRowId);
|
||||
table.CheckConstraint("CK_PollGroup_IntervalMs_Min", "IntervalMs >= 50");
|
||||
table.ForeignKey(
|
||||
name: "FK_PollGroup_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tag",
|
||||
columns: table => new
|
||||
{
|
||||
TagRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
TagId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
EquipmentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
FolderPath = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
DataType = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
AccessLevel = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
WriteIdempotent = table.Column<bool>(type: "bit", nullable: false),
|
||||
PollGroupId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
TagConfig = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tag", x => x.TagRowId);
|
||||
table.CheckConstraint("CK_Tag_TagConfig_IsJson", "ISJSON(TagConfig) = 1");
|
||||
table.ForeignKey(
|
||||
name: "FK_Tag_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UnsArea",
|
||||
columns: table => new
|
||||
{
|
||||
UnsAreaRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
UnsAreaId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Notes = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UnsArea", x => x.UnsAreaRowId);
|
||||
table.ForeignKey(
|
||||
name: "FK_UnsArea_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_UnsArea_ServerCluster_ClusterId",
|
||||
column: x => x.ClusterId,
|
||||
principalTable: "ServerCluster",
|
||||
principalColumn: "ClusterId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UnsLine",
|
||||
columns: table => new
|
||||
{
|
||||
UnsLineRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
UnsLineId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
UnsAreaId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Notes = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UnsLine", x => x.UnsLineRowId);
|
||||
table.ForeignKey(
|
||||
name: "FK_UnsLine_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ClusterNode_ApplicationUri",
|
||||
table: "ClusterNode",
|
||||
column: "ApplicationUri",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ClusterNode_Primary_Per_Cluster",
|
||||
table: "ClusterNode",
|
||||
column: "ClusterId",
|
||||
unique: true,
|
||||
filter: "[RedundancyRole] = 'Primary'");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClusterNodeCredential_NodeId",
|
||||
table: "ClusterNodeCredential",
|
||||
columns: new[] { "NodeId", "Enabled" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ClusterNodeCredential_Value",
|
||||
table: "ClusterNodeCredential",
|
||||
columns: new[] { "Kind", "Value" },
|
||||
unique: true,
|
||||
filter: "[Enabled] = 1");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClusterNodeGenerationState_Generation",
|
||||
table: "ClusterNodeGenerationState",
|
||||
column: "CurrentGenerationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigAuditLog_Cluster_Time",
|
||||
table: "ConfigAuditLog",
|
||||
columns: new[] { "ClusterId", "Timestamp" },
|
||||
descending: new[] { false, true });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigAuditLog_Generation",
|
||||
table: "ConfigAuditLog",
|
||||
column: "GenerationId",
|
||||
filter: "[GenerationId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigGeneration_Cluster_Published",
|
||||
table: "ConfigGeneration",
|
||||
columns: new[] { "ClusterId", "Status", "GenerationId" },
|
||||
descending: new[] { false, false, true })
|
||||
.Annotation("SqlServer:Include", new[] { "PublishedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigGeneration_ParentGenerationId",
|
||||
table: "ConfigGeneration",
|
||||
column: "ParentGenerationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ConfigGeneration_Draft_Per_Cluster",
|
||||
table: "ConfigGeneration",
|
||||
column: "ClusterId",
|
||||
unique: true,
|
||||
filter: "[Status] = 'Draft'");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Device_Generation_Driver",
|
||||
table: "Device",
|
||||
columns: new[] { "GenerationId", "DriverInstanceId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Device_Generation_LogicalId",
|
||||
table: "Device",
|
||||
columns: new[] { "GenerationId", "DeviceId" },
|
||||
unique: true,
|
||||
filter: "[DeviceId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DriverInstance_ClusterId",
|
||||
table: "DriverInstance",
|
||||
column: "ClusterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DriverInstance_Generation_Cluster",
|
||||
table: "DriverInstance",
|
||||
columns: new[] { "GenerationId", "ClusterId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DriverInstance_Generation_Namespace",
|
||||
table: "DriverInstance",
|
||||
columns: new[] { "GenerationId", "NamespaceId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_DriverInstance_Generation_LogicalId",
|
||||
table: "DriverInstance",
|
||||
columns: new[] { "GenerationId", "DriverInstanceId" },
|
||||
unique: true,
|
||||
filter: "[DriverInstanceId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Equipment_Generation_Driver",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "DriverInstanceId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Equipment_Generation_Line",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "UnsLineId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Equipment_Generation_MachineCode",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "MachineCode" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Equipment_Generation_SAPID",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "SAPID" },
|
||||
filter: "[SAPID] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Equipment_Generation_ZTag",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "ZTag" },
|
||||
filter: "[ZTag] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Equipment_Generation_LinePath",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "UnsLineId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Equipment_Generation_LogicalId",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "EquipmentId" },
|
||||
unique: true,
|
||||
filter: "[EquipmentId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Equipment_Generation_Uuid",
|
||||
table: "Equipment",
|
||||
columns: new[] { "GenerationId", "EquipmentUuid" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExternalIdReservation_Equipment",
|
||||
table: "ExternalIdReservation",
|
||||
column: "EquipmentUuid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ExternalIdReservation_KindValue_Active",
|
||||
table: "ExternalIdReservation",
|
||||
columns: new[] { "Kind", "Value" },
|
||||
unique: true,
|
||||
filter: "[ReleasedAt] IS NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Namespace_ClusterId",
|
||||
table: "Namespace",
|
||||
column: "ClusterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Namespace_Generation_Cluster",
|
||||
table: "Namespace",
|
||||
columns: new[] { "GenerationId", "ClusterId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Namespace_Generation_Cluster_Kind",
|
||||
table: "Namespace",
|
||||
columns: new[] { "GenerationId", "ClusterId", "Kind" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Namespace_Generation_LogicalId",
|
||||
table: "Namespace",
|
||||
columns: new[] { "GenerationId", "NamespaceId" },
|
||||
unique: true,
|
||||
filter: "[NamespaceId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Namespace_Generation_LogicalId_Cluster",
|
||||
table: "Namespace",
|
||||
columns: new[] { "GenerationId", "NamespaceId", "ClusterId" },
|
||||
unique: true,
|
||||
filter: "[NamespaceId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Namespace_Generation_NamespaceUri",
|
||||
table: "Namespace",
|
||||
columns: new[] { "GenerationId", "NamespaceUri" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NodeAcl_Generation_Cluster",
|
||||
table: "NodeAcl",
|
||||
columns: new[] { "GenerationId", "ClusterId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NodeAcl_Generation_Group",
|
||||
table: "NodeAcl",
|
||||
columns: new[] { "GenerationId", "LdapGroup" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NodeAcl_Generation_Scope",
|
||||
table: "NodeAcl",
|
||||
columns: new[] { "GenerationId", "ScopeKind", "ScopeId" },
|
||||
filter: "[ScopeId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_NodeAcl_Generation_GroupScope",
|
||||
table: "NodeAcl",
|
||||
columns: new[] { "GenerationId", "ClusterId", "LdapGroup", "ScopeKind", "ScopeId" },
|
||||
unique: true,
|
||||
filter: "[ScopeId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_NodeAcl_Generation_LogicalId",
|
||||
table: "NodeAcl",
|
||||
columns: new[] { "GenerationId", "NodeAclId" },
|
||||
unique: true,
|
||||
filter: "[NodeAclId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PollGroup_Generation_Driver",
|
||||
table: "PollGroup",
|
||||
columns: new[] { "GenerationId", "DriverInstanceId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_PollGroup_Generation_LogicalId",
|
||||
table: "PollGroup",
|
||||
columns: new[] { "GenerationId", "PollGroupId" },
|
||||
unique: true,
|
||||
filter: "[PollGroupId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerCluster_Site",
|
||||
table: "ServerCluster",
|
||||
column: "Site");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ServerCluster_Name",
|
||||
table: "ServerCluster",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tag_Generation_Driver_Device",
|
||||
table: "Tag",
|
||||
columns: new[] { "GenerationId", "DriverInstanceId", "DeviceId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tag_Generation_Equipment",
|
||||
table: "Tag",
|
||||
columns: new[] { "GenerationId", "EquipmentId" },
|
||||
filter: "[EquipmentId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Tag_Generation_EquipmentPath",
|
||||
table: "Tag",
|
||||
columns: new[] { "GenerationId", "EquipmentId", "Name" },
|
||||
unique: true,
|
||||
filter: "[EquipmentId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Tag_Generation_FolderPath",
|
||||
table: "Tag",
|
||||
columns: new[] { "GenerationId", "DriverInstanceId", "FolderPath", "Name" },
|
||||
unique: true,
|
||||
filter: "[EquipmentId] IS NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Tag_Generation_LogicalId",
|
||||
table: "Tag",
|
||||
columns: new[] { "GenerationId", "TagId" },
|
||||
unique: true,
|
||||
filter: "[TagId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UnsArea_ClusterId",
|
||||
table: "UnsArea",
|
||||
column: "ClusterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UnsArea_Generation_Cluster",
|
||||
table: "UnsArea",
|
||||
columns: new[] { "GenerationId", "ClusterId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_UnsArea_Generation_ClusterName",
|
||||
table: "UnsArea",
|
||||
columns: new[] { "GenerationId", "ClusterId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_UnsArea_Generation_LogicalId",
|
||||
table: "UnsArea",
|
||||
columns: new[] { "GenerationId", "UnsAreaId" },
|
||||
unique: true,
|
||||
filter: "[UnsAreaId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UnsLine_Generation_Area",
|
||||
table: "UnsLine",
|
||||
columns: new[] { "GenerationId", "UnsAreaId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_UnsLine_Generation_AreaName",
|
||||
table: "UnsLine",
|
||||
columns: new[] { "GenerationId", "UnsAreaId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_UnsLine_Generation_LogicalId",
|
||||
table: "UnsLine",
|
||||
columns: new[] { "GenerationId", "UnsLineId" },
|
||||
unique: true,
|
||||
filter: "[UnsLineId] IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClusterNodeCredential");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClusterNodeGenerationState");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConfigAuditLog");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Device");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DriverInstance");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Equipment");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExternalIdReservation");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Namespace");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "NodeAcl");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PollGroup");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tag");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UnsArea");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UnsLine");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClusterNode");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConfigGeneration");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerCluster");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-1208
File diff suppressed because it is too large
Load Diff
-510
@@ -1,510 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Stored procedures per <c>config-db-schema.md §"Stored Procedures"</c>. All node + admin DB
|
||||
/// access funnels through these — direct table writes are revoked in the AuthorizationGrants
|
||||
/// migration that follows. CREATE OR ALTER style so procs version with the schema.
|
||||
/// </summary>
|
||||
public partial class StoredProcedures : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.GetCurrentGenerationForCluster);
|
||||
migrationBuilder.Sql(Procs.GetGenerationContent);
|
||||
migrationBuilder.Sql(Procs.RegisterNodeGenerationApplied);
|
||||
migrationBuilder.Sql(Procs.ValidateDraft);
|
||||
migrationBuilder.Sql(Procs.PublishGeneration);
|
||||
migrationBuilder.Sql(Procs.RollbackToGeneration);
|
||||
migrationBuilder.Sql(Procs.ComputeGenerationDiff);
|
||||
migrationBuilder.Sql(Procs.ReleaseExternalIdReservation);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
foreach (var name in new[]
|
||||
{
|
||||
"sp_ReleaseExternalIdReservation", "sp_ComputeGenerationDiff", "sp_RollbackToGeneration",
|
||||
"sp_PublishGeneration", "sp_ValidateDraft", "sp_RegisterNodeGenerationApplied",
|
||||
"sp_GetGenerationContent", "sp_GetCurrentGenerationForCluster",
|
||||
})
|
||||
{
|
||||
migrationBuilder.Sql($"IF OBJECT_ID(N'dbo.{name}', N'P') IS NOT NULL DROP PROCEDURE dbo.{name};");
|
||||
}
|
||||
}
|
||||
|
||||
private static class Procs
|
||||
{
|
||||
public const string GetCurrentGenerationForCluster = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_GetCurrentGenerationForCluster
|
||||
@NodeId nvarchar(64),
|
||||
@ClusterId nvarchar(64)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @Caller nvarchar(128) = SUSER_SNAME();
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ClusterNodeCredential
|
||||
WHERE NodeId = @NodeId AND Value = @Caller AND Enabled = 1)
|
||||
BEGIN
|
||||
RAISERROR('Unauthorized: caller %s is not bound to NodeId %s', 16, 1, @Caller, @NodeId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ClusterNode
|
||||
WHERE NodeId = @NodeId AND ClusterId = @ClusterId AND Enabled = 1)
|
||||
BEGIN
|
||||
RAISERROR('Forbidden: NodeId %s does not belong to ClusterId %s', 16, 1, @NodeId, @ClusterId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
SELECT TOP 1 GenerationId, ClusterId, Status, PublishedAt, PublishedBy, Notes
|
||||
FROM dbo.ConfigGeneration
|
||||
WHERE ClusterId = @ClusterId AND Status = 'Published'
|
||||
ORDER BY GenerationId DESC;
|
||||
END
|
||||
";
|
||||
|
||||
public const string GetGenerationContent = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_GetGenerationContent
|
||||
@NodeId nvarchar(64),
|
||||
@GenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @Caller nvarchar(128) = SUSER_SNAME();
|
||||
DECLARE @ClusterId nvarchar(64);
|
||||
|
||||
SELECT @ClusterId = ClusterId FROM dbo.ConfigGeneration WHERE GenerationId = @GenerationId;
|
||||
|
||||
IF @ClusterId IS NULL
|
||||
BEGIN
|
||||
RAISERROR('GenerationId %I64d not found', 16, 1, @GenerationId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM dbo.ClusterNodeCredential c
|
||||
JOIN dbo.ClusterNode n ON n.NodeId = c.NodeId
|
||||
WHERE c.NodeId = @NodeId AND c.Value = @Caller AND c.Enabled = 1
|
||||
AND n.ClusterId = @ClusterId AND n.Enabled = 1)
|
||||
BEGIN
|
||||
RAISERROR('Forbidden: caller %s not bound to a node in ClusterId %s', 16, 1, @Caller, @ClusterId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
SELECT * FROM dbo.Namespace WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.UnsArea WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.UnsLine WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.DriverInstance WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.Device WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.Equipment WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.PollGroup WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.Tag WHERE GenerationId = @GenerationId;
|
||||
SELECT * FROM dbo.NodeAcl WHERE GenerationId = @GenerationId;
|
||||
END
|
||||
";
|
||||
|
||||
public const string RegisterNodeGenerationApplied = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_RegisterNodeGenerationApplied
|
||||
@NodeId nvarchar(64),
|
||||
@GenerationId bigint,
|
||||
@Status nvarchar(16),
|
||||
@Error nvarchar(max) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @Caller nvarchar(128) = SUSER_SNAME();
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ClusterNodeCredential
|
||||
WHERE NodeId = @NodeId AND Value = @Caller AND Enabled = 1)
|
||||
BEGIN
|
||||
RAISERROR('Unauthorized: caller %s is not bound to NodeId %s', 16, 1, @Caller, @NodeId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
MERGE dbo.ClusterNodeGenerationState AS tgt
|
||||
USING (SELECT @NodeId AS NodeId) AS src ON tgt.NodeId = src.NodeId
|
||||
WHEN MATCHED THEN UPDATE SET
|
||||
CurrentGenerationId = @GenerationId,
|
||||
LastAppliedAt = SYSUTCDATETIME(),
|
||||
LastAppliedStatus = @Status,
|
||||
LastAppliedError = @Error,
|
||||
LastSeenAt = SYSUTCDATETIME()
|
||||
WHEN NOT MATCHED THEN INSERT
|
||||
(NodeId, CurrentGenerationId, LastAppliedAt, LastAppliedStatus, LastAppliedError, LastSeenAt)
|
||||
VALUES (@NodeId, @GenerationId, SYSUTCDATETIME(), @Status, @Error, SYSUTCDATETIME());
|
||||
|
||||
-- Build DetailsJson via STRING_ESCAPE so a @Status containing a double-quote/backslash cannot
|
||||
-- produce malformed JSON (which would fail CK_ConfigAuditLog_DetailsJson_IsJson and abort the
|
||||
-- transaction) or inject extra JSON structure into the audit record.
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, NodeId, GenerationId, DetailsJson)
|
||||
VALUES (@Caller, 'NodeApplied', @NodeId, @GenerationId,
|
||||
CONCAT('{""status"":""', STRING_ESCAPE(@Status, 'json'), '""}'));
|
||||
END
|
||||
";
|
||||
|
||||
public const string ValidateDraft = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ValidateDraft
|
||||
@DraftGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @ClusterId nvarchar(64);
|
||||
DECLARE @Status nvarchar(16);
|
||||
|
||||
SELECT @ClusterId = ClusterId, @Status = Status
|
||||
FROM dbo.ConfigGeneration WHERE GenerationId = @DraftGenerationId;
|
||||
|
||||
IF @ClusterId IS NULL
|
||||
BEGIN
|
||||
RAISERROR('GenerationId %I64d not found', 16, 1, @DraftGenerationId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF @Status <> 'Draft'
|
||||
BEGIN
|
||||
RAISERROR('GenerationId %I64d is not in Draft status (current=%s)', 16, 1, @DraftGenerationId, @Status);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM dbo.Tag t
|
||||
LEFT JOIN dbo.DriverInstance d ON d.GenerationId = t.GenerationId AND d.DriverInstanceId = t.DriverInstanceId
|
||||
WHERE t.GenerationId = @DraftGenerationId AND d.DriverInstanceId IS NULL)
|
||||
BEGIN
|
||||
RAISERROR('Draft has tags with unresolved DriverInstanceId', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM dbo.Tag t
|
||||
LEFT JOIN dbo.Device dv ON dv.GenerationId = t.GenerationId AND dv.DeviceId = t.DeviceId
|
||||
WHERE t.GenerationId = @DraftGenerationId AND t.DeviceId IS NOT NULL AND dv.DeviceId IS NULL)
|
||||
BEGIN
|
||||
RAISERROR('Draft has tags with unresolved DeviceId', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM dbo.Tag t
|
||||
LEFT JOIN dbo.PollGroup pg ON pg.GenerationId = t.GenerationId AND pg.PollGroupId = t.PollGroupId
|
||||
WHERE t.GenerationId = @DraftGenerationId AND t.PollGroupId IS NOT NULL AND pg.PollGroupId IS NULL)
|
||||
BEGIN
|
||||
RAISERROR('Draft has tags with unresolved PollGroupId', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM dbo.DriverInstance di
|
||||
JOIN dbo.Namespace ns ON ns.GenerationId = di.GenerationId AND ns.NamespaceId = di.NamespaceId
|
||||
WHERE di.GenerationId = @DraftGenerationId
|
||||
AND ns.ClusterId <> di.ClusterId)
|
||||
BEGIN
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, ClusterId, GenerationId)
|
||||
VALUES (SUSER_SNAME(), 'CrossClusterNamespaceAttempt', @ClusterId, @DraftGenerationId);
|
||||
RAISERROR('BadCrossClusterNamespaceBinding: namespace and driver must belong to the same cluster', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM dbo.Equipment draft
|
||||
JOIN dbo.Equipment prior
|
||||
ON prior.EquipmentId = draft.EquipmentId
|
||||
AND prior.EquipmentUuid <> draft.EquipmentUuid
|
||||
AND prior.GenerationId <> draft.GenerationId
|
||||
JOIN dbo.ConfigGeneration pg ON pg.GenerationId = prior.GenerationId
|
||||
WHERE draft.GenerationId = @DraftGenerationId
|
||||
AND pg.ClusterId = @ClusterId)
|
||||
BEGIN
|
||||
RAISERROR('EquipmentUuid immutability violated for an EquipmentId that existed in a prior generation', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM dbo.Equipment draft
|
||||
JOIN dbo.ExternalIdReservation r
|
||||
ON r.Kind = 'ZTag' AND r.Value = draft.ZTag AND r.ReleasedAt IS NULL
|
||||
AND r.EquipmentUuid <> draft.EquipmentUuid
|
||||
WHERE draft.GenerationId = @DraftGenerationId AND draft.ZTag IS NOT NULL)
|
||||
BEGIN
|
||||
RAISERROR('BadDuplicateExternalIdentifier: a ZTag in the draft is reserved by a different EquipmentUuid', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM dbo.Equipment draft
|
||||
JOIN dbo.ExternalIdReservation r
|
||||
ON r.Kind = 'SAPID' AND r.Value = draft.SAPID AND r.ReleasedAt IS NULL
|
||||
AND r.EquipmentUuid <> draft.EquipmentUuid
|
||||
WHERE draft.GenerationId = @DraftGenerationId AND draft.SAPID IS NOT NULL)
|
||||
BEGIN
|
||||
RAISERROR('BadDuplicateExternalIdentifier: a SAPID in the draft is reserved by a different EquipmentUuid', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
END
|
||||
";
|
||||
|
||||
public const string PublishGeneration = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_PublishGeneration
|
||||
@ClusterId nvarchar(64),
|
||||
@DraftGenerationId bigint,
|
||||
@Notes nvarchar(1024) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
-- Transaction-nesting awareness: if a caller (e.g. sp_RollbackToGeneration) already
|
||||
-- holds a transaction, we use SAVE TRANSACTION so our failure path rolls back only to
|
||||
-- the savepoint instead of issuing a bare ROLLBACK that wipes the caller's transaction
|
||||
-- (which sets @@TRANCOUNT = 0 and causes error 3902 on the caller's subsequent COMMIT).
|
||||
DECLARE @OwnsTxn bit = 0;
|
||||
DECLARE @SaveName nvarchar(32) = N'sp_PublishGeneration';
|
||||
|
||||
IF @@TRANCOUNT = 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION;
|
||||
SET @OwnsTxn = 1;
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
SAVE TRANSACTION sp_PublishGeneration;
|
||||
END
|
||||
|
||||
DECLARE @Lock nvarchar(255) = N'OtOpcUa_Publish_' + @ClusterId;
|
||||
DECLARE @LockResult int;
|
||||
EXEC @LockResult = sp_getapplock @Resource = @Lock, @LockMode = 'Exclusive', @LockTimeout = 0;
|
||||
IF @LockResult < 0
|
||||
BEGIN
|
||||
RAISERROR('PublishConflict: another publish is in progress for cluster %s', 16, 1, @ClusterId);
|
||||
IF @OwnsTxn = 1 ROLLBACK;
|
||||
ELSE ROLLBACK TRANSACTION sp_PublishGeneration;
|
||||
RETURN;
|
||||
END
|
||||
|
||||
-- sp_ValidateDraft signals every rejection with RAISERROR(..., 16, 1) — a severity-16 error is
|
||||
-- NOT batch-aborting and SET XACT_ABORT ON does not abort the transaction for it, so without a
|
||||
-- TRY/CATCH control would return here and the draft would publish despite failed validation.
|
||||
-- Catch the validation error, roll back the publish transaction (only to our savepoint when a
|
||||
-- caller owns the outer transaction), and re-raise so the caller sees the real validation failure.
|
||||
BEGIN TRY
|
||||
EXEC dbo.sp_ValidateDraft @DraftGenerationId = @DraftGenerationId;
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
IF @OwnsTxn = 1 ROLLBACK;
|
||||
ELSE ROLLBACK TRANSACTION sp_PublishGeneration;
|
||||
THROW;
|
||||
END CATCH
|
||||
|
||||
MERGE dbo.ExternalIdReservation AS tgt
|
||||
USING (
|
||||
SELECT 'ZTag' AS Kind, ZTag AS Value, EquipmentUuid
|
||||
FROM dbo.Equipment
|
||||
WHERE GenerationId = @DraftGenerationId AND ZTag IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT 'SAPID', SAPID, EquipmentUuid
|
||||
FROM dbo.Equipment
|
||||
WHERE GenerationId = @DraftGenerationId AND SAPID IS NOT NULL
|
||||
) AS src
|
||||
ON tgt.Kind = src.Kind AND tgt.Value = src.Value AND tgt.EquipmentUuid = src.EquipmentUuid
|
||||
WHEN MATCHED THEN UPDATE SET LastPublishedAt = SYSUTCDATETIME()
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
INSERT (Kind, Value, EquipmentUuid, ClusterId, FirstPublishedBy, LastPublishedAt)
|
||||
VALUES (src.Kind, src.Value, src.EquipmentUuid, @ClusterId, SUSER_SNAME(), SYSUTCDATETIME());
|
||||
|
||||
UPDATE dbo.ConfigGeneration
|
||||
SET Status = 'Superseded'
|
||||
WHERE ClusterId = @ClusterId AND Status = 'Published';
|
||||
|
||||
UPDATE dbo.ConfigGeneration
|
||||
SET Status = 'Published',
|
||||
PublishedAt = SYSUTCDATETIME(),
|
||||
PublishedBy = SUSER_SNAME(),
|
||||
Notes = ISNULL(@Notes, Notes)
|
||||
WHERE GenerationId = @DraftGenerationId AND ClusterId = @ClusterId AND Status = 'Draft';
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
BEGIN
|
||||
RAISERROR('Draft %I64d for cluster %s not in Draft status (was it already published?)', 16, 1, @DraftGenerationId, @ClusterId);
|
||||
IF @OwnsTxn = 1 ROLLBACK;
|
||||
ELSE ROLLBACK TRANSACTION sp_PublishGeneration;
|
||||
RETURN;
|
||||
END
|
||||
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, ClusterId, GenerationId)
|
||||
VALUES (SUSER_SNAME(), 'Published', @ClusterId, @DraftGenerationId);
|
||||
|
||||
IF @OwnsTxn = 1 COMMIT;
|
||||
END
|
||||
";
|
||||
|
||||
public const string RollbackToGeneration = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_RollbackToGeneration
|
||||
@ClusterId nvarchar(64),
|
||||
@TargetGenerationId bigint,
|
||||
@Notes nvarchar(1024) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ConfigGeneration
|
||||
WHERE GenerationId = @TargetGenerationId AND ClusterId = @ClusterId
|
||||
AND Status IN ('Published', 'Superseded'))
|
||||
BEGIN
|
||||
RAISERROR('Target generation %I64d not found or not rollback-eligible', 16, 1, @TargetGenerationId);
|
||||
ROLLBACK; RETURN;
|
||||
END
|
||||
|
||||
DECLARE @NewGenId bigint;
|
||||
INSERT dbo.ConfigGeneration (ClusterId, Status, CreatedAt, CreatedBy, PublishedAt, PublishedBy, Notes)
|
||||
VALUES (@ClusterId, 'Draft', SYSUTCDATETIME(), SUSER_SNAME(), NULL, NULL,
|
||||
ISNULL(@Notes, CONCAT('Rollback clone of generation ', @TargetGenerationId)));
|
||||
SET @NewGenId = SCOPE_IDENTITY();
|
||||
|
||||
INSERT dbo.Namespace (GenerationId, NamespaceId, ClusterId, Kind, NamespaceUri, Enabled, Notes)
|
||||
SELECT @NewGenId, NamespaceId, ClusterId, Kind, NamespaceUri, Enabled, Notes FROM dbo.Namespace WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.UnsArea (GenerationId, UnsAreaId, ClusterId, Name, Notes)
|
||||
SELECT @NewGenId, UnsAreaId, ClusterId, Name, Notes FROM dbo.UnsArea WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.UnsLine (GenerationId, UnsLineId, UnsAreaId, Name, Notes)
|
||||
SELECT @NewGenId, UnsLineId, UnsAreaId, Name, Notes FROM dbo.UnsLine WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.DriverInstance (GenerationId, DriverInstanceId, ClusterId, NamespaceId, Name, DriverType, Enabled, DriverConfig)
|
||||
SELECT @NewGenId, DriverInstanceId, ClusterId, NamespaceId, Name, DriverType, Enabled, DriverConfig FROM dbo.DriverInstance WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.Device (GenerationId, DeviceId, DriverInstanceId, Name, Enabled, DeviceConfig)
|
||||
SELECT @NewGenId, DeviceId, DriverInstanceId, Name, Enabled, DeviceConfig FROM dbo.Device WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.Equipment (GenerationId, EquipmentId, EquipmentUuid, DriverInstanceId, DeviceId, UnsLineId, Name, MachineCode, ZTag, SAPID, Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, AssetLocation, ManufacturerUri, DeviceManualUri, EquipmentClassRef, Enabled)
|
||||
SELECT @NewGenId, EquipmentId, EquipmentUuid, DriverInstanceId, DeviceId, UnsLineId, Name, MachineCode, ZTag, SAPID, Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, AssetLocation, ManufacturerUri, DeviceManualUri, EquipmentClassRef, Enabled FROM dbo.Equipment WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.PollGroup (GenerationId, PollGroupId, DriverInstanceId, Name, IntervalMs)
|
||||
SELECT @NewGenId, PollGroupId, DriverInstanceId, Name, IntervalMs FROM dbo.PollGroup WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.Tag (GenerationId, TagId, DriverInstanceId, DeviceId, EquipmentId, Name, FolderPath, DataType, AccessLevel, WriteIdempotent, PollGroupId, TagConfig)
|
||||
SELECT @NewGenId, TagId, DriverInstanceId, DeviceId, EquipmentId, Name, FolderPath, DataType, AccessLevel, WriteIdempotent, PollGroupId, TagConfig FROM dbo.Tag WHERE GenerationId = @TargetGenerationId;
|
||||
INSERT dbo.NodeAcl (GenerationId, NodeAclId, ClusterId, LdapGroup, ScopeKind, ScopeId, PermissionFlags, Notes)
|
||||
SELECT @NewGenId, NodeAclId, ClusterId, LdapGroup, ScopeKind, ScopeId, PermissionFlags, Notes FROM dbo.NodeAcl WHERE GenerationId = @TargetGenerationId;
|
||||
|
||||
EXEC dbo.sp_PublishGeneration @ClusterId = @ClusterId, @DraftGenerationId = @NewGenId, @Notes = @Notes;
|
||||
|
||||
-- @TargetGenerationId is a bigint, but build the JSON value via an explicit numeric CONVERT so
|
||||
-- the emitted token is always a bare JSON number — never reliant on implicit string coercion.
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, ClusterId, GenerationId, DetailsJson)
|
||||
VALUES (SUSER_SNAME(), 'RolledBack', @ClusterId, @NewGenId,
|
||||
CONCAT('{""rolledBackTo"":', CONVERT(nvarchar(20), CONVERT(bigint, @TargetGenerationId)), '}'));
|
||||
|
||||
COMMIT;
|
||||
END
|
||||
";
|
||||
|
||||
public const string ComputeGenerationDiff = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ComputeGenerationDiff
|
||||
@FromGenerationId bigint,
|
||||
@ToGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
CREATE TABLE #diff (TableName nvarchar(32), LogicalId nvarchar(64), ChangeKind nvarchar(16));
|
||||
|
||||
WITH f AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Namespace', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'DriverInstance', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Equipment', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Tag', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
SELECT TableName, LogicalId, ChangeKind FROM #diff;
|
||||
DROP TABLE #diff;
|
||||
END
|
||||
";
|
||||
|
||||
public const string ReleaseExternalIdReservation = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ReleaseExternalIdReservation
|
||||
@Kind nvarchar(16),
|
||||
@Value nvarchar(64),
|
||||
@ReleaseReason nvarchar(512)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF @ReleaseReason IS NULL OR LEN(@ReleaseReason) = 0
|
||||
BEGIN
|
||||
RAISERROR('ReleaseReason is required', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
UPDATE dbo.ExternalIdReservation
|
||||
SET ReleasedAt = SYSUTCDATETIME(),
|
||||
ReleasedBy = SUSER_SNAME(),
|
||||
ReleaseReason = @ReleaseReason
|
||||
WHERE Kind = @Kind AND Value = @Value AND ReleasedAt IS NULL;
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
BEGIN
|
||||
RAISERROR('No active reservation found for (%s, %s)', 16, 1, @Kind, @Value);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
-- Escape both caller-supplied values via STRING_ESCAPE so quotes/backslashes cannot break the
|
||||
-- JSON document or inject additional structure into the audit record.
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, DetailsJson)
|
||||
VALUES (SUSER_SNAME(), 'ExternalIdReleased',
|
||||
CONCAT('{""kind"":""', STRING_ESCAPE(@Kind, 'json'),
|
||||
'"",""value"":""', STRING_ESCAPE(@Value, 'json'), '""}'));
|
||||
END
|
||||
";
|
||||
}
|
||||
}
|
||||
Generated
-1208
File diff suppressed because it is too large
Load Diff
-55
@@ -1,55 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the two DB roles per <c>config-db-schema.md §"Authorization Model"</c> and grants
|
||||
/// EXECUTE on the appropriate stored procedures. Deliberately grants no direct table DML — all
|
||||
/// writes funnel through the procs, which authenticate via <c>SUSER_SNAME()</c>.
|
||||
/// Principals (SQL logins, gMSA users, cert-mapped users) are provisioned by the DBA outside
|
||||
/// this migration and then added to one of the two roles.
|
||||
/// </summary>
|
||||
public partial class AuthorizationGrants : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaNode') IS NULL
|
||||
CREATE ROLE OtOpcUaNode;
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaAdmin') IS NULL
|
||||
CREATE ROLE OtOpcUaAdmin;
|
||||
");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetCurrentGenerationForCluster TO OtOpcUaNode;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetGenerationContent TO OtOpcUaNode;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_RegisterNodeGenerationApplied TO OtOpcUaNode;
|
||||
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetCurrentGenerationForCluster TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetGenerationContent TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_ValidateDraft TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_PublishGeneration TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_RollbackToGeneration TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_ComputeGenerationDiff TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_ReleaseExternalIdReservation TO OtOpcUaAdmin;
|
||||
|
||||
DENY UPDATE, DELETE, INSERT ON SCHEMA::dbo TO OtOpcUaNode;
|
||||
DENY UPDATE, DELETE, INSERT ON SCHEMA::dbo TO OtOpcUaAdmin;
|
||||
DENY SELECT ON SCHEMA::dbo TO OtOpcUaNode;
|
||||
-- Admins may SELECT for reporting views in the future — grant views explicitly, not the schema.
|
||||
DENY SELECT ON SCHEMA::dbo TO OtOpcUaAdmin;
|
||||
");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaNode') IS NOT NULL
|
||||
DROP ROLE OtOpcUaNode;
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaAdmin') IS NOT NULL
|
||||
DROP ROLE OtOpcUaAdmin;
|
||||
");
|
||||
}
|
||||
}
|
||||
Generated
-1248
File diff suppressed because it is too large
Load Diff
-49
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDriverHostStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DriverHostStatus",
|
||||
columns: table => new
|
||||
{
|
||||
NodeId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
HostName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
State = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
StateChangedUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false),
|
||||
LastSeenUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false),
|
||||
Detail = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DriverHostStatus", x => new { x.NodeId, x.DriverInstanceId, x.HostName });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DriverHostStatus_LastSeen",
|
||||
table: "DriverHostStatus",
|
||||
column: "LastSeenUtc");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DriverHostStatus_Node",
|
||||
table: "DriverHostStatus",
|
||||
column: "NodeId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DriverHostStatus");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1287
File diff suppressed because it is too large
Load Diff
-46
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDriverInstanceResilienceStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DriverInstanceResilienceStatus",
|
||||
columns: table => new
|
||||
{
|
||||
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
HostName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||
LastCircuitBreakerOpenUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
ConsecutiveFailures = table.Column<int>(type: "int", nullable: false),
|
||||
CurrentBulkheadDepth = table.Column<int>(type: "int", nullable: false),
|
||||
LastRecycleUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
BaselineFootprintBytes = table.Column<long>(type: "bigint", nullable: false),
|
||||
CurrentFootprintBytes = table.Column<long>(type: "bigint", nullable: false),
|
||||
LastSampledUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DriverInstanceResilienceStatus", x => new { x.DriverInstanceId, x.HostName });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DriverResilience_LastSampled",
|
||||
table: "DriverInstanceResilienceStatus",
|
||||
column: "LastSampledUtc");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DriverInstanceResilienceStatus");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1342
File diff suppressed because it is too large
Load Diff
-62
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddLdapGroupRoleMapping : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "LdapGroupRoleMapping",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
LdapGroup = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
|
||||
Role = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
IsSystemWide = table.Column<bool>(type: "bit", nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false),
|
||||
Notes = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_LdapGroupRoleMapping", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_LdapGroupRoleMapping_ServerCluster_ClusterId",
|
||||
column: x => x.ClusterId,
|
||||
principalTable: "ServerCluster",
|
||||
principalColumn: "ClusterId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LdapGroupRoleMapping_ClusterId",
|
||||
table: "LdapGroupRoleMapping",
|
||||
column: "ClusterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_LdapGroupRoleMapping_Group",
|
||||
table: "LdapGroupRoleMapping",
|
||||
column: "LdapGroup");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_LdapGroupRoleMapping_Group_Cluster",
|
||||
table: "LdapGroupRoleMapping",
|
||||
columns: new[] { "LdapGroup", "ClusterId" },
|
||||
unique: true,
|
||||
filter: "[ClusterId] IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "LdapGroupRoleMapping");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1347
File diff suppressed because it is too large
Load Diff
-37
@@ -1,37 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDriverInstanceResilienceConfig : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ResilienceConfig",
|
||||
table: "DriverInstance",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddCheckConstraint(
|
||||
name: "CK_DriverInstance_ResilienceConfig_IsJson",
|
||||
table: "DriverInstance",
|
||||
sql: "ResilienceConfig IS NULL OR ISJSON(ResilienceConfig) = 1");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropCheckConstraint(
|
||||
name: "CK_DriverInstance_ResilienceConfig_IsJson",
|
||||
table: "DriverInstance");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ResilienceConfig",
|
||||
table: "DriverInstance");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1505
File diff suppressed because it is too large
Load Diff
-91
@@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddEquipmentImportBatch : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EquipmentImportBatch",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ClusterId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
CreatedBy = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false),
|
||||
RowsStaged = table.Column<int>(type: "int", nullable: false),
|
||||
RowsAccepted = table.Column<int>(type: "int", nullable: false),
|
||||
RowsRejected = table.Column<int>(type: "int", nullable: false),
|
||||
FinalisedAtUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EquipmentImportBatch", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "EquipmentImportRow",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
BatchId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
LineNumberInFile = table.Column<int>(type: "int", nullable: false),
|
||||
IsAccepted = table.Column<bool>(type: "bit", nullable: false),
|
||||
RejectReason = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
ZTag = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
MachineCode = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
SAPID = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
EquipmentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
EquipmentUuid = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
UnsAreaName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
UnsLineName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Manufacturer = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
Model = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
SerialNumber = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
HardwareRevision = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
SoftwareRevision = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
YearOfConstruction = table.Column<string>(type: "nvarchar(8)", maxLength: 8, nullable: true),
|
||||
AssetLocation = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
ManufacturerUri = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
DeviceManualUri = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_EquipmentImportRow", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_EquipmentImportRow_EquipmentImportBatch_BatchId",
|
||||
column: x => x.BatchId,
|
||||
principalTable: "EquipmentImportBatch",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EquipmentImportBatch_Creator_Finalised",
|
||||
table: "EquipmentImportBatch",
|
||||
columns: new[] { "CreatedBy", "FinalisedAtUtc" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EquipmentImportRow_Batch",
|
||||
table: "EquipmentImportRow",
|
||||
column: "BatchId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "EquipmentImportRow");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "EquipmentImportBatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1505
File diff suppressed because it is too large
Load Diff
-172
@@ -1,172 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends <c>dbo.sp_ComputeGenerationDiff</c> to emit <c>NodeAcl</c> rows alongside the
|
||||
/// existing Namespace/DriverInstance/Equipment/Tag output — closes the final slice of
|
||||
/// task #196 (DiffViewer ACL section). Logical id for NodeAcl is a composite
|
||||
/// <c>LdapGroup|ScopeKind|ScopeId</c> triple so a Change row surfaces whether the grant
|
||||
/// shifted permissions, moved scope, or was added/removed outright.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public partial class ExtendComputeGenerationDiffWithNodeAcl : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.ComputeGenerationDiffV2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.ComputeGenerationDiffV1);
|
||||
}
|
||||
|
||||
private static class Procs
|
||||
{
|
||||
/// <summary>V2 — adds the NodeAcl section to the diff output.</summary>
|
||||
public const string ComputeGenerationDiffV2 = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ComputeGenerationDiff
|
||||
@FromGenerationId bigint,
|
||||
@ToGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
CREATE TABLE #diff (TableName nvarchar(32), LogicalId nvarchar(128), ChangeKind nvarchar(16));
|
||||
|
||||
WITH f AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Namespace', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'DriverInstance', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Equipment', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Tag', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
-- NodeAcl section. Logical id is the (LdapGroup, ScopeKind, ScopeId) triple so the diff
|
||||
-- distinguishes same row with new permissions (Modified via CHECKSUM on PermissionFlags + Notes)
|
||||
-- from a scope move (which surfaces as Added + Removed of different logical ids).
|
||||
WITH f AS (
|
||||
SELECT CONVERT(nvarchar(128), LdapGroup + '|' + CONVERT(nvarchar(16), ScopeKind) + '|' + ISNULL(ScopeId, '(cluster)')) AS LogicalId,
|
||||
CHECKSUM(ClusterId, PermissionFlags, Notes) AS Sig
|
||||
FROM dbo.NodeAcl WHERE GenerationId = @FromGenerationId),
|
||||
t AS (
|
||||
SELECT CONVERT(nvarchar(128), LdapGroup + '|' + CONVERT(nvarchar(16), ScopeKind) + '|' + ISNULL(ScopeId, '(cluster)')) AS LogicalId,
|
||||
CHECKSUM(ClusterId, PermissionFlags, Notes) AS Sig
|
||||
FROM dbo.NodeAcl WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'NodeAcl', COALESCE(f.LogicalId, t.LogicalId),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
SELECT TableName, LogicalId, ChangeKind FROM #diff;
|
||||
DROP TABLE #diff;
|
||||
END
|
||||
";
|
||||
|
||||
/// <summary>V1 — exact proc shipped in migration 20260417215224_StoredProcedures. Restored on Down().</summary>
|
||||
public const string ComputeGenerationDiffV1 = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ComputeGenerationDiff
|
||||
@FromGenerationId bigint,
|
||||
@ToGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
CREATE TABLE #diff (TableName nvarchar(32), LogicalId nvarchar(64), ChangeKind nvarchar(16));
|
||||
|
||||
WITH f AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Namespace', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'DriverInstance', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Equipment', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Tag', CONVERT(nvarchar(64), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
SELECT TableName, LogicalId, ChangeKind FROM #diff;
|
||||
DROP TABLE #diff;
|
||||
END
|
||||
";
|
||||
}
|
||||
}
|
||||
}
|
||||
-1793
File diff suppressed because it is too large
Load Diff
-186
@@ -1,186 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPhase7ScriptingTables : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Script",
|
||||
columns: table => new
|
||||
{
|
||||
ScriptRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ScriptId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
SourceCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
SourceHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Language = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Script", x => x.ScriptRowId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Script_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ScriptedAlarm",
|
||||
columns: table => new
|
||||
{
|
||||
ScriptedAlarmRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ScriptedAlarmId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
EquipmentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
AlarmType = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
Severity = table.Column<int>(type: "int", nullable: false),
|
||||
MessageTemplate = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||
PredicateScriptId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
HistorizeToAveva = table.Column<bool>(type: "bit", nullable: false),
|
||||
Retain = table.Column<bool>(type: "bit", nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ScriptedAlarm", x => x.ScriptedAlarmRowId);
|
||||
table.CheckConstraint("CK_ScriptedAlarm_AlarmType", "AlarmType IN ('AlarmCondition','LimitAlarm','OffNormalAlarm','DiscreteAlarm')");
|
||||
table.CheckConstraint("CK_ScriptedAlarm_Severity_Range", "Severity BETWEEN 1 AND 1000");
|
||||
table.ForeignKey(
|
||||
name: "FK_ScriptedAlarm_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ScriptedAlarmState",
|
||||
columns: table => new
|
||||
{
|
||||
ScriptedAlarmId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
EnabledState = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
AckedState = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
ConfirmedState = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
ShelvingState = table.Column<string>(type: "nvarchar(16)", maxLength: 16, nullable: false),
|
||||
ShelvingExpiresUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
LastAckUser = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
LastAckComment = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||
LastAckUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
LastConfirmUser = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
|
||||
LastConfirmComment = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: true),
|
||||
LastConfirmUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
|
||||
CommentsJson = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
UpdatedAtUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false, defaultValueSql: "SYSUTCDATETIME()")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ScriptedAlarmState", x => x.ScriptedAlarmId);
|
||||
table.CheckConstraint("CK_ScriptedAlarmState_CommentsJson_IsJson", "ISJSON(CommentsJson) = 1");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VirtualTag",
|
||||
columns: table => new
|
||||
{
|
||||
VirtualTagRowId = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWSEQUENTIALID()"),
|
||||
GenerationId = table.Column<long>(type: "bigint", nullable: false),
|
||||
VirtualTagId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
EquipmentId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||
DataType = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
ScriptId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||
ChangeTriggered = table.Column<bool>(type: "bit", nullable: false),
|
||||
TimerIntervalMs = table.Column<int>(type: "int", nullable: true),
|
||||
Historize = table.Column<bool>(type: "bit", nullable: false),
|
||||
Enabled = table.Column<bool>(type: "bit", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VirtualTag", x => x.VirtualTagRowId);
|
||||
table.CheckConstraint("CK_VirtualTag_TimerInterval_Min", "TimerIntervalMs IS NULL OR TimerIntervalMs >= 50");
|
||||
table.CheckConstraint("CK_VirtualTag_Trigger_AtLeastOne", "ChangeTriggered = 1 OR TimerIntervalMs IS NOT NULL");
|
||||
table.ForeignKey(
|
||||
name: "FK_VirtualTag_ConfigGeneration_GenerationId",
|
||||
column: x => x.GenerationId,
|
||||
principalTable: "ConfigGeneration",
|
||||
principalColumn: "GenerationId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Script_Generation_SourceHash",
|
||||
table: "Script",
|
||||
columns: new[] { "GenerationId", "SourceHash" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_Script_Generation_LogicalId",
|
||||
table: "Script",
|
||||
columns: new[] { "GenerationId", "ScriptId" },
|
||||
unique: true,
|
||||
filter: "[ScriptId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ScriptedAlarm_Generation_Script",
|
||||
table: "ScriptedAlarm",
|
||||
columns: new[] { "GenerationId", "PredicateScriptId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ScriptedAlarm_Generation_EquipmentPath",
|
||||
table: "ScriptedAlarm",
|
||||
columns: new[] { "GenerationId", "EquipmentId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ScriptedAlarm_Generation_LogicalId",
|
||||
table: "ScriptedAlarm",
|
||||
columns: new[] { "GenerationId", "ScriptedAlarmId" },
|
||||
unique: true,
|
||||
filter: "[ScriptedAlarmId] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VirtualTag_Generation_Script",
|
||||
table: "VirtualTag",
|
||||
columns: new[] { "GenerationId", "ScriptId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VirtualTag_Generation_EquipmentPath",
|
||||
table: "VirtualTag",
|
||||
columns: new[] { "GenerationId", "EquipmentId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_VirtualTag_Generation_LogicalId",
|
||||
table: "VirtualTag",
|
||||
columns: new[] { "GenerationId", "VirtualTagId" },
|
||||
unique: true,
|
||||
filter: "[VirtualTagId] IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Script");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ScriptedAlarm");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ScriptedAlarmState");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "VirtualTag");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1793
File diff suppressed because it is too large
Load Diff
-232
@@ -1,232 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Phase 7 follow-up (task #241) — extends <c>dbo.sp_ComputeGenerationDiff</c> to emit
|
||||
/// Script / VirtualTag / ScriptedAlarm rows alongside the existing Namespace /
|
||||
/// DriverInstance / Equipment / Tag / NodeAcl output. Admin DiffViewer now shows
|
||||
/// Phase 7 changes between generations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Logical ids: ScriptId, VirtualTagId, ScriptedAlarmId — stable across generations
|
||||
/// so a Script whose source changes surfaces as Modified (CHECKSUM picks up the
|
||||
/// SourceHash delta) while a renamed script surfaces as Modified on Name alone.
|
||||
/// ScriptedAlarmState is deliberately excluded — it's not generation-scoped, so
|
||||
/// diffing it between generations is meaningless.
|
||||
/// </remarks>
|
||||
/// <inheritdoc />
|
||||
public partial class ExtendComputeGenerationDiffWithPhase7 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.ComputeGenerationDiffV3);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.ComputeGenerationDiffV2);
|
||||
}
|
||||
|
||||
private static class Procs
|
||||
{
|
||||
/// <summary>V3 — adds Script / VirtualTag / ScriptedAlarm sections.</summary>
|
||||
public const string ComputeGenerationDiffV3 = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ComputeGenerationDiff
|
||||
@FromGenerationId bigint,
|
||||
@ToGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
CREATE TABLE #diff (TableName nvarchar(32), LogicalId nvarchar(128), ChangeKind nvarchar(16));
|
||||
|
||||
WITH f AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Namespace', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'DriverInstance', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Equipment', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Tag', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (
|
||||
SELECT CONVERT(nvarchar(128), LdapGroup + '|' + CONVERT(nvarchar(16), ScopeKind) + '|' + ISNULL(ScopeId, '(cluster)')) AS LogicalId,
|
||||
CHECKSUM(ClusterId, PermissionFlags, Notes) AS Sig
|
||||
FROM dbo.NodeAcl WHERE GenerationId = @FromGenerationId),
|
||||
t AS (
|
||||
SELECT CONVERT(nvarchar(128), LdapGroup + '|' + CONVERT(nvarchar(16), ScopeKind) + '|' + ISNULL(ScopeId, '(cluster)')) AS LogicalId,
|
||||
CHECKSUM(ClusterId, PermissionFlags, Notes) AS Sig
|
||||
FROM dbo.NodeAcl WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'NodeAcl', COALESCE(f.LogicalId, t.LogicalId),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
-- Phase 7 — Script section. CHECKSUM picks up source changes via SourceHash + rename
|
||||
-- via Name; Language future-proofs for non-C# engines. Same Name + same Source =
|
||||
-- Unchanged (identical hash).
|
||||
WITH f AS (SELECT ScriptId AS LogicalId, CHECKSUM(Name, SourceHash, Language) AS Sig FROM dbo.Script WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT ScriptId AS LogicalId, CHECKSUM(Name, SourceHash, Language) AS Sig FROM dbo.Script WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Script', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
-- Phase 7 — VirtualTag section.
|
||||
WITH f AS (SELECT VirtualTagId AS LogicalId, CHECKSUM(EquipmentId, Name, DataType, ScriptId, ChangeTriggered, TimerIntervalMs, Historize, Enabled) AS Sig FROM dbo.VirtualTag WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT VirtualTagId AS LogicalId, CHECKSUM(EquipmentId, Name, DataType, ScriptId, ChangeTriggered, TimerIntervalMs, Historize, Enabled) AS Sig FROM dbo.VirtualTag WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'VirtualTag', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
-- Phase 7 — ScriptedAlarm section. ScriptedAlarmState (operator ack trail) is
|
||||
-- logical-id keyed outside the generation scope + intentionally excluded here —
|
||||
-- diffing ack state between generations is semantically meaningless.
|
||||
WITH f AS (SELECT ScriptedAlarmId AS LogicalId, CHECKSUM(EquipmentId, Name, AlarmType, Severity, MessageTemplate, PredicateScriptId, HistorizeToAveva, Retain, Enabled) AS Sig FROM dbo.ScriptedAlarm WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT ScriptedAlarmId AS LogicalId, CHECKSUM(EquipmentId, Name, AlarmType, Severity, MessageTemplate, PredicateScriptId, HistorizeToAveva, Retain, Enabled) AS Sig FROM dbo.ScriptedAlarm WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'ScriptedAlarm', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
SELECT TableName, LogicalId, ChangeKind FROM #diff;
|
||||
DROP TABLE #diff;
|
||||
END
|
||||
";
|
||||
|
||||
/// <summary>V2 — restores the pre-Phase-7 proc on Down().</summary>
|
||||
public const string ComputeGenerationDiffV2 = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ComputeGenerationDiff
|
||||
@FromGenerationId bigint,
|
||||
@ToGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
CREATE TABLE #diff (TableName nvarchar(32), LogicalId nvarchar(128), ChangeKind nvarchar(16));
|
||||
|
||||
WITH f AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT NamespaceId AS LogicalId, CHECKSUM(NamespaceUri, Kind, Enabled, Notes) AS Sig FROM dbo.Namespace WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Namespace', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT DriverInstanceId AS LogicalId, CHECKSUM(ClusterId, NamespaceId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) AS Sig FROM dbo.DriverInstance WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'DriverInstance', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT EquipmentId AS LogicalId, CHECKSUM(EquipmentUuid, DriverInstanceId, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) AS Sig FROM dbo.Equipment WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Equipment', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @FromGenerationId),
|
||||
t AS (SELECT TagId AS LogicalId, CHECKSUM(DriverInstanceId, DeviceId, EquipmentId, PollGroupId, FolderPath, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) AS Sig FROM dbo.Tag WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'Tag', CONVERT(nvarchar(128), COALESCE(f.LogicalId, t.LogicalId)),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
WITH f AS (
|
||||
SELECT CONVERT(nvarchar(128), LdapGroup + '|' + CONVERT(nvarchar(16), ScopeKind) + '|' + ISNULL(ScopeId, '(cluster)')) AS LogicalId,
|
||||
CHECKSUM(ClusterId, PermissionFlags, Notes) AS Sig
|
||||
FROM dbo.NodeAcl WHERE GenerationId = @FromGenerationId),
|
||||
t AS (
|
||||
SELECT CONVERT(nvarchar(128), LdapGroup + '|' + CONVERT(nvarchar(16), ScopeKind) + '|' + ISNULL(ScopeId, '(cluster)')) AS LogicalId,
|
||||
CHECKSUM(ClusterId, PermissionFlags, Notes) AS Sig
|
||||
FROM dbo.NodeAcl WHERE GenerationId = @ToGenerationId)
|
||||
INSERT #diff
|
||||
SELECT 'NodeAcl', COALESCE(f.LogicalId, t.LogicalId),
|
||||
CASE WHEN f.LogicalId IS NULL THEN 'Added'
|
||||
WHEN t.LogicalId IS NULL THEN 'Removed'
|
||||
WHEN f.Sig <> t.Sig THEN 'Modified'
|
||||
ELSE 'Unchanged' END
|
||||
FROM f FULL OUTER JOIN t ON f.LogicalId = t.LogicalId
|
||||
WHERE f.LogicalId IS NULL OR t.LogicalId IS NULL OR f.Sig <> t.Sig;
|
||||
|
||||
SELECT TableName, LogicalId, ChangeKind FROM #diff;
|
||||
DROP TABLE #diff;
|
||||
END
|
||||
";
|
||||
}
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Admin-008: adds <c>@ReleasedBy</c> parameter to
|
||||
/// <c>dbo.sp_ReleaseExternalIdReservation</c> so the operator principal name (the LDAP
|
||||
/// sign-in) is recorded in <c>ExternalIdReservation.ReleasedBy</c> and the
|
||||
/// <c>ConfigAuditLog.Principal</c> column.
|
||||
///
|
||||
/// Prior to this migration the proc used <c>SUSER_SNAME()</c> for both columns, which
|
||||
/// recorded the shared SQL service account rather than the Admin-UI operator who performed
|
||||
/// the release — making the audit trail useless for attribution. The stored proc now
|
||||
/// accepts <c>@ReleasedBy nvarchar(128)</c> and uses it for both columns; validation
|
||||
/// rejects a null/empty value the same way <c>@ReleaseReason</c> is validated.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public partial class AddReleasedByToReleaseExternalIdReservation : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.ReleaseExternalIdReservationV2);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(Procs.ReleaseExternalIdReservationV1);
|
||||
}
|
||||
|
||||
private static class Procs
|
||||
{
|
||||
/// <summary>V2 — accepts <c>@ReleasedBy</c> for proper operator attribution.</summary>
|
||||
public const string ReleaseExternalIdReservationV2 = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ReleaseExternalIdReservation
|
||||
@Kind nvarchar(16),
|
||||
@Value nvarchar(64),
|
||||
@ReleaseReason nvarchar(512),
|
||||
@ReleasedBy nvarchar(128)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF @ReleaseReason IS NULL OR LEN(@ReleaseReason) = 0
|
||||
BEGIN
|
||||
RAISERROR('ReleaseReason is required', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF @ReleasedBy IS NULL OR LEN(@ReleasedBy) = 0
|
||||
BEGIN
|
||||
RAISERROR('ReleasedBy is required', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
UPDATE dbo.ExternalIdReservation
|
||||
SET ReleasedAt = SYSUTCDATETIME(),
|
||||
ReleasedBy = @ReleasedBy,
|
||||
ReleaseReason = @ReleaseReason
|
||||
WHERE Kind = @Kind AND Value = @Value AND ReleasedAt IS NULL;
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
BEGIN
|
||||
RAISERROR('No active reservation found for (%s, %s)', 16, 1, @Kind, @Value);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
-- Escape all caller-supplied values via STRING_ESCAPE so quotes/backslashes cannot break the
|
||||
-- JSON document or inject additional structure into the audit record.
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, DetailsJson)
|
||||
VALUES (@ReleasedBy, 'ExternalIdReleased',
|
||||
CONCAT('{""kind"":""', STRING_ESCAPE(@Kind, 'json'),
|
||||
'"",""value"":""', STRING_ESCAPE(@Value, 'json'), '""}'));
|
||||
END
|
||||
";
|
||||
|
||||
/// <summary>V1 — original proc (uses SUSER_SNAME() for attribution). Restored on Down().</summary>
|
||||
public const string ReleaseExternalIdReservationV1 = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ReleaseExternalIdReservation
|
||||
@Kind nvarchar(16),
|
||||
@Value nvarchar(64),
|
||||
@ReleaseReason nvarchar(512)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF @ReleaseReason IS NULL OR LEN(@ReleaseReason) = 0
|
||||
BEGIN
|
||||
RAISERROR('ReleaseReason is required', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
UPDATE dbo.ExternalIdReservation
|
||||
SET ReleasedAt = SYSUTCDATETIME(),
|
||||
ReleasedBy = SUSER_SNAME(),
|
||||
ReleaseReason = @ReleaseReason
|
||||
WHERE Kind = @Kind AND Value = @Value AND ReleasedAt IS NULL;
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
BEGIN
|
||||
RAISERROR('No active reservation found for (%s, %s)', 16, 1, @Kind, @Value);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
-- Escape both caller-supplied values via STRING_ESCAPE so quotes/backslashes cannot break the
|
||||
-- JSON document or inject additional structure into the audit record.
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, DetailsJson)
|
||||
VALUES (SUSER_SNAME(), 'ExternalIdReleased',
|
||||
CONCAT('{""kind"":""', STRING_ESCAPE(@Kind, 'json'),
|
||||
'"",""value"":""', STRING_ESCAPE(@Value, 'json'), '""}'));
|
||||
END
|
||||
";
|
||||
}
|
||||
}
|
||||
}
|
||||
-1562
File diff suppressed because it is too large
Load Diff
-1755
File diff suppressed because it is too large
Load Diff
-50
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddConfigAuditLogEventIdColumns : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "CorrelationId",
|
||||
table: "ConfigAuditLog",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "EventId",
|
||||
table: "ConfigAuditLog",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UX_ConfigAuditLog_EventId",
|
||||
table: "ConfigAuditLog",
|
||||
column: "EventId",
|
||||
unique: true,
|
||||
filter: "[EventId] IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "UX_ConfigAuditLog_EventId",
|
||||
table: "ConfigAuditLog");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CorrelationId",
|
||||
table: "ConfigAuditLog");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EventId",
|
||||
table: "ConfigAuditLog");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1755
File diff suppressed because it is too large
Load Diff
-39
@@ -1,39 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Task 1.7 — canonicalizes the control-plane admin role VALUES persisted in the
|
||||
/// <c>LdapGroupRoleMapping.Role</c> column. The column stores the <c>AdminRole</c> enum
|
||||
/// member name as a string (EF <c>HasConversion<string></c>, <c>nvarchar(32)</c>);
|
||||
/// renaming the enum members (<c>ConfigViewer → Viewer</c>, <c>ConfigEditor → Designer</c>,
|
||||
/// <c>FleetAdmin → Administrator</c>) therefore requires rewriting existing rows so the C#
|
||||
/// enum and the stored strings stay in sync.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a pure DATA migration: the schema (column type, length, indexes) is unchanged,
|
||||
/// so the model snapshot is byte-identical to the prior migration. The new canonical strings
|
||||
/// ("Viewer" = 6, "Designer" = 8, "Administrator" = 13 chars) all fit the existing
|
||||
/// <c>nvarchar(32)</c> column. Enforcement semantics are preserved — it is a rename only.
|
||||
/// </remarks>
|
||||
public partial class CanonicalizeAdminRoles : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("UPDATE [LdapGroupRoleMapping] SET [Role] = N'Viewer' WHERE [Role] = N'ConfigViewer';");
|
||||
migrationBuilder.Sql("UPDATE [LdapGroupRoleMapping] SET [Role] = N'Designer' WHERE [Role] = N'ConfigEditor';");
|
||||
migrationBuilder.Sql("UPDATE [LdapGroupRoleMapping] SET [Role] = N'Administrator' WHERE [Role] = N'FleetAdmin';");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("UPDATE [LdapGroupRoleMapping] SET [Role] = N'FleetAdmin' WHERE [Role] = N'Administrator';");
|
||||
migrationBuilder.Sql("UPDATE [LdapGroupRoleMapping] SET [Role] = N'ConfigEditor' WHERE [Role] = N'Designer';");
|
||||
migrationBuilder.Sql("UPDATE [LdapGroupRoleMapping] SET [Role] = N'ConfigViewer' WHERE [Role] = N'Viewer';");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1759
File diff suppressed because it is too large
Load Diff
-35
@@ -1,35 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Task 2.2 — adds the nullable <c>Outcome</c> column to <c>ConfigAuditLog</c> for the
|
||||
/// canonical <c>ZB.MOM.WW.Audit.AuditOutcome</c> (stored as its enum member name,
|
||||
/// <c>nvarchar(16)</c>, mirroring how <c>AdminRole</c> is persisted). Purely additive:
|
||||
/// nullable with no backfill, so existing rows and the bespoke stored-procedure audit
|
||||
/// path (which does not derive an outcome) keep writing NULL.
|
||||
/// </summary>
|
||||
public partial class AddConfigAuditLogOutcome : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Outcome",
|
||||
table: "ConfigAuditLog",
|
||||
type: "nvarchar(16)",
|
||||
maxLength: 16,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Outcome",
|
||||
table: "ConfigAuditLog");
|
||||
}
|
||||
}
|
||||
}
|
||||
-1758
File diff suppressed because it is too large
Load Diff
-42
@@ -1,42 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class NullableEquipmentDriverInstanceId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DriverInstanceId",
|
||||
table: "Equipment",
|
||||
type: "nvarchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(64)",
|
||||
oldMaxLength: 64);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// WARNING: this rollback converts any existing NULL (driver-less) rows to "" for DriverInstanceId.
|
||||
// Only safe when no driver-less equipment exists in the database.
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DriverInstanceId",
|
||||
table: "Equipment",
|
||||
type: "nvarchar(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(64)",
|
||||
oldMaxLength: 64,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1758
File diff suppressed because it is too large
Load Diff
-181
@@ -1,181 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Forward-only DATA-cleanup migration for the Galaxy clean break: the
|
||||
/// <c>NamespaceKind.SystemPlatform</c> enum member was removed (Galaxy is now an
|
||||
/// Equipment-kind driver). <c>Namespace.Kind</c> is persisted as a string via
|
||||
/// <c>HasConversion<string></c>, so any leftover row with <c>Kind = 'SystemPlatform'</c>
|
||||
/// would throw at materialisation time (no matching enum member) the moment the new code
|
||||
/// reads the table. This deletes those namespaces plus everything that hung off them.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// There is NO schema/model change in this migration — the model snapshot is unchanged.
|
||||
/// The unique index <c>UX_Namespace_Cluster_Kind</c> (unique on <c>ClusterId, Kind</c>) is
|
||||
/// intentionally KEPT: <c>Simulated</c> remains a reserved kind, so one-of-each-kind-per-cluster
|
||||
/// is still the correct constraint.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The cross-table relationships in this graph are LOGICAL foreign keys (string Id columns,
|
||||
/// e.g. <c>DriverInstance.NamespaceId</c> → <c>Namespace.NamespaceId</c>,
|
||||
/// <c>Tag.DriverInstanceId</c> → <c>DriverInstance.DriverInstanceId</c>) enforced by
|
||||
/// <c>sp_ValidateDraft</c>, NOT physical DB FK constraints — only the <c>*.ClusterId</c>
|
||||
/// relationships are real DB FKs. So no FK constraint can be violated here. The deletes are
|
||||
/// nonetheless ordered child→parent so no orphaned rows are left behind, and every statement
|
||||
/// is keyed off the set of SystemPlatform namespaces, so on a DB with zero such rows every
|
||||
/// DELETE simply affects 0 rows (idempotent / safe to run repeatedly).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Two tables carry LOGICAL refs with no physical FK and so were easy to miss: <c>NodeAcl</c>
|
||||
/// (per-scope grants keyed by <c>ScopeKind</c> + <c>ScopeId</c>) is cleaned for the
|
||||
/// Namespace/Equipment/FolderSegment/Tag scopes that get deleted, and
|
||||
/// <c>ExternalIdReservation</c> (fleet-wide ZTag/SAPID reservations keyed by
|
||||
/// <c>EquipmentUuid</c>) is RELEASED — not deleted — for the affected equipment so the
|
||||
/// reserved external IDs free up while the audit row survives. Both run before the entity
|
||||
/// rows they reference are removed so the id subqueries still resolve.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <inheritdoc />
|
||||
public partial class CleanupSystemPlatformNamespaces : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Delete in child -> parent order. Everything is keyed off the set of namespaces whose
|
||||
// Kind = 'SystemPlatform' (the string form persisted by HasConversion<string>), funnelled
|
||||
// through the DriverInstances that lived in those namespaces.
|
||||
migrationBuilder.Sql(@"
|
||||
-- Defensive: SystemPlatform namespaces historically held only EquipmentId-NULL mirror tags, but a
|
||||
-- stray equipment-scoped tag / Equipment could be bound to a SystemPlatform-namespace driver. Clean
|
||||
-- both, plus anything that FKs into the affected Equipment (VirtualTag / ScriptedAlarm / state).
|
||||
|
||||
-- Logical-reference cleanup that must run FIRST -------------------------------------------
|
||||
-- NodeAcl rows carry purely LOGICAL scope refs (ScopeKind + ScopeId, no physical FK). When the
|
||||
-- scoped entity is deleted below the ScopeId dangles and the ACL evaluator would still match it,
|
||||
-- silently granting/denying on a node that no longer exists. ScopeKind persists as a string via
|
||||
-- HasConversion<string>, so the values below ('Namespace'/'Equipment'/'FolderSegment'/'Tag') are
|
||||
-- the literal enum member names. These deletes must read the entity-id subqueries while those rows
|
||||
-- still exist, hence they run before the entity DELETEs further down.
|
||||
|
||||
-- Namespace-scoped grants pointing at the SystemPlatform namespaces themselves.
|
||||
DELETE FROM [NodeAcl]
|
||||
WHERE [ScopeKind] = 'Namespace'
|
||||
AND [ScopeId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform');
|
||||
|
||||
-- Equipment- and FolderSegment-scoped grants pointing at Equipment that hangs off the affected
|
||||
-- DriverInstances (both share Equipment.EquipmentId as the ScopeId).
|
||||
DELETE FROM [NodeAcl]
|
||||
WHERE [ScopeKind] IN ('Equipment', 'FolderSegment')
|
||||
AND [ScopeId] IN (
|
||||
SELECT [EquipmentId] FROM [Equipment]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform')));
|
||||
|
||||
-- Tag-scoped grants pointing at Tags bound to the affected DriverInstances (ScopeId = Tag.TagId).
|
||||
DELETE FROM [NodeAcl]
|
||||
WHERE [ScopeKind] = 'Tag'
|
||||
AND [ScopeId] IN (
|
||||
SELECT [TagId] FROM [Tag]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform')));
|
||||
|
||||
-- ExternalIdReservation is NOT generation-versioned and must outlive equipment deletion as a
|
||||
-- RELEASE, not a delete: active rows (ReleasedAt IS NULL) hold a fleet-wide unique reservation of
|
||||
-- the ZTag/SAPID under a filtered unique index. If we left them active after dropping the
|
||||
-- Equipment, the same external IDs could never be reused. Release them so the IDs free up while the
|
||||
-- audit row survives. Run BEFORE the Equipment DELETE so the EquipmentUuid subquery still resolves.
|
||||
UPDATE [ExternalIdReservation]
|
||||
SET [ReleasedAt] = SYSUTCDATETIME(),
|
||||
[ReleasedBy] = 'CleanupSystemPlatformNamespaces',
|
||||
[ReleaseReason] = 'Equipment removed by SystemPlatform-namespace cleanup migration (retired NamespaceKind).'
|
||||
WHERE [ReleasedAt] IS NULL
|
||||
AND [EquipmentUuid] IN (
|
||||
SELECT [EquipmentUuid] FROM [Equipment]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform')));
|
||||
|
||||
-- Grandchildren of the affected Equipment ------------------------------------------------
|
||||
-- ScriptedAlarmState is keyed by ScriptedAlarm.ScriptedAlarmId; remove before its ScriptedAlarm.
|
||||
DELETE FROM [ScriptedAlarmState]
|
||||
WHERE [ScriptedAlarmId] IN (
|
||||
SELECT [ScriptedAlarmId] FROM [ScriptedAlarm]
|
||||
WHERE [EquipmentId] IN (
|
||||
SELECT [EquipmentId] FROM [Equipment]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform'))));
|
||||
|
||||
DELETE FROM [ScriptedAlarm]
|
||||
WHERE [EquipmentId] IN (
|
||||
SELECT [EquipmentId] FROM [Equipment]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform')));
|
||||
|
||||
DELETE FROM [VirtualTag]
|
||||
WHERE [EquipmentId] IN (
|
||||
SELECT [EquipmentId] FROM [Equipment]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform')));
|
||||
|
||||
-- Children of the affected DriverInstances ------------------------------------------------
|
||||
-- Tags bound to those drivers: both EquipmentId-NULL mirror tags AND any equipment-scoped tag.
|
||||
DELETE FROM [Tag]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform'));
|
||||
|
||||
DELETE FROM [PollGroup]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform'));
|
||||
|
||||
DELETE FROM [Device]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform'));
|
||||
|
||||
DELETE FROM [Equipment]
|
||||
WHERE [DriverInstanceId] IN (
|
||||
SELECT [DriverInstanceId] FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform'));
|
||||
|
||||
-- The DriverInstances themselves ----------------------------------------------------------
|
||||
DELETE FROM [DriverInstance]
|
||||
WHERE [NamespaceId] IN (
|
||||
SELECT [NamespaceId] FROM [Namespace] WHERE [Kind] = 'SystemPlatform');
|
||||
|
||||
-- The SystemPlatform namespaces (the rows that would crash EF on read) --------------------
|
||||
DELETE FROM [Namespace] WHERE [Kind] = 'SystemPlatform';
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Forward-only data cleanup: the deleted SystemPlatform namespaces and their dependent
|
||||
// rows cannot be reconstructed (the data is gone, and SystemPlatform is no longer a valid
|
||||
// NamespaceKind). Intentionally a no-op rather than a throw, so rolling back a LATER
|
||||
// migration does not blow up on this one.
|
||||
}
|
||||
}
|
||||
}
|
||||
+226
-274
@@ -12,8 +12,8 @@ using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
[DbContext(typeof(OtOpcUaConfigDbContext))]
|
||||
[Migration("20260526081556_V2HostingAlignment")]
|
||||
partial class V2HostingAlignment
|
||||
[Migration("20260715230637_V3Initial")]
|
||||
partial class V3Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -168,9 +168,15 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid?>("CorrelationId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("DetailsJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("EventId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
@@ -183,6 +189,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Outcome")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("nvarchar(16)");
|
||||
|
||||
b.Property<string>("Principal")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
@@ -195,6 +205,11 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
|
||||
b.HasKey("AuditId");
|
||||
|
||||
b.HasIndex("EventId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_ConfigAuditLog_EventId")
|
||||
.HasFilter("[EventId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("GenerationId")
|
||||
.HasDatabaseName("IX_ConfigAuditLog_Generation")
|
||||
.HasFilter("[GenerationId] IS NOT NULL");
|
||||
@@ -361,6 +376,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.HasIndex("DriverInstanceId")
|
||||
.HasDatabaseName("IX_Device_Driver");
|
||||
|
||||
b.HasIndex("DriverInstanceId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Device_Driver_Name");
|
||||
|
||||
b.ToTable("Device", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_Device_DeviceConfig_IsJson", "ISJSON(DeviceConfig) = 1");
|
||||
@@ -440,8 +459,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("NamespaceId")
|
||||
.IsRequired()
|
||||
b.Property<string>("RawFolderId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
@@ -464,8 +482,18 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasDatabaseName("UX_DriverInstance_LogicalId")
|
||||
.HasFilter("[DriverInstanceId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("NamespaceId")
|
||||
.HasDatabaseName("IX_DriverInstance_Namespace");
|
||||
b.HasIndex("RawFolderId")
|
||||
.HasDatabaseName("IX_DriverInstance_RawFolder");
|
||||
|
||||
b.HasIndex("ClusterId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_DriverInstance_ClusterRoot_Name")
|
||||
.HasFilter("[RawFolderId] IS NULL");
|
||||
|
||||
b.HasIndex("ClusterId", "RawFolderId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_DriverInstance_Folder_Name")
|
||||
.HasFilter("[RawFolderId] IS NOT NULL");
|
||||
|
||||
b.ToTable("DriverInstance", null, t =>
|
||||
{
|
||||
@@ -525,19 +553,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("DeviceManualUri")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("DriverInstanceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
@@ -610,9 +629,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
|
||||
b.HasKey("EquipmentRowId");
|
||||
|
||||
b.HasIndex("DriverInstanceId")
|
||||
.HasDatabaseName("IX_Equipment_Driver");
|
||||
|
||||
b.HasIndex("EquipmentId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Equipment_LogicalId")
|
||||
@@ -643,148 +659,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.ToTable("Equipment", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ClusterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAtUtc")
|
||||
.HasColumnType("datetime2(3)");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<DateTime?>("FinalisedAtUtc")
|
||||
.HasColumnType("datetime2(3)");
|
||||
|
||||
b.Property<int>("RowsAccepted")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RowsRejected")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RowsStaged")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy", "FinalisedAtUtc")
|
||||
.HasDatabaseName("IX_EquipmentImportBatch_Creator_Finalised");
|
||||
|
||||
b.ToTable("EquipmentImportBatch", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportRow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("AssetLocation")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<Guid>("BatchId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("DeviceManualUri")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("EquipmentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("EquipmentUuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("HardwareRevision")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("IsAccepted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("LineNumberInFile")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("MachineCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Manufacturer")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("ManufacturerUri")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("RejectReason")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("SAPID")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("SerialNumber")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("SoftwareRevision")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("UnsAreaName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("UnsLineName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("YearOfConstruction")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("ZTag")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BatchId")
|
||||
.HasDatabaseName("IX_EquipmentImportRow_Batch");
|
||||
|
||||
b.ToTable("EquipmentImportRow", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ExternalIdReservation", b =>
|
||||
{
|
||||
b.Property<Guid>("ReservationId")
|
||||
@@ -894,66 +768,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.ToTable("LdapGroupRoleMapping", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Namespace", b =>
|
||||
{
|
||||
b.Property<Guid>("NamespaceRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("ClusterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Kind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("NamespaceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("NamespaceUri")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.HasKey("NamespaceRowId");
|
||||
|
||||
b.HasIndex("ClusterId")
|
||||
.HasDatabaseName("IX_Namespace_Cluster");
|
||||
|
||||
b.HasIndex("NamespaceId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_LogicalId")
|
||||
.HasFilter("[NamespaceId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("NamespaceUri")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_NamespaceUri");
|
||||
|
||||
b.HasIndex("ClusterId", "Kind")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_Cluster_Kind");
|
||||
|
||||
b.ToTable("Namespace", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeAcl", b =>
|
||||
{
|
||||
b.Property<Guid>("NodeAclRowId")
|
||||
@@ -1109,6 +923,63 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
|
||||
{
|
||||
b.Property<Guid>("RawFolderRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("ClusterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ParentRawFolderId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("RawFolderId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("RawFolderRowId");
|
||||
|
||||
b.HasIndex("ClusterId")
|
||||
.HasDatabaseName("IX_RawFolder_Cluster");
|
||||
|
||||
b.HasIndex("RawFolderId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_RawFolder_LogicalId")
|
||||
.HasFilter("[RawFolderId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("ClusterId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_RawFolder_ClusterRoot_Name")
|
||||
.HasFilter("[ParentRawFolderId] IS NULL");
|
||||
|
||||
b.HasIndex("ClusterId", "ParentRawFolderId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_RawFolder_Parent_Name")
|
||||
.HasFilter("[ParentRawFolderId] IS NOT NULL");
|
||||
|
||||
b.ToTable("RawFolder", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Script", b =>
|
||||
{
|
||||
b.Property<Guid>("ScriptRowId")
|
||||
@@ -1388,22 +1259,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("DriverInstanceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("EquipmentId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("FolderPath")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
@@ -1423,6 +1282,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TagGroupId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
@@ -1432,27 +1295,23 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
|
||||
b.HasKey("TagRowId");
|
||||
|
||||
b.HasIndex("EquipmentId")
|
||||
.HasDatabaseName("IX_Tag_Equipment")
|
||||
.HasFilter("[EquipmentId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("TagId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Tag_LogicalId")
|
||||
.HasFilter("[TagId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("DriverInstanceId", "DeviceId")
|
||||
.HasDatabaseName("IX_Tag_Driver_Device");
|
||||
|
||||
b.HasIndex("EquipmentId", "Name")
|
||||
b.HasIndex("DeviceId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Tag_EquipmentPath")
|
||||
.HasFilter("[EquipmentId] IS NOT NULL");
|
||||
.HasDatabaseName("UX_Tag_Device_Name")
|
||||
.HasFilter("[TagGroupId] IS NULL");
|
||||
|
||||
b.HasIndex("DriverInstanceId", "FolderPath", "Name")
|
||||
b.HasIndex("DeviceId", "TagGroupId")
|
||||
.HasDatabaseName("IX_Tag_Device");
|
||||
|
||||
b.HasIndex("DeviceId", "TagGroupId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Tag_FolderPath")
|
||||
.HasFilter("[EquipmentId] IS NULL");
|
||||
.HasDatabaseName("UX_Tag_Group_Name")
|
||||
.HasFilter("[TagGroupId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Tag", null, t =>
|
||||
{
|
||||
@@ -1460,6 +1319,63 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.TagGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("TagGroupRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ParentTagGroupId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TagGroupId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("TagGroupRowId");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.HasDatabaseName("IX_TagGroup_Device");
|
||||
|
||||
b.HasIndex("TagGroupId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_TagGroup_LogicalId")
|
||||
.HasFilter("[TagGroupId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("DeviceId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_TagGroup_DeviceRoot_Name")
|
||||
.HasFilter("[ParentTagGroupId] IS NULL");
|
||||
|
||||
b.HasIndex("DeviceId", "ParentTagGroupId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_TagGroup_Parent_Name")
|
||||
.HasFilter("[ParentTagGroupId] IS NOT NULL");
|
||||
|
||||
b.ToTable("TagGroup", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsArea", b =>
|
||||
{
|
||||
b.Property<Guid>("UnsAreaRowId")
|
||||
@@ -1556,6 +1472,60 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.ToTable("UnsLine", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsTagReference", b =>
|
||||
{
|
||||
b.Property<Guid>("UnsTagReferenceRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("DisplayNameOverride")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EquipmentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("UnsTagReferenceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("UnsTagReferenceRowId");
|
||||
|
||||
b.HasIndex("EquipmentId")
|
||||
.HasDatabaseName("IX_UnsTagReference_Equipment");
|
||||
|
||||
b.HasIndex("TagId")
|
||||
.HasDatabaseName("IX_UnsTagReference_Tag");
|
||||
|
||||
b.HasIndex("UnsTagReferenceId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_UnsTagReference_LogicalId")
|
||||
.HasFilter("[UnsTagReferenceId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("EquipmentId", "TagId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_UnsTagReference_Equip_Tag");
|
||||
|
||||
b.ToTable("UnsTagReference", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.VirtualTag", b =>
|
||||
{
|
||||
b.Property<Guid>("VirtualTagRowId")
|
||||
@@ -1660,17 +1630,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Cluster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportRow", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", "Batch")
|
||||
.WithMany("Rows")
|
||||
.HasForeignKey("BatchId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Batch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.LdapGroupRoleMapping", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
|
||||
@@ -1681,17 +1640,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Cluster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Namespace", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
|
||||
.WithMany("Namespaces")
|
||||
.HasForeignKey("ClusterId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Cluster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeDeploymentState", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Deployment", "Deployment")
|
||||
@@ -1711,6 +1659,15 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Node");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", null)
|
||||
.WithMany("RawFolders")
|
||||
.HasForeignKey("ClusterId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsArea", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
|
||||
@@ -1727,16 +1684,11 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Credentials");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", b =>
|
||||
{
|
||||
b.Navigation("Rows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", b =>
|
||||
{
|
||||
b.Navigation("Namespaces");
|
||||
|
||||
b.Navigation("Nodes");
|
||||
|
||||
b.Navigation("RawFolders");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Hand-authored SQL squashed into <c>V3Initial</c> from the retired per-migration proc files
|
||||
/// (<c>*_StoredProcedures.cs</c>, <c>*_ExtendComputeGenerationDiffWith*.cs</c>) and
|
||||
/// <c>AuthorizationGrants.cs</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Port audit (v3 WP1).</b> The v1 stored procs were built entirely on the
|
||||
/// <c>ConfigGeneration</c> / <c>GenerationId</c> / <c>ClusterNodeGenerationState</c>
|
||||
/// generation model, which <c>V2HostingAlignment</c> (2026-05-26) dropped outright — so
|
||||
/// those procs have been vestigial since v2 (SQL Server's deferred name resolution let them
|
||||
/// keep existing while referencing phantom tables). No production code calls them; the v2/v3
|
||||
/// deploy + diff path is entirely C# (ControlPlane <c>ConfigComposer</c>,
|
||||
/// <c>AddressSpaceChangeClassifier</c>, <c>DraftValidator</c>). Their only consumer is
|
||||
/// <c>AuthorizationTests</c>, which exercises the DB-role EXECUTE grants and expects
|
||||
/// RAISERROR / permission failures — never a successful data operation.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Under greenfield v3 the two-role authorization model (<c>OtOpcUaNode</c> /
|
||||
/// <c>OtOpcUaAdmin</c> + EXECUTE grants) is <b>retained</b> as a real security feature, so the
|
||||
/// eight proc <b>names</b> must exist for the grants + tests to bind. Each proc is rewritten
|
||||
/// here to be schema-valid against the v3 schema (references NO dropped table/column):
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><c>sp_GetCurrentGenerationForCluster</c> keeps its <c>ClusterNodeCredential</c> +
|
||||
/// <c>ClusterNode</c> "Unauthorized" auth check (both tables retained), then RAISERRORs a
|
||||
/// v3-retirement message — preserving the exact behavior AuthorizationTests asserts.</item>
|
||||
/// <item><c>sp_PublishGeneration</c> is granted to <c>OtOpcUaAdmin</c> only and RAISERRORs a
|
||||
/// retirement message (never the word "permission"), so the Node-denied vs Admin-allowed
|
||||
/// grant assertions still hold.</item>
|
||||
/// <item><c>sp_ComputeGenerationDiff</c> is rewritten to reference the v3 tables
|
||||
/// (RawFolder / DriverInstance / Device / TagGroup / Tag / Equipment / UnsTagReference /
|
||||
/// NodeAcl) as a schema-valid current-state signature inventory. A genuine
|
||||
/// generation-to-generation diff is architecturally impossible now (no GenerationId column);
|
||||
/// the real diff lives in the C# ControlPlane.</item>
|
||||
/// <item><c>sp_ReleaseExternalIdReservation</c> is ported verbatim — it only touches retained
|
||||
/// tables (<c>ExternalIdReservation</c>, <c>ConfigAuditLog</c>).</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public partial class V3Initial
|
||||
{
|
||||
private static void CreateStoredProcedures(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(GetCurrentGenerationForCluster);
|
||||
migrationBuilder.Sql(GetGenerationContent);
|
||||
migrationBuilder.Sql(RegisterNodeGenerationApplied);
|
||||
migrationBuilder.Sql(ValidateDraft);
|
||||
migrationBuilder.Sql(PublishGeneration);
|
||||
migrationBuilder.Sql(RollbackToGeneration);
|
||||
migrationBuilder.Sql(ComputeGenerationDiff);
|
||||
migrationBuilder.Sql(ReleaseExternalIdReservation);
|
||||
}
|
||||
|
||||
private static void DropStoredProcedures(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
foreach (var name in new[]
|
||||
{
|
||||
"sp_ReleaseExternalIdReservation", "sp_ComputeGenerationDiff", "sp_RollbackToGeneration",
|
||||
"sp_PublishGeneration", "sp_ValidateDraft", "sp_RegisterNodeGenerationApplied",
|
||||
"sp_GetGenerationContent", "sp_GetCurrentGenerationForCluster",
|
||||
})
|
||||
{
|
||||
migrationBuilder.Sql($"IF OBJECT_ID(N'dbo.{name}', N'P') IS NOT NULL DROP PROCEDURE dbo.{name};");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateAuthorizationGrants(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaNode') IS NULL
|
||||
CREATE ROLE OtOpcUaNode;
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaAdmin') IS NULL
|
||||
CREATE ROLE OtOpcUaAdmin;
|
||||
");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetCurrentGenerationForCluster TO OtOpcUaNode;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetGenerationContent TO OtOpcUaNode;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_RegisterNodeGenerationApplied TO OtOpcUaNode;
|
||||
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetCurrentGenerationForCluster TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_GetGenerationContent TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_ValidateDraft TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_PublishGeneration TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_RollbackToGeneration TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_ComputeGenerationDiff TO OtOpcUaAdmin;
|
||||
GRANT EXECUTE ON OBJECT::dbo.sp_ReleaseExternalIdReservation TO OtOpcUaAdmin;
|
||||
|
||||
DENY UPDATE, DELETE, INSERT ON SCHEMA::dbo TO OtOpcUaNode;
|
||||
DENY UPDATE, DELETE, INSERT ON SCHEMA::dbo TO OtOpcUaAdmin;
|
||||
DENY SELECT ON SCHEMA::dbo TO OtOpcUaNode;
|
||||
-- Admins may SELECT for reporting views in the future — grant views explicitly, not the schema.
|
||||
DENY SELECT ON SCHEMA::dbo TO OtOpcUaAdmin;
|
||||
");
|
||||
}
|
||||
|
||||
private static void DropAuthorizationGrants(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaNode') IS NOT NULL
|
||||
DROP ROLE OtOpcUaNode;
|
||||
IF DATABASE_PRINCIPAL_ID('OtOpcUaAdmin') IS NOT NULL
|
||||
DROP ROLE OtOpcUaAdmin;
|
||||
");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// v3 retirement stubs. The generation model is gone (see class remarks); these preserve the
|
||||
// authorization surface + the AuthorizationTests behaviors while referencing only v3 tables.
|
||||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
private const string GetCurrentGenerationForCluster = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_GetCurrentGenerationForCluster
|
||||
@NodeId nvarchar(64),
|
||||
@ClusterId nvarchar(64)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
-- Auth surface retained verbatim (ClusterNodeCredential + ClusterNode are v3 tables). An
|
||||
-- unbound caller still gets 'Unauthorized', which AuthorizationTests asserts.
|
||||
DECLARE @Caller nvarchar(128) = SUSER_SNAME();
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ClusterNodeCredential
|
||||
WHERE NodeId = @NodeId AND Value = @Caller AND Enabled = 1)
|
||||
BEGIN
|
||||
RAISERROR('Unauthorized: caller %s is not bound to NodeId %s', 16, 1, @Caller, @NodeId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ClusterNode
|
||||
WHERE NodeId = @NodeId AND ClusterId = @ClusterId AND Enabled = 1)
|
||||
BEGIN
|
||||
RAISERROR('Forbidden: NodeId %s does not belong to ClusterId %s', 16, 1, @NodeId, @ClusterId);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
RAISERROR('sp_GetCurrentGenerationForCluster is retired under v3: config/deploy state moved to the C# ControlPlane (Deployment table).', 16, 1);
|
||||
END
|
||||
";
|
||||
|
||||
private const string GetGenerationContent = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_GetGenerationContent
|
||||
@NodeId nvarchar(64),
|
||||
@GenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
RAISERROR('sp_GetGenerationContent is retired under v3: generation content is sealed into Deployment.ArtifactBlob by the C# ControlPlane.', 16, 1);
|
||||
END
|
||||
";
|
||||
|
||||
private const string RegisterNodeGenerationApplied = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_RegisterNodeGenerationApplied
|
||||
@NodeId nvarchar(64),
|
||||
@GenerationId bigint,
|
||||
@Status nvarchar(16),
|
||||
@Error nvarchar(max) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
RAISERROR('sp_RegisterNodeGenerationApplied is retired under v3: node-apply state is tracked in NodeDeploymentState by the C# ControlPlane.', 16, 1);
|
||||
END
|
||||
";
|
||||
|
||||
private const string ValidateDraft = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ValidateDraft
|
||||
@DraftGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
RAISERROR('sp_ValidateDraft is retired under v3: draft validation runs in the managed DraftValidator (Configuration.Validation).', 16, 1);
|
||||
END
|
||||
";
|
||||
|
||||
private const string PublishGeneration = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_PublishGeneration
|
||||
@ClusterId nvarchar(64),
|
||||
@DraftGenerationId bigint,
|
||||
@Notes nvarchar(1024) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
-- Message intentionally omits the word 'permission' so AuthorizationTests can distinguish a
|
||||
-- grant failure (Node role) from a body-level error (Admin role reaching the proc body).
|
||||
RAISERROR('sp_PublishGeneration is retired under v3: publishing is done by the C# ControlPlane (ConfigComposer + Deployment).', 16, 1);
|
||||
END
|
||||
";
|
||||
|
||||
private const string RollbackToGeneration = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_RollbackToGeneration
|
||||
@ClusterId nvarchar(64),
|
||||
@TargetGenerationId bigint,
|
||||
@Notes nvarchar(1024) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
RAISERROR('sp_RollbackToGeneration is retired under v3: rollback is a C# ControlPlane operation over the Deployment history.', 16, 1);
|
||||
END
|
||||
";
|
||||
|
||||
// Rewritten to the v3 schema. There is no GenerationId to diff (the per-generation table model
|
||||
// was dropped in v2), so this emits a current-state signature inventory across the v3 tables —
|
||||
// referencing RawFolder, DriverInstance, Device, TagGroup, Tag, Equipment, UnsTagReference, and
|
||||
// NodeAcl. The genuine generation-to-generation diff now lives in the C# ControlPlane
|
||||
// (AddressSpaceChangeClassifier); this proc is retained only to carry the OtOpcUaAdmin EXECUTE
|
||||
// grant and to keep the v3 table surface referenced from SQL.
|
||||
private const string ComputeGenerationDiff = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ComputeGenerationDiff
|
||||
@FromGenerationId bigint,
|
||||
@ToGenerationId bigint
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
-- @FromGenerationId / @ToGenerationId are ignored: the v3 config tables are single live-state
|
||||
-- (no GenerationId column). Emit a current-state signature per row so the shape (TableName,
|
||||
-- LogicalId, Sig) still round-trips through any legacy caller.
|
||||
SELECT TableName, LogicalId, Sig FROM (
|
||||
SELECT 'RawFolder' AS TableName, RawFolderId AS LogicalId, CHECKSUM(ClusterId, ParentRawFolderId, Name, SortOrder) AS Sig FROM dbo.RawFolder
|
||||
UNION ALL
|
||||
SELECT 'DriverInstance', DriverInstanceId, CHECKSUM(ClusterId, RawFolderId, Name, DriverType, Enabled, CONVERT(varchar(max), DriverConfig)) FROM dbo.DriverInstance
|
||||
UNION ALL
|
||||
SELECT 'Device', DeviceId, CHECKSUM(DriverInstanceId, Name, Enabled, CONVERT(varchar(max), DeviceConfig)) FROM dbo.Device
|
||||
UNION ALL
|
||||
SELECT 'TagGroup', TagGroupId, CHECKSUM(DeviceId, ParentTagGroupId, Name, SortOrder) FROM dbo.TagGroup
|
||||
UNION ALL
|
||||
SELECT 'Tag', TagId, CHECKSUM(DeviceId, TagGroupId, PollGroupId, Name, DataType, AccessLevel, WriteIdempotent, CONVERT(varchar(max), TagConfig)) FROM dbo.Tag
|
||||
UNION ALL
|
||||
SELECT 'Equipment', EquipmentId, CHECKSUM(EquipmentUuid, UnsLineId, Name, MachineCode, ZTag, SAPID, EquipmentClassRef, Manufacturer, Model, SerialNumber) FROM dbo.Equipment
|
||||
UNION ALL
|
||||
SELECT 'UnsTagReference', UnsTagReferenceId, CHECKSUM(EquipmentId, TagId, DisplayNameOverride, SortOrder) FROM dbo.UnsTagReference
|
||||
UNION ALL
|
||||
SELECT 'NodeAcl', NodeAclId, CHECKSUM(ClusterId, LdapGroup, ScopeKind, ScopeId, PermissionFlags, Notes) FROM dbo.NodeAcl
|
||||
) AS inventory;
|
||||
END
|
||||
";
|
||||
|
||||
// Ported verbatim — touches only retained tables (ExternalIdReservation, ConfigAuditLog).
|
||||
private const string ReleaseExternalIdReservation = @"
|
||||
CREATE OR ALTER PROCEDURE dbo.sp_ReleaseExternalIdReservation
|
||||
@Kind nvarchar(16),
|
||||
@Value nvarchar(64),
|
||||
@ReleaseReason nvarchar(512)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
|
||||
IF @ReleaseReason IS NULL OR LEN(@ReleaseReason) = 0
|
||||
BEGIN
|
||||
RAISERROR('ReleaseReason is required', 16, 1);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
UPDATE dbo.ExternalIdReservation
|
||||
SET ReleasedAt = SYSUTCDATETIME(),
|
||||
ReleasedBy = SUSER_SNAME(),
|
||||
ReleaseReason = @ReleaseReason
|
||||
WHERE Kind = @Kind AND Value = @Value AND ReleasedAt IS NULL;
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
BEGIN
|
||||
RAISERROR('No active reservation found for (%s, %s)', 16, 1, @Kind, @Value);
|
||||
RETURN;
|
||||
END
|
||||
|
||||
-- Escape both caller-supplied values via STRING_ESCAPE so quotes/backslashes cannot break the
|
||||
-- JSON document or inject additional structure into the audit record.
|
||||
INSERT dbo.ConfigAuditLog (Principal, EventType, DetailsJson)
|
||||
VALUES (SUSER_SNAME(), 'ExternalIdReleased',
|
||||
CONCAT('{""kind"":""', STRING_ESCAPE(@Kind, 'json'),
|
||||
'"",""value"":""', STRING_ESCAPE(@Value, 'json'), '""}'));
|
||||
END
|
||||
";
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+209
-271
@@ -373,6 +373,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.HasIndex("DriverInstanceId")
|
||||
.HasDatabaseName("IX_Device_Driver");
|
||||
|
||||
b.HasIndex("DriverInstanceId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Device_Driver_Name");
|
||||
|
||||
b.ToTable("Device", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_Device_DeviceConfig_IsJson", "ISJSON(DeviceConfig) = 1");
|
||||
@@ -452,8 +456,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("NamespaceId")
|
||||
.IsRequired()
|
||||
b.Property<string>("RawFolderId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
@@ -476,8 +479,18 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasDatabaseName("UX_DriverInstance_LogicalId")
|
||||
.HasFilter("[DriverInstanceId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("NamespaceId")
|
||||
.HasDatabaseName("IX_DriverInstance_Namespace");
|
||||
b.HasIndex("RawFolderId")
|
||||
.HasDatabaseName("IX_DriverInstance_RawFolder");
|
||||
|
||||
b.HasIndex("ClusterId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_DriverInstance_ClusterRoot_Name")
|
||||
.HasFilter("[RawFolderId] IS NULL");
|
||||
|
||||
b.HasIndex("ClusterId", "RawFolderId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_DriverInstance_Folder_Name")
|
||||
.HasFilter("[RawFolderId] IS NOT NULL");
|
||||
|
||||
b.ToTable("DriverInstance", null, t =>
|
||||
{
|
||||
@@ -537,18 +550,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("DeviceManualUri")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("DriverInstanceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
@@ -621,9 +626,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
|
||||
b.HasKey("EquipmentRowId");
|
||||
|
||||
b.HasIndex("DriverInstanceId")
|
||||
.HasDatabaseName("IX_Equipment_Driver");
|
||||
|
||||
b.HasIndex("EquipmentId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Equipment_LogicalId")
|
||||
@@ -654,148 +656,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.ToTable("Equipment", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ClusterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAtUtc")
|
||||
.HasColumnType("datetime2(3)");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<DateTime?>("FinalisedAtUtc")
|
||||
.HasColumnType("datetime2(3)");
|
||||
|
||||
b.Property<int>("RowsAccepted")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RowsRejected")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RowsStaged")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy", "FinalisedAtUtc")
|
||||
.HasDatabaseName("IX_EquipmentImportBatch_Creator_Finalised");
|
||||
|
||||
b.ToTable("EquipmentImportBatch", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportRow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("AssetLocation")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<Guid>("BatchId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("DeviceManualUri")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("EquipmentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("EquipmentUuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("HardwareRevision")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("IsAccepted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("LineNumberInFile")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("MachineCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("Manufacturer")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("ManufacturerUri")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("RejectReason")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("SAPID")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("SerialNumber")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("SoftwareRevision")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("UnsAreaName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("UnsLineName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("YearOfConstruction")
|
||||
.HasMaxLength(8)
|
||||
.HasColumnType("nvarchar(8)");
|
||||
|
||||
b.Property<string>("ZTag")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BatchId")
|
||||
.HasDatabaseName("IX_EquipmentImportRow_Batch");
|
||||
|
||||
b.ToTable("EquipmentImportRow", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ExternalIdReservation", b =>
|
||||
{
|
||||
b.Property<Guid>("ReservationId")
|
||||
@@ -905,66 +765,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.ToTable("LdapGroupRoleMapping", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Namespace", b =>
|
||||
{
|
||||
b.Property<Guid>("NamespaceRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("ClusterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Kind")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("NamespaceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("NamespaceUri")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.HasKey("NamespaceRowId");
|
||||
|
||||
b.HasIndex("ClusterId")
|
||||
.HasDatabaseName("IX_Namespace_Cluster");
|
||||
|
||||
b.HasIndex("NamespaceId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_LogicalId")
|
||||
.HasFilter("[NamespaceId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("NamespaceUri")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_NamespaceUri");
|
||||
|
||||
b.HasIndex("ClusterId", "Kind")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_Cluster_Kind");
|
||||
|
||||
b.ToTable("Namespace", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeAcl", b =>
|
||||
{
|
||||
b.Property<Guid>("NodeAclRowId")
|
||||
@@ -1120,6 +920,63 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
|
||||
{
|
||||
b.Property<Guid>("RawFolderRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("ClusterId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ParentRawFolderId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("RawFolderId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("RawFolderRowId");
|
||||
|
||||
b.HasIndex("ClusterId")
|
||||
.HasDatabaseName("IX_RawFolder_Cluster");
|
||||
|
||||
b.HasIndex("RawFolderId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_RawFolder_LogicalId")
|
||||
.HasFilter("[RawFolderId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("ClusterId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_RawFolder_ClusterRoot_Name")
|
||||
.HasFilter("[ParentRawFolderId] IS NULL");
|
||||
|
||||
b.HasIndex("ClusterId", "ParentRawFolderId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_RawFolder_Parent_Name")
|
||||
.HasFilter("[ParentRawFolderId] IS NOT NULL");
|
||||
|
||||
b.ToTable("RawFolder", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Script", b =>
|
||||
{
|
||||
b.Property<Guid>("ScriptRowId")
|
||||
@@ -1399,22 +1256,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("DriverInstanceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("EquipmentId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("FolderPath")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
@@ -1434,6 +1279,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("TagGroupId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
@@ -1443,27 +1292,23 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
|
||||
b.HasKey("TagRowId");
|
||||
|
||||
b.HasIndex("EquipmentId")
|
||||
.HasDatabaseName("IX_Tag_Equipment")
|
||||
.HasFilter("[EquipmentId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("TagId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Tag_LogicalId")
|
||||
.HasFilter("[TagId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("DriverInstanceId", "DeviceId")
|
||||
.HasDatabaseName("IX_Tag_Driver_Device");
|
||||
|
||||
b.HasIndex("EquipmentId", "Name")
|
||||
b.HasIndex("DeviceId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Tag_EquipmentPath")
|
||||
.HasFilter("[EquipmentId] IS NOT NULL");
|
||||
.HasDatabaseName("UX_Tag_Device_Name")
|
||||
.HasFilter("[TagGroupId] IS NULL");
|
||||
|
||||
b.HasIndex("DriverInstanceId", "FolderPath", "Name")
|
||||
b.HasIndex("DeviceId", "TagGroupId")
|
||||
.HasDatabaseName("IX_Tag_Device");
|
||||
|
||||
b.HasIndex("DeviceId", "TagGroupId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_Tag_FolderPath")
|
||||
.HasFilter("[EquipmentId] IS NULL");
|
||||
.HasDatabaseName("UX_Tag_Group_Name")
|
||||
.HasFilter("[TagGroupId] IS NOT NULL");
|
||||
|
||||
b.ToTable("Tag", null, t =>
|
||||
{
|
||||
@@ -1471,6 +1316,63 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.TagGroup", b =>
|
||||
{
|
||||
b.Property<Guid>("TagGroupRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("DeviceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("ParentTagGroupId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TagGroupId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("TagGroupRowId");
|
||||
|
||||
b.HasIndex("DeviceId")
|
||||
.HasDatabaseName("IX_TagGroup_Device");
|
||||
|
||||
b.HasIndex("TagGroupId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_TagGroup_LogicalId")
|
||||
.HasFilter("[TagGroupId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("DeviceId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_TagGroup_DeviceRoot_Name")
|
||||
.HasFilter("[ParentTagGroupId] IS NULL");
|
||||
|
||||
b.HasIndex("DeviceId", "ParentTagGroupId", "Name")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_TagGroup_Parent_Name")
|
||||
.HasFilter("[ParentTagGroupId] IS NOT NULL");
|
||||
|
||||
b.ToTable("TagGroup", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsArea", b =>
|
||||
{
|
||||
b.Property<Guid>("UnsAreaRowId")
|
||||
@@ -1567,6 +1469,60 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.ToTable("UnsLine", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsTagReference", b =>
|
||||
{
|
||||
b.Property<Guid>("UnsTagReferenceRowId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier")
|
||||
.HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
|
||||
b.Property<string>("DisplayNameOverride")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("EquipmentId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("rowversion");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("UnsTagReferenceId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("UnsTagReferenceRowId");
|
||||
|
||||
b.HasIndex("EquipmentId")
|
||||
.HasDatabaseName("IX_UnsTagReference_Equipment");
|
||||
|
||||
b.HasIndex("TagId")
|
||||
.HasDatabaseName("IX_UnsTagReference_Tag");
|
||||
|
||||
b.HasIndex("UnsTagReferenceId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_UnsTagReference_LogicalId")
|
||||
.HasFilter("[UnsTagReferenceId] IS NOT NULL");
|
||||
|
||||
b.HasIndex("EquipmentId", "TagId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UX_UnsTagReference_Equip_Tag");
|
||||
|
||||
b.ToTable("UnsTagReference", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.VirtualTag", b =>
|
||||
{
|
||||
b.Property<Guid>("VirtualTagRowId")
|
||||
@@ -1671,17 +1627,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Cluster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportRow", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", "Batch")
|
||||
.WithMany("Rows")
|
||||
.HasForeignKey("BatchId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Batch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.LdapGroupRoleMapping", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
|
||||
@@ -1692,17 +1637,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Cluster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Namespace", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
|
||||
.WithMany("Namespaces")
|
||||
.HasForeignKey("ClusterId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Cluster");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeDeploymentState", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Deployment", "Deployment")
|
||||
@@ -1722,6 +1656,15 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Node");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", null)
|
||||
.WithMany("RawFolders")
|
||||
.HasForeignKey("ClusterId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsArea", b =>
|
||||
{
|
||||
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
|
||||
@@ -1738,16 +1681,11 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
|
||||
b.Navigation("Credentials");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", b =>
|
||||
{
|
||||
b.Navigation("Rows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", b =>
|
||||
{
|
||||
b.Navigation("Namespaces");
|
||||
|
||||
b.Navigation("Nodes");
|
||||
|
||||
b.Navigation("RawFolders");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
public DbSet<ClusterNode> ClusterNodes => Set<ClusterNode>();
|
||||
/// <summary>Gets the DbSet of cluster node credentials.</summary>
|
||||
public DbSet<ClusterNodeCredential> ClusterNodeCredentials => Set<ClusterNodeCredential>();
|
||||
/// <summary>Gets the DbSet of namespaces.</summary>
|
||||
public DbSet<Namespace> Namespaces => Set<Namespace>();
|
||||
/// <summary>Gets the DbSet of v3 Raw-tree driver-organizing folders.</summary>
|
||||
public DbSet<RawFolder> RawFolders => Set<RawFolder>();
|
||||
/// <summary>Gets the DbSet of v3 Raw-tree tag groups (nestable under a device).</summary>
|
||||
public DbSet<TagGroup> TagGroups => Set<TagGroup>();
|
||||
/// <summary>Gets the DbSet of v3 UNS tag references (equipment → raw tag projection).</summary>
|
||||
public DbSet<UnsTagReference> UnsTagReferences => Set<UnsTagReference>();
|
||||
/// <summary>Gets the DbSet of UNS areas.</summary>
|
||||
public DbSet<UnsArea> UnsAreas => Set<UnsArea>();
|
||||
/// <summary>Gets the DbSet of UNS lines.</summary>
|
||||
@@ -46,10 +50,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
public DbSet<DriverInstanceResilienceStatus> DriverInstanceResilienceStatuses => Set<DriverInstanceResilienceStatus>();
|
||||
/// <summary>Gets the DbSet of LDAP group role mappings.</summary>
|
||||
public DbSet<LdapGroupRoleMapping> LdapGroupRoleMappings => Set<LdapGroupRoleMapping>();
|
||||
/// <summary>Gets the DbSet of equipment import batches.</summary>
|
||||
public DbSet<EquipmentImportBatch> EquipmentImportBatches => Set<EquipmentImportBatch>();
|
||||
/// <summary>Gets the DbSet of equipment import rows.</summary>
|
||||
public DbSet<EquipmentImportRow> EquipmentImportRows => Set<EquipmentImportRow>();
|
||||
/// <summary>Gets the DbSet of scripts.</summary>
|
||||
public DbSet<Script> Scripts => Set<Script>();
|
||||
/// <summary>Gets the DbSet of virtual tags.</summary>
|
||||
@@ -79,13 +79,15 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
ConfigureServerCluster(modelBuilder);
|
||||
ConfigureClusterNode(modelBuilder);
|
||||
ConfigureClusterNodeCredential(modelBuilder);
|
||||
ConfigureNamespace(modelBuilder);
|
||||
ConfigureRawFolder(modelBuilder);
|
||||
ConfigureUnsArea(modelBuilder);
|
||||
ConfigureUnsLine(modelBuilder);
|
||||
ConfigureDriverInstance(modelBuilder);
|
||||
ConfigureDevice(modelBuilder);
|
||||
ConfigureTagGroup(modelBuilder);
|
||||
ConfigureEquipment(modelBuilder);
|
||||
ConfigureTag(modelBuilder);
|
||||
ConfigureUnsTagReference(modelBuilder);
|
||||
ConfigurePollGroup(modelBuilder);
|
||||
ConfigureNodeAcl(modelBuilder);
|
||||
ConfigureConfigAuditLog(modelBuilder);
|
||||
@@ -93,7 +95,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
ConfigureDriverHostStatus(modelBuilder);
|
||||
ConfigureDriverInstanceResilienceStatus(modelBuilder);
|
||||
ConfigureLdapGroupRoleMapping(modelBuilder);
|
||||
ConfigureEquipmentImportBatch(modelBuilder);
|
||||
ConfigureScript(modelBuilder);
|
||||
ConfigureVirtualTag(modelBuilder);
|
||||
ConfigureScriptedAlarm(modelBuilder);
|
||||
@@ -183,32 +184,39 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureNamespace(ModelBuilder modelBuilder)
|
||||
private static void ConfigureRawFolder(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Namespace>(e =>
|
||||
modelBuilder.Entity<RawFolder>(e =>
|
||||
{
|
||||
e.ToTable("Namespace");
|
||||
e.HasKey(x => x.NamespaceRowId);
|
||||
e.Property(x => x.NamespaceRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.NamespaceId).HasMaxLength(64);
|
||||
e.ToTable("RawFolder");
|
||||
e.HasKey(x => x.RawFolderRowId);
|
||||
e.Property(x => x.RawFolderRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.RawFolderId).HasMaxLength(64);
|
||||
e.Property(x => x.ClusterId).HasMaxLength(64);
|
||||
e.Property(x => x.Kind).HasConversion<string>().HasMaxLength(32);
|
||||
e.Property(x => x.NamespaceUri).HasMaxLength(256);
|
||||
e.Property(x => x.Notes).HasMaxLength(1024);
|
||||
e.Property(x => x.ParentRawFolderId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Cluster).WithMany(c => c.Namespaces)
|
||||
// Cluster-rooted. Restrict so a cluster with folders cannot be deleted out from under
|
||||
// them (matches the OnDelete(Restrict) convention across the config schema). Child FKs
|
||||
// (DriverInstance.RawFolderId, RawFolder.ParentRawFolderId) are logical-only, so
|
||||
// non-empty-folder-delete is enforced at the service layer in later batches.
|
||||
e.HasOne<ServerCluster>().WithMany(c => c.RawFolders)
|
||||
.HasForeignKey(x => x.ClusterId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
e.HasIndex(x => new { x.ClusterId, x.Kind }).IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_Cluster_Kind");
|
||||
e.HasIndex(x => x.NamespaceUri).IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_NamespaceUri");
|
||||
e.HasIndex(x => x.NamespaceId).IsUnique()
|
||||
.HasDatabaseName("UX_Namespace_LogicalId");
|
||||
e.HasIndex(x => x.ClusterId)
|
||||
.HasDatabaseName("IX_Namespace_Cluster");
|
||||
e.HasIndex(x => x.RawFolderId).IsUnique().HasDatabaseName("UX_RawFolder_LogicalId");
|
||||
e.HasIndex(x => x.ClusterId).HasDatabaseName("IX_RawFolder_Cluster");
|
||||
|
||||
// Sibling-name uniqueness. Two filtered indexes cover the nullable ParentRawFolderId:
|
||||
// one for nested folders (Parent NOT NULL), one for cluster-root folders (Parent NULL),
|
||||
// mirroring the Tag filtered-index convention.
|
||||
e.HasIndex(x => new { x.ClusterId, x.ParentRawFolderId, x.Name }).IsUnique()
|
||||
.HasFilter("[ParentRawFolderId] IS NOT NULL")
|
||||
.HasDatabaseName("UX_RawFolder_Parent_Name");
|
||||
e.HasIndex(x => new { x.ClusterId, x.Name }).IsUnique()
|
||||
.HasFilter("[ParentRawFolderId] IS NULL")
|
||||
.HasDatabaseName("UX_RawFolder_ClusterRoot_Name");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -267,7 +275,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.DriverInstanceRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.DriverInstanceId).HasMaxLength(64);
|
||||
e.Property(x => x.ClusterId).HasMaxLength(64);
|
||||
e.Property(x => x.NamespaceId).HasMaxLength(64);
|
||||
e.Property(x => x.RawFolderId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.DriverType).HasMaxLength(32);
|
||||
e.Property(x => x.DriverConfig).HasColumnType("nvarchar(max)");
|
||||
@@ -277,8 +285,18 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.HasOne(x => x.Cluster).WithMany().HasForeignKey(x => x.ClusterId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
e.HasIndex(x => x.ClusterId).HasDatabaseName("IX_DriverInstance_Cluster");
|
||||
e.HasIndex(x => x.NamespaceId).HasDatabaseName("IX_DriverInstance_Namespace");
|
||||
e.HasIndex(x => x.RawFolderId).HasDatabaseName("IX_DriverInstance_RawFolder");
|
||||
e.HasIndex(x => x.DriverInstanceId).IsUnique().HasDatabaseName("UX_DriverInstance_LogicalId");
|
||||
|
||||
// v3 sibling-name uniqueness (a RawPath segment). Two filtered indexes cover the nullable
|
||||
// RawFolderId: nested drivers keyed by (ClusterId, RawFolderId, Name), cluster-root drivers
|
||||
// by (ClusterId, Name).
|
||||
e.HasIndex(x => new { x.ClusterId, x.RawFolderId, x.Name }).IsUnique()
|
||||
.HasFilter("[RawFolderId] IS NOT NULL")
|
||||
.HasDatabaseName("UX_DriverInstance_Folder_Name");
|
||||
e.HasIndex(x => new { x.ClusterId, x.Name }).IsUnique()
|
||||
.HasFilter("[RawFolderId] IS NULL")
|
||||
.HasDatabaseName("UX_DriverInstance_ClusterRoot_Name");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,6 +318,38 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
|
||||
e.HasIndex(x => x.DriverInstanceId).HasDatabaseName("IX_Device_Driver");
|
||||
e.HasIndex(x => x.DeviceId).IsUnique().HasDatabaseName("UX_Device_LogicalId");
|
||||
// v3 sibling-name uniqueness within a driver (a RawPath segment).
|
||||
e.HasIndex(x => new { x.DriverInstanceId, x.Name }).IsUnique()
|
||||
.HasDatabaseName("UX_Device_Driver_Name");
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureTagGroup(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<TagGroup>(e =>
|
||||
{
|
||||
e.ToTable("TagGroup");
|
||||
e.HasKey(x => x.TagGroupRowId);
|
||||
e.Property(x => x.TagGroupRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.TagGroupId).HasMaxLength(64);
|
||||
e.Property(x => x.DeviceId).HasMaxLength(64);
|
||||
e.Property(x => x.ParentTagGroupId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasIndex(x => x.TagGroupId).IsUnique().HasDatabaseName("UX_TagGroup_LogicalId");
|
||||
e.HasIndex(x => x.DeviceId).HasDatabaseName("IX_TagGroup_Device");
|
||||
|
||||
// Sibling-name uniqueness. Two filtered indexes cover the nullable ParentTagGroupId:
|
||||
// nested groups keyed by (DeviceId, ParentTagGroupId, Name), device-root groups by
|
||||
// (DeviceId, Name). DeviceId/ParentTagGroupId are logical FKs (no navigation); non-empty
|
||||
// group deletes are blocked at the service layer in later batches.
|
||||
e.HasIndex(x => new { x.DeviceId, x.ParentTagGroupId, x.Name }).IsUnique()
|
||||
.HasFilter("[ParentTagGroupId] IS NOT NULL")
|
||||
.HasDatabaseName("UX_TagGroup_Parent_Name");
|
||||
e.HasIndex(x => new { x.DeviceId, x.Name }).IsUnique()
|
||||
.HasFilter("[ParentTagGroupId] IS NULL")
|
||||
.HasDatabaseName("UX_TagGroup_DeviceRoot_Name");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -311,8 +361,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.HasKey(x => x.EquipmentRowId);
|
||||
e.Property(x => x.EquipmentRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.EquipmentId).HasMaxLength(64);
|
||||
e.Property(x => x.DriverInstanceId).HasMaxLength(64);
|
||||
e.Property(x => x.DeviceId).HasMaxLength(64);
|
||||
e.Property(x => x.UnsLineId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(32);
|
||||
e.Property(x => x.MachineCode).HasMaxLength(64);
|
||||
@@ -329,7 +377,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.EquipmentClassRef).HasMaxLength(128);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasIndex(x => x.DriverInstanceId).HasDatabaseName("IX_Equipment_Driver");
|
||||
e.HasIndex(x => x.UnsLineId).HasDatabaseName("IX_Equipment_Line");
|
||||
e.HasIndex(x => x.EquipmentId).IsUnique().HasDatabaseName("UX_Equipment_LogicalId");
|
||||
e.HasIndex(x => new { x.UnsLineId, x.Name }).IsUnique().HasDatabaseName("UX_Equipment_LinePath");
|
||||
@@ -351,28 +398,29 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.HasKey(x => x.TagRowId);
|
||||
e.Property(x => x.TagRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.TagId).HasMaxLength(64);
|
||||
e.Property(x => x.DriverInstanceId).HasMaxLength(64);
|
||||
e.Property(x => x.DeviceId).HasMaxLength(64);
|
||||
e.Property(x => x.EquipmentId).HasMaxLength(64);
|
||||
// v3: raw-only tag. Required DeviceId FK; nullable TagGroupId. EquipmentId/FolderPath/
|
||||
// DriverInstanceId are retired — identity is the RawPath (device + optional group + name).
|
||||
e.Property(x => x.DeviceId).HasMaxLength(64).IsRequired();
|
||||
e.Property(x => x.TagGroupId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.FolderPath).HasMaxLength(512);
|
||||
e.Property(x => x.DataType).HasMaxLength(32);
|
||||
e.Property(x => x.AccessLevel).HasConversion<string>().HasMaxLength(16);
|
||||
e.Property(x => x.PollGroupId).HasMaxLength(64);
|
||||
e.Property(x => x.TagConfig).HasColumnType("nvarchar(max)");
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasIndex(x => new { x.DriverInstanceId, x.DeviceId }).HasDatabaseName("IX_Tag_Driver_Device");
|
||||
e.HasIndex(x => x.EquipmentId)
|
||||
.HasFilter("[EquipmentId] IS NOT NULL")
|
||||
.HasDatabaseName("IX_Tag_Equipment");
|
||||
e.HasIndex(x => new { x.DeviceId, x.TagGroupId }).HasDatabaseName("IX_Tag_Device");
|
||||
e.HasIndex(x => x.TagId).IsUnique().HasDatabaseName("UX_Tag_LogicalId");
|
||||
e.HasIndex(x => new { x.EquipmentId, x.Name }).IsUnique()
|
||||
.HasFilter("[EquipmentId] IS NOT NULL")
|
||||
.HasDatabaseName("UX_Tag_EquipmentPath");
|
||||
e.HasIndex(x => new { x.DriverInstanceId, x.FolderPath, x.Name }).IsUnique()
|
||||
.HasFilter("[EquipmentId] IS NULL")
|
||||
.HasDatabaseName("UX_Tag_FolderPath");
|
||||
|
||||
// Sibling-name uniqueness (a RawPath segment). Two filtered indexes cover the nullable
|
||||
// TagGroupId: grouped tags keyed by (DeviceId, TagGroupId, Name), device-root tags by
|
||||
// (DeviceId, Name).
|
||||
e.HasIndex(x => new { x.DeviceId, x.TagGroupId, x.Name }).IsUnique()
|
||||
.HasFilter("[TagGroupId] IS NOT NULL")
|
||||
.HasDatabaseName("UX_Tag_Group_Name");
|
||||
e.HasIndex(x => new { x.DeviceId, x.Name }).IsUnique()
|
||||
.HasFilter("[TagGroupId] IS NULL")
|
||||
.HasDatabaseName("UX_Tag_Device_Name");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -567,51 +615,28 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureEquipmentImportBatch(ModelBuilder modelBuilder)
|
||||
private static void ConfigureUnsTagReference(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<EquipmentImportBatch>(e =>
|
||||
modelBuilder.Entity<UnsTagReference>(e =>
|
||||
{
|
||||
e.ToTable("EquipmentImportBatch");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.ClusterId).HasMaxLength(64);
|
||||
e.Property(x => x.CreatedBy).HasMaxLength(128);
|
||||
e.Property(x => x.CreatedAtUtc).HasColumnType("datetime2(3)");
|
||||
e.Property(x => x.FinalisedAtUtc).HasColumnType("datetime2(3)");
|
||||
|
||||
// Admin preview modal filters by user; finalise / drop both hit this index.
|
||||
e.HasIndex(x => new { x.CreatedBy, x.FinalisedAtUtc })
|
||||
.HasDatabaseName("IX_EquipmentImportBatch_Creator_Finalised");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<EquipmentImportRow>(e =>
|
||||
{
|
||||
e.ToTable("EquipmentImportRow");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.ZTag).HasMaxLength(128);
|
||||
e.Property(x => x.MachineCode).HasMaxLength(128);
|
||||
e.Property(x => x.SAPID).HasMaxLength(128);
|
||||
e.ToTable("UnsTagReference");
|
||||
e.HasKey(x => x.UnsTagReferenceRowId);
|
||||
e.Property(x => x.UnsTagReferenceRowId).HasDefaultValueSql("NEWSEQUENTIALID()");
|
||||
e.Property(x => x.UnsTagReferenceId).HasMaxLength(64);
|
||||
e.Property(x => x.EquipmentId).HasMaxLength(64);
|
||||
e.Property(x => x.EquipmentUuid).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.UnsAreaName).HasMaxLength(64);
|
||||
e.Property(x => x.UnsLineName).HasMaxLength(64);
|
||||
e.Property(x => x.Manufacturer).HasMaxLength(256);
|
||||
e.Property(x => x.Model).HasMaxLength(256);
|
||||
e.Property(x => x.SerialNumber).HasMaxLength(256);
|
||||
e.Property(x => x.HardwareRevision).HasMaxLength(64);
|
||||
e.Property(x => x.SoftwareRevision).HasMaxLength(64);
|
||||
e.Property(x => x.YearOfConstruction).HasMaxLength(8);
|
||||
e.Property(x => x.AssetLocation).HasMaxLength(512);
|
||||
e.Property(x => x.ManufacturerUri).HasMaxLength(512);
|
||||
e.Property(x => x.DeviceManualUri).HasMaxLength(512);
|
||||
e.Property(x => x.RejectReason).HasMaxLength(512);
|
||||
e.Property(x => x.TagId).HasMaxLength(64);
|
||||
e.Property(x => x.DisplayNameOverride).HasMaxLength(128);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Batch)
|
||||
.WithMany(b => b.Rows)
|
||||
.HasForeignKey(x => x.BatchId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
e.HasIndex(x => x.UnsTagReferenceId).IsUnique().HasDatabaseName("UX_UnsTagReference_LogicalId");
|
||||
e.HasIndex(x => x.EquipmentId).HasDatabaseName("IX_UnsTagReference_Equipment");
|
||||
e.HasIndex(x => x.TagId).HasDatabaseName("IX_UnsTagReference_Tag");
|
||||
|
||||
e.HasIndex(x => x.BatchId).HasDatabaseName("IX_EquipmentImportRow_Batch");
|
||||
// One reference per (equipment, raw tag). EquipmentId/TagId are logical FKs (no
|
||||
// navigation); the "raw-tag delete blocked while referenced" rule is enforced at the
|
||||
// service layer in later batches.
|
||||
e.HasIndex(x => new { x.EquipmentId, x.TagId }).IsUnique()
|
||||
.HasDatabaseName("UX_UnsTagReference_Equip_Tag");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@ public sealed class DraftSnapshot
|
||||
/// </summary>
|
||||
public string? Site { get; init; }
|
||||
|
||||
/// <summary>Gets the list of OPC UA namespaces.</summary>
|
||||
public IReadOnlyList<Namespace> Namespaces { get; init; } = [];
|
||||
// v3: the Namespace entity is retired (the two OPC UA namespaces are implicit). The old
|
||||
// Namespaces list + namespace-binding validation are gone. WP4 (Wave C) owns the new v3
|
||||
// rules (raw-name charset, historized-tagname length, UNS effective-leaf uniqueness).
|
||||
/// <summary>Gets the list of driver instances.</summary>
|
||||
public IReadOnlyList<DriverInstance> DriverInstances { get; init; } = [];
|
||||
/// <summary>Gets the list of devices.</summary>
|
||||
|
||||
@@ -36,7 +36,7 @@ public static class DraftSnapshotFactory
|
||||
// EquipmentUuid checks run in separate validator passes — no rule here reads these fields.
|
||||
GenerationId = 0, // generation model dropped; placeholder (no rule reads it)
|
||||
ClusterId = string.Empty, // global snapshot; rules compare entity ClusterId fields, not this
|
||||
Namespaces = await db.Namespaces.AsNoTracking().ToListAsync(ct),
|
||||
// v3: Namespace entity retired — no namespace list to materialize.
|
||||
DriverInstances = await db.DriverInstances.AsNoTracking().ToListAsync(ct),
|
||||
Devices = await db.Devices.AsNoTracking().ToListAsync(ct),
|
||||
UnsAreas = await db.UnsAreas.AsNoTracking().ToListAsync(ct),
|
||||
|
||||
@@ -30,56 +30,34 @@ public static class DraftValidator
|
||||
ValidateUnsSegments(draft, errors);
|
||||
ValidatePathLength(draft, errors);
|
||||
ValidateEquipmentUuidImmutability(draft, errors);
|
||||
ValidateSameClusterNamespaceBinding(draft, errors);
|
||||
ValidateReservationPreflight(draft, errors);
|
||||
ValidateEquipmentIdDerivation(draft, errors);
|
||||
ValidateNoEquipmentSignalNameCollision(draft, errors);
|
||||
ValidateGalaxyTagFullName(draft, errors);
|
||||
|
||||
// v3 WP1 note: the retired rules ValidateSameClusterNamespaceBinding (Namespace entity gone)
|
||||
// and ValidateGalaxyTagFullName (blob-as-identity gone; Tags are raw-only, not equipment-bound)
|
||||
// are deleted here. TODO(v3 WP4): add the new v3 rules — raw-name charset (no '/', no
|
||||
// lead/trail whitespace) at every level, historized effective-tagname ≤ 255 chars, and UNS
|
||||
// effective-leaf uniqueness across UnsTagReference/VirtualTag/ScriptedAlarm.
|
||||
return errors;
|
||||
}
|
||||
|
||||
private static void ValidateGalaxyTagFullName(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
var typeByDriver = draft.DriverInstances
|
||||
.ToDictionary(d => d.DriverInstanceId, d => d.DriverType, StringComparer.Ordinal);
|
||||
foreach (var t in draft.Tags)
|
||||
{
|
||||
if (t.EquipmentId is null) continue;
|
||||
if (!typeByDriver.TryGetValue(t.DriverInstanceId, out var dtype) || dtype != "GalaxyMxGateway")
|
||||
continue;
|
||||
// The Galaxy rule wants the EXPLICIT reference, not the raw-blob fallback — so it reads
|
||||
// TagConfigIntent.ExplicitFullName (the "FullName" property only when present-and-string,
|
||||
// else null), preserving the historical null-on-absent semantics by construction (R2-11).
|
||||
if (string.IsNullOrWhiteSpace(TagConfigIntent.Parse(t.TagConfig).ExplicitFullName))
|
||||
errors.Add(new("GalaxyTagMissingReference",
|
||||
$"Galaxy tag '{t.TagId}' on equipment '{t.EquipmentId}' is missing a Galaxy reference (TagConfig.FullName)",
|
||||
t.TagId));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateNoEquipmentSignalNameCollision(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
// Materialiser NodeId key: "{EquipmentId}[/{FolderPath}]/{Name}". Tag (EquipmentId != null) and
|
||||
// VirtualTag share this space with no DB cross-table uniqueness, so the same key from both collides.
|
||||
static string Key(string eq, string? folder, string name) =>
|
||||
string.IsNullOrWhiteSpace(folder) ? $"{eq}/{name}" : $"{eq}/{folder}/{name}";
|
||||
// v3: Tags are raw-only (no EquipmentId), so the old Tag-vs-VirtualTag NodeId collision does
|
||||
// not apply here. Until WP4 wires the full UNS effective-leaf rule (across UnsTagReference,
|
||||
// VirtualTag, and ScriptedAlarm), enforce the still-valid invariant that a VirtualTag Name is
|
||||
// unique within its owning equipment — the OPC UA NodeId key is "{EquipmentId}/{Name}".
|
||||
var signals = draft.VirtualTags.Select(v => (Eq: v.EquipmentId, v.Name));
|
||||
|
||||
var signals = draft.Tags
|
||||
.Where(t => t.EquipmentId is not null)
|
||||
.Select(t => (Key: Key(t.EquipmentId!, t.FolderPath, t.Name), Eq: t.EquipmentId!, t.Name))
|
||||
// VirtualTag has no FolderPath column today — null is correct here; update if it ever gains one.
|
||||
.Concat(draft.VirtualTags
|
||||
.Select(v => (Key: Key(v.EquipmentId, null, v.Name), Eq: v.EquipmentId, v.Name)));
|
||||
|
||||
foreach (var g in signals.GroupBy(s => s.Key, StringComparer.Ordinal))
|
||||
foreach (var g in signals.GroupBy(s => $"{s.Eq}/{s.Name}", StringComparer.Ordinal))
|
||||
{
|
||||
var items = g.ToList();
|
||||
if (items.Count <= 1) continue;
|
||||
var f = items[0];
|
||||
errors.Add(new("EquipmentSignalNameCollision",
|
||||
$"{items.Count} signals collide on OPC UA NodeId '{g.Key}' (equipment '{f.Eq}', name '{f.Name}'); " +
|
||||
"a Name must be unique across Tag and VirtualTag within an equipment+folder",
|
||||
"a VirtualTag Name must be unique within an equipment",
|
||||
f.Eq));
|
||||
}
|
||||
}
|
||||
@@ -149,27 +127,6 @@ public static class DraftValidator
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateSameClusterNamespaceBinding(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
var nsById = draft.Namespaces.ToDictionary(n => n.NamespaceId);
|
||||
|
||||
foreach (var di in draft.DriverInstances)
|
||||
{
|
||||
if (!nsById.TryGetValue(di.NamespaceId, out var ns))
|
||||
{
|
||||
errors.Add(new("NamespaceUnresolved",
|
||||
$"DriverInstance '{di.DriverInstanceId}' references unknown NamespaceId '{di.NamespaceId}'",
|
||||
di.DriverInstanceId));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ns.ClusterId != di.ClusterId)
|
||||
errors.Add(new("BadCrossClusterNamespaceBinding",
|
||||
$"DriverInstance '{di.DriverInstanceId}' is in cluster '{di.ClusterId}' but references namespace in cluster '{ns.ClusterId}'",
|
||||
di.DriverInstanceId));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateReservationPreflight(DraftSnapshot draft, List<ValidationError> errors)
|
||||
{
|
||||
var activeByKindValue = draft.ActiveReservations
|
||||
|
||||
Reference in New Issue
Block a user