Two complementary pieces that together unblock the last Phase 7 exit-gate deferrals. ## #239 — Thread virtual + scripted-alarm IReadable through to DriverNodeManager OtOpcUaServer gains virtualReadable + scriptedAlarmReadable ctor params; shared across every DriverNodeManager it materializes so reads from a virtual-tag node in any driver's subtree route to the same engine instance. Nulls preserve pre-Phase-7 behaviour (existing tests + drivers untouched). OpcUaApplicationHost mirrors the same params and forwards them to OtOpcUaServer. This is the minimum viable wiring — the actual VirtualTagEngine + ScriptedAlarmEngine instantiation (loading Script/VirtualTag/ScriptedAlarm rows from the sealed cache, building an ITagUpstreamSource bridge to DriverNodeManager reads, compiling each script via ScriptEvaluator) lands in task #243. Without that follow-up, deployments composed with null sources behave exactly as they did before Phase 7 — address-space nodes with Source=Virtual return BadNotFound per ADR-002, which is the designed "misconfiguration, not silent fallback" behaviour from PR #186. ## #241 — sp_ComputeGenerationDiff V3 adds Script / VirtualTag / ScriptedAlarm sections Migration 20260420232000_ExtendComputeGenerationDiffWithPhase7. Same CHECKSUM-based Modified detection the existing sections use. Logical ids: ScriptId / VirtualTagId / ScriptedAlarmId. Script CHECKSUM covers Name + SourceHash + Language — source edits surface as Modified because SourceHash changes; renames surface as Modified on Name alone; identical (hash + name + language) = Unchanged. VirtualTag + ScriptedAlarm CHECKSUMs cover their content columns. ScriptedAlarmState is deliberately excluded — it's logical-id keyed outside the generation scope per plan decision #14 (ack state follows alarm identity across Modified generations); diffing it between generations is semantically meaningless. Down() restores V2 (the NodeAcl-extended proc from migration 20260420000001). ## No new test count — both pieces are proven by existing suites The NodeSourceKind dispatch kernel is already covered by DriverNodeManagerSourceDispatchTests (PR #186). The diff-proc extension is exercised by the existing Admin DiffViewer pipeline test suite once operators publish Phase 7 drafts; a Phase 7 end-to-end diff assertion lands with task #240.
233 lines
14 KiB
C#
233 lines
14 KiB
C#
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
|
|
";
|
|
}
|
|
}
|
|
}
|