diff --git a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs index 9a8d34b..28a82b2 100644 --- a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs +++ b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs @@ -114,6 +114,24 @@ public sealed class GalaxyRepository(GalaxyRepositoryOptions options) : IGalaxyR return rows; } + /// + public async Task> GetAlarmAttributesAsync(CancellationToken ct = default) + { + List rows = new(); + + using SqlConnection conn = new(options.ConnectionString); + await conn.OpenAsync(ct).ConfigureAwait(false); + + using SqlCommand cmd = new(AlarmAttributesSql, conn) { CommandTimeout = options.CommandTimeoutSeconds }; + using SqlDataReader reader = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false); + + while (await reader.ReadAsync(ct).ConfigureAwait(false)) + { + rows.Add(MapAlarmRow(reader.GetString(0), reader.GetString(1), reader.GetString(2))); + } + return rows; + } + /// /// Maps the SQL columns projected by AlarmAttributesSql onto a /// . @@ -284,5 +302,55 @@ SELECT FROM ranked r LEFT JOIN data_type dt ON dt.mx_data_type = r.mx_data_type WHERE r.rn = 1 +ORDER BY r.tag_name, r.attribute_name"; + + // Returns one row per alarm-bearing attribute across all deployed objects. The three + // projected columns (full_tag_reference, source_object_reference, area_name) are mapped + // by MapAlarmRow. Only attributes whose owning object has an AlarmExtension primitive + // instance matching the attribute name are returned, which is why the EXISTS correlated + // sub-query against deployed_package_chain is needed rather than relying solely on + // dynamic_attribute.mx_attribute_category. + private const string AlarmAttributesSql = @" +;WITH deployed_package_chain AS ( + SELECT g.gobject_id, p.package_id, p.derived_from_package_id, 0 AS depth + FROM gobject g + INNER JOIN package p ON p.package_id = g.deployed_package_id + WHERE g.is_template = 0 AND g.deployed_package_id <> 0 + UNION ALL + SELECT dpc.gobject_id, p.package_id, p.derived_from_package_id, dpc.depth + 1 + FROM deployed_package_chain dpc + INNER JOIN package p ON p.package_id = dpc.derived_from_package_id + WHERE dpc.derived_from_package_id <> 0 AND dpc.depth < 10 +), +candidate AS ( + SELECT dpc.gobject_id, g.tag_name, da.attribute_name, dpc.depth + FROM deployed_package_chain dpc + INNER JOIN dynamic_attribute da ON da.package_id = dpc.package_id + INNER JOIN gobject g ON g.gobject_id = dpc.gobject_id + INNER JOIN template_definition td ON td.template_definition_id = g.template_definition_id + WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26) + AND da.attribute_name NOT LIKE '[_]%' + AND da.attribute_name NOT LIKE '%.Description' + AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24) +), +ranked AS ( + SELECT c.*, ROW_NUMBER() OVER ( + PARTITION BY c.gobject_id, c.attribute_name ORDER BY c.depth) AS rn + FROM candidate c +) +SELECT + r.tag_name + '.' + r.attribute_name AS full_tag_reference, + r.tag_name AS source_object_reference, + ISNULL(area.tag_name, '') AS area_name +FROM ranked r +INNER JOIN gobject g ON g.gobject_id = r.gobject_id +LEFT JOIN gobject area ON area.gobject_id = g.area_gobject_id +WHERE r.rn = 1 + AND EXISTS ( + SELECT 1 FROM deployed_package_chain dpc2 + INNER JOIN primitive_instance pi ON pi.package_id = dpc2.package_id AND pi.primitive_name = r.attribute_name + INNER JOIN primitive_definition pd ON pd.primitive_definition_id = pi.primitive_definition_id AND pd.primitive_name = 'AlarmExtension' + WHERE dpc2.gobject_id = r.gobject_id + ) ORDER BY r.tag_name, r.attribute_name"; } diff --git a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/IGalaxyRepository.cs b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/IGalaxyRepository.cs index 8924e50..3eef56d 100644 --- a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/IGalaxyRepository.cs +++ b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/IGalaxyRepository.cs @@ -23,4 +23,9 @@ public interface IGalaxyRepository /// Retrieves all attributes for Galaxy objects from the repository. /// Token to cancel the asynchronous operation. Task> GetAttributesAsync(CancellationToken ct = default); + + /// Returns the alarm-bearing attributes across deployed Galaxy objects. + /// Cancellation token. + /// The alarm-bearing attribute rows. + Task> GetAlarmAttributesAsync(CancellationToken ct = default); } diff --git a/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/Fakes.cs b/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/Fakes.cs index 799443d..93bca44 100644 --- a/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/Fakes.cs +++ b/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/Fakes.cs @@ -12,14 +12,17 @@ internal sealed class FakeGalaxyRepository : IGalaxyRepository { private readonly IReadOnlyList _hierarchy; private readonly IReadOnlyList _attributes; + private readonly IReadOnlyList _alarmAttributes; public FakeGalaxyRepository( IReadOnlyList hierarchy, IReadOnlyList attributes, - DateTime? deployTime) + DateTime? deployTime, + IReadOnlyList? alarmAttributes = null) { _hierarchy = hierarchy; _attributes = attributes; + _alarmAttributes = alarmAttributes ?? Array.Empty(); DeployTime = deployTime; } @@ -33,6 +36,8 @@ internal sealed class FakeGalaxyRepository : IGalaxyRepository public int AttributeReadCount { get; private set; } + public int AlarmAttributeReadCount { get; private set; } + public Task TestConnectionAsync(CancellationToken ct = default) => ThrowOnQuery is null ? Task.FromResult(true) : throw ThrowOnQuery; @@ -67,6 +72,17 @@ internal sealed class FakeGalaxyRepository : IGalaxyRepository AttributeReadCount++; return Task.FromResult(_attributes.ToList()); } + + public Task> GetAlarmAttributesAsync(CancellationToken ct = default) + { + if (ThrowOnQuery is not null) + { + throw ThrowOnQuery; + } + + AlarmAttributeReadCount++; + return Task.FromResult(_alarmAttributes.ToList()); + } } /// Records published deploy events so tests can assert publication.