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.