474 lines
20 KiB
C#
474 lines
20 KiB
C#
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());
|
|
|
|
INSERT dbo.ConfigAuditLog (Principal, EventType, NodeId, GenerationId, DetailsJson)
|
|
VALUES (@Caller, 'NodeApplied', @NodeId, @GenerationId,
|
|
CONCAT('{""status"":""', @Status, '""}'));
|
|
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;
|
|
|
|
BEGIN TRANSACTION;
|
|
|
|
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);
|
|
ROLLBACK;
|
|
RETURN;
|
|
END
|
|
|
|
EXEC dbo.sp_ValidateDraft @DraftGenerationId = @DraftGenerationId;
|
|
|
|
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 found (was it already published?)', 16, 1, @DraftGenerationId, @ClusterId);
|
|
ROLLBACK;
|
|
RETURN;
|
|
END
|
|
|
|
INSERT dbo.ConfigAuditLog (Principal, EventType, ClusterId, GenerationId)
|
|
VALUES (SUSER_SNAME(), 'Published', @ClusterId, @DraftGenerationId);
|
|
|
|
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;
|
|
|
|
INSERT dbo.ConfigAuditLog (Principal, EventType, ClusterId, GenerationId, DetailsJson)
|
|
VALUES (SUSER_SNAME(), 'RolledBack', @ClusterId, @NewGenId,
|
|
CONCAT('{""rolledBackTo"":', @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
|
|
|
|
INSERT dbo.ConfigAuditLog (Principal, EventType, DetailsJson)
|
|
VALUES (SUSER_SNAME(), 'ExternalIdReleased',
|
|
CONCAT('{""kind"":""', @Kind, '"",""value"":""', @Value, '""}'));
|
|
END
|
|
";
|
|
}
|
|
}
|