8c6d6aff6a
Moves the database-independent logic of the SQL Server historian backend into a new shared library for the upcoming QuestDB backend (#666): status codes and bits, value/column mapping, StoredSample + quality clamp, the AtTime and aggregate evaluators, BucketPlan, LiveWritePreparer and HistoryUpdatePlanner (both with a resolutionTicks parameter, default 1), tag-name rules, the bootstrap failure cache, health counters, alarm event ids, the event continuation and severity clamp. The SQL Server backend now calls it; its T-SQL, TVP builders, connection, bootstrapper and catalog stay. Tests: StatusCodeOracleTests, ValueWriterPrepareTests, the column half of SampleRowMapperTests and ProcessedReadTests.Every_enum_member_is_either_served_or_refused move to the new Sql.Common.Tests under the same names. SqlServer integration tests get mechanical renames only. Test-name set across the three projects is identical to the baseline (160 tests). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
62 lines
2.3 KiB
C#
62 lines
2.3 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common;
|
|
|
|
/// <summary>
|
|
/// The deterministic alarm-event id and numeric severity the SQL historian backends store, so the store-and-forward
|
|
/// drain's at-least-once redelivery (and both pair nodes writing the same event) converge on one row.
|
|
/// </summary>
|
|
public static class AlarmEventIds
|
|
{
|
|
/// <summary>
|
|
/// Lowercase-hex SHA-256 over length-prefixed UTF-8 fields (little-endian int32 byte length, -1 for null):
|
|
/// EquipmentPath, AlarmId, EventKind, TimestampUtc.Ticks (decimal), User, Comment, Message. Length-prefixing keeps
|
|
/// field boundaries unambiguous (<c>"a|b"+"c"</c> ≠ <c>"a"+"b|c"</c>).
|
|
/// </summary>
|
|
/// <param name="e">The event.</param>
|
|
/// <returns>64 hex characters.</returns>
|
|
public static string ComputeEventId(AlarmHistorianEvent e)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
Field(ms, e.EquipmentPath);
|
|
Field(ms, e.AlarmId);
|
|
Field(ms, e.EventKind);
|
|
Field(ms, e.TimestampUtc.Ticks.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
|
Field(ms, e.User);
|
|
Field(ms, e.Comment);
|
|
Field(ms, e.Message);
|
|
return Convert.ToHexStringLower(SHA256.HashData(ms.ToArray()));
|
|
}
|
|
|
|
/// <summary>The numeric Part 9 priority stored for a severity (same values the gateway path sends).</summary>
|
|
/// <param name="s">Alarm severity.</param>
|
|
/// <returns>200 / 500 / 700 / 900; 500 for anything else.</returns>
|
|
public static int ToNumericSeverity(AlarmSeverity s) => s switch
|
|
{
|
|
AlarmSeverity.Low => 200,
|
|
AlarmSeverity.Medium => 500,
|
|
AlarmSeverity.High => 700,
|
|
AlarmSeverity.Critical => 900,
|
|
_ => 500,
|
|
};
|
|
|
|
private static void Field(MemoryStream ms, string? value)
|
|
{
|
|
Span<byte> len = stackalloc byte[4];
|
|
if (value is null)
|
|
{
|
|
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(len, -1);
|
|
ms.Write(len);
|
|
return;
|
|
}
|
|
|
|
var bytes = Encoding.UTF8.GetBytes(value);
|
|
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(len, bytes.Length);
|
|
ms.Write(len);
|
|
ms.Write(bytes);
|
|
}
|
|
}
|