feat(galaxyrepo): SQL browse provider (hierarchy + attributes)
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
namespace ZB.MOM.WW.GalaxyRepository;
|
||||||
|
|
||||||
|
/// <summary>One row from <see cref="GalaxyRepository.GetAttributesAsync"/>.</summary>
|
||||||
|
public sealed class GalaxyAttributeRow
|
||||||
|
{
|
||||||
|
/// <summary>Gets the Galaxy object identifier.</summary>
|
||||||
|
public int GobjectId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the tag name.</summary>
|
||||||
|
public string TagName { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets the attribute name.</summary>
|
||||||
|
public string AttributeName { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets the full tag reference.</summary>
|
||||||
|
public string FullTagReference { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets the MXAccess data type code.</summary>
|
||||||
|
public int MxDataType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the data type name.</summary>
|
||||||
|
public string? DataTypeName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether this is an array.</summary>
|
||||||
|
public bool IsArray { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the array dimension, if applicable.</summary>
|
||||||
|
public int? ArrayDimension { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the MXAccess attribute category code.</summary>
|
||||||
|
public int MxAttributeCategory { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the security classification code.</summary>
|
||||||
|
public int SecurityClassification { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether this is historized.</summary>
|
||||||
|
public bool IsHistorized { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether this is an alarm.</summary>
|
||||||
|
public bool IsAlarm { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
namespace ZB.MOM.WW.GalaxyRepository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One row from <see cref="GalaxyRepository.GetHierarchyAsync"/>: a deployed Galaxy
|
||||||
|
/// <c>gobject</c> with its hierarchy parent and template-derivation chain.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GalaxyHierarchyRow
|
||||||
|
{
|
||||||
|
/// <summary>Gets the Galaxy object identifier.</summary>
|
||||||
|
public int GobjectId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the tag name.</summary>
|
||||||
|
public string TagName { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets the contained name.</summary>
|
||||||
|
public string ContainedName { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets the browse name.</summary>
|
||||||
|
public string BrowseName { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Gets the parent Galaxy object identifier.</summary>
|
||||||
|
public int ParentGobjectId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether this is an area.</summary>
|
||||||
|
public bool IsArea { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the category identifier.</summary>
|
||||||
|
public int CategoryId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the Galaxy object identifier of the host.</summary>
|
||||||
|
public int HostedByGobjectId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Gets the template derivation chain.</summary>
|
||||||
|
public IReadOnlyList<string> TemplateChain { get; init; } = Array.Empty<string>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.GalaxyRepository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SQL access to the AVEVA System Platform Galaxy Repository database.
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="HierarchySql" /> is the query originally ported from the OtOpcUa
|
||||||
|
/// project. <see cref="AttributesSql" /> has diverged: it additionally enumerates the
|
||||||
|
/// built-in attributes contributed by each object's primitives (from
|
||||||
|
/// <c>attribute_definition</c> via <c>primitive_instance</c>), so engine/platform objects
|
||||||
|
/// and extension sub-attributes (e.g. <c>TestAlarm001.Acked</c>) are surfaced. The
|
||||||
|
/// OtOpcUa query is not kept in sync.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GalaxyRepository(GalaxyRepositoryOptions options) : IGalaxyRepository
|
||||||
|
{
|
||||||
|
/// <summary>Tests the connection to the Galaxy Repository database.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
public async Task<bool> TestConnectionAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using SqlConnection conn = new(options.ConnectionString);
|
||||||
|
await conn.OpenAsync(ct).ConfigureAwait(false);
|
||||||
|
using SqlCommand cmd = new("SELECT 1", conn) { CommandTimeout = options.CommandTimeoutSeconds };
|
||||||
|
object? result = await cmd.ExecuteScalarAsync(ct).ConfigureAwait(false);
|
||||||
|
return result is int i && i == 1;
|
||||||
|
}
|
||||||
|
catch (SqlException) { return false; }
|
||||||
|
catch (InvalidOperationException) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Retrieves the last deployment time from the Galaxy Repository.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
public async Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
using SqlConnection conn = new(options.ConnectionString);
|
||||||
|
await conn.OpenAsync(ct).ConfigureAwait(false);
|
||||||
|
using SqlCommand cmd = new("SELECT time_of_last_deploy FROM galaxy", conn)
|
||||||
|
{ CommandTimeout = options.CommandTimeoutSeconds };
|
||||||
|
object? result = await cmd.ExecuteScalarAsync(ct).ConfigureAwait(false);
|
||||||
|
return result is DateTime dt ? dt : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Retrieves the complete hierarchy of Galaxy objects from the repository.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
public async Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
List<GalaxyHierarchyRow> rows = new();
|
||||||
|
|
||||||
|
using SqlConnection conn = new(options.ConnectionString);
|
||||||
|
await conn.OpenAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
using SqlCommand cmd = new(HierarchySql, conn) { CommandTimeout = options.CommandTimeoutSeconds };
|
||||||
|
using SqlDataReader reader = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
while (await reader.ReadAsync(ct).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
string templateChainRaw = reader.IsDBNull(8) ? string.Empty : reader.GetString(8);
|
||||||
|
string[] templateChain = templateChainRaw.Length == 0
|
||||||
|
? Array.Empty<string>()
|
||||||
|
: templateChainRaw.Split(['|'], StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(s => s.Trim())
|
||||||
|
.Where(s => s.Length > 0)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
rows.Add(new GalaxyHierarchyRow
|
||||||
|
{
|
||||||
|
GobjectId = Convert.ToInt32(reader.GetValue(0)),
|
||||||
|
TagName = reader.GetString(1),
|
||||||
|
ContainedName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2),
|
||||||
|
BrowseName = reader.GetString(3),
|
||||||
|
ParentGobjectId = Convert.ToInt32(reader.GetValue(4)),
|
||||||
|
IsArea = Convert.ToInt32(reader.GetValue(5)) == 1,
|
||||||
|
CategoryId = Convert.ToInt32(reader.GetValue(6)),
|
||||||
|
HostedByGobjectId = Convert.ToInt32(reader.GetValue(7)),
|
||||||
|
TemplateChain = templateChain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Retrieves all attributes for Galaxy objects from the repository.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
public async Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
List<GalaxyAttributeRow> rows = new();
|
||||||
|
|
||||||
|
using SqlConnection conn = new(options.ConnectionString);
|
||||||
|
await conn.OpenAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
using SqlCommand cmd = new(AttributesSql, conn) { CommandTimeout = options.CommandTimeoutSeconds };
|
||||||
|
using SqlDataReader reader = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
while (await reader.ReadAsync(ct).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
rows.Add(new GalaxyAttributeRow
|
||||||
|
{
|
||||||
|
GobjectId = Convert.ToInt32(reader.GetValue(0)),
|
||||||
|
TagName = reader.GetString(1),
|
||||||
|
AttributeName = reader.GetString(2),
|
||||||
|
FullTagReference = reader.GetString(3),
|
||||||
|
MxDataType = Convert.ToInt32(reader.GetValue(4)),
|
||||||
|
DataTypeName = reader.IsDBNull(5) ? null : reader.GetString(5),
|
||||||
|
IsArray = Convert.ToInt32(reader.GetValue(6)) == 1,
|
||||||
|
ArrayDimension = reader.IsDBNull(7) ? null : Convert.ToInt32(reader.GetValue(7)),
|
||||||
|
MxAttributeCategory = Convert.ToInt32(reader.GetValue(8)),
|
||||||
|
SecurityClassification = Convert.ToInt32(reader.GetValue(9)),
|
||||||
|
IsHistorized = Convert.ToInt32(reader.GetValue(10)) == 1,
|
||||||
|
IsAlarm = Convert.ToInt32(reader.GetValue(11)) == 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Area objects (category 13) are returned even when undeployed (deployed_package_id = 0):
|
||||||
|
// they are organizational/model nodes that group deployed objects, so excluding them
|
||||||
|
// orphans every area whose containing area is not itself deployed. All non-area objects
|
||||||
|
// still require deployment. Orphans left by a missing/deleted parent area are re-rooted
|
||||||
|
// by GalaxyHierarchyIndex.Build so nothing disappears from browse.
|
||||||
|
private const string HierarchySql = @"
|
||||||
|
;WITH template_chain AS (
|
||||||
|
SELECT g.gobject_id AS instance_gobject_id, t.gobject_id AS template_gobject_id,
|
||||||
|
t.tag_name AS template_tag_name, t.derived_from_gobject_id, 0 AS depth
|
||||||
|
FROM gobject g
|
||||||
|
INNER JOIN gobject t ON t.gobject_id = g.derived_from_gobject_id
|
||||||
|
WHERE g.is_template = 0 AND g.deployed_package_id <> 0 AND g.derived_from_gobject_id <> 0
|
||||||
|
UNION ALL
|
||||||
|
SELECT tc.instance_gobject_id, t.gobject_id, t.tag_name, t.derived_from_gobject_id, tc.depth + 1
|
||||||
|
FROM template_chain tc
|
||||||
|
INNER JOIN gobject t ON t.gobject_id = tc.derived_from_gobject_id
|
||||||
|
WHERE tc.derived_from_gobject_id <> 0 AND tc.depth < 10
|
||||||
|
)
|
||||||
|
SELECT DISTINCT
|
||||||
|
g.gobject_id,
|
||||||
|
g.tag_name,
|
||||||
|
g.contained_name,
|
||||||
|
CASE WHEN g.contained_name IS NULL OR g.contained_name = ''
|
||||||
|
THEN g.tag_name
|
||||||
|
ELSE g.contained_name
|
||||||
|
END AS browse_name,
|
||||||
|
CASE WHEN g.contained_by_gobject_id = 0
|
||||||
|
THEN g.area_gobject_id
|
||||||
|
ELSE g.contained_by_gobject_id
|
||||||
|
END AS parent_gobject_id,
|
||||||
|
CASE WHEN td.category_id = 13
|
||||||
|
THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END AS is_area,
|
||||||
|
td.category_id AS category_id,
|
||||||
|
g.hosted_by_gobject_id AS hosted_by_gobject_id,
|
||||||
|
ISNULL(
|
||||||
|
STUFF((
|
||||||
|
SELECT '|' + tc.template_tag_name
|
||||||
|
FROM template_chain tc
|
||||||
|
WHERE tc.instance_gobject_id = g.gobject_id
|
||||||
|
ORDER BY tc.depth
|
||||||
|
FOR XML PATH('')
|
||||||
|
), 1, 1, ''),
|
||||||
|
''
|
||||||
|
) AS template_chain
|
||||||
|
FROM gobject g
|
||||||
|
INNER JOIN template_definition td
|
||||||
|
ON g.template_definition_id = td.template_definition_id
|
||||||
|
WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)
|
||||||
|
AND g.is_template = 0
|
||||||
|
AND (g.deployed_package_id <> 0 OR td.category_id = 13)
|
||||||
|
ORDER BY parent_gobject_id, g.tag_name";
|
||||||
|
|
||||||
|
// Unlike HierarchySql, this query has diverged from the OtOpcUa original. It returns two
|
||||||
|
// kinds of attribute: user-configured dynamic attributes (the original `dynamic_attribute`
|
||||||
|
// body, src_pri 0) and the built-in attributes every object inherits from its primitives
|
||||||
|
// (`attribute_definition` joined through `primitive_instance`, src_pri 1). Built-in
|
||||||
|
// attributes are why engine/platform objects and extension sub-attributes such as
|
||||||
|
// `TestAlarm001.Acked` show up at all. Built-in rows carry no category filter (the
|
||||||
|
// `attribute_definition` category numbering differs from `dynamic_attribute`'s — only the
|
||||||
|
// `_`-prefix and `.Description` name exclusions apply) and are never flagged
|
||||||
|
// `is_historized`/`is_alarm`: those flags describe a user attribute that anchors an
|
||||||
|
// extension, not the extension's machinery leaves.
|
||||||
|
private const string AttributesSql = @"
|
||||||
|
;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, da.mx_data_type, da.is_array,
|
||||||
|
CASE WHEN da.is_array = 1
|
||||||
|
THEN CONVERT(int, CONVERT(varbinary(2),
|
||||||
|
SUBSTRING(da.mx_value, 15, 2) + SUBSTRING(da.mx_value, 13, 2), 2))
|
||||||
|
ELSE NULL END AS array_dimension,
|
||||||
|
da.mx_attribute_category, da.security_classification, dpc.depth, 0 AS src_pri
|
||||||
|
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)
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
dpc.gobject_id, g.tag_name,
|
||||||
|
CASE WHEN pi.primitive_name IS NULL OR pi.primitive_name = ''
|
||||||
|
THEN ad.attribute_name
|
||||||
|
ELSE pi.primitive_name + '.' + ad.attribute_name END AS attribute_name,
|
||||||
|
ad.mx_data_type, ad.is_array,
|
||||||
|
CASE WHEN ad.is_array = 1
|
||||||
|
THEN CONVERT(int, CONVERT(varbinary(2),
|
||||||
|
SUBSTRING(ad.mx_value, 15, 2) + SUBSTRING(ad.mx_value, 13, 2), 2))
|
||||||
|
ELSE NULL END AS array_dimension,
|
||||||
|
ad.mx_attribute_category, ad.security_classification, dpc.depth, 1 AS src_pri
|
||||||
|
FROM deployed_package_chain dpc
|
||||||
|
INNER JOIN primitive_instance pi ON pi.package_id = dpc.package_id
|
||||||
|
INNER JOIN attribute_definition ad ON ad.primitive_definition_id = pi.primitive_definition_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 ad.attribute_name NOT LIKE '[_]%'
|
||||||
|
AND ad.attribute_name NOT LIKE '%.Description'
|
||||||
|
),
|
||||||
|
ranked AS (
|
||||||
|
SELECT c.*, ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY c.gobject_id, c.attribute_name ORDER BY c.src_pri, c.depth) AS rn
|
||||||
|
FROM candidate c
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
r.gobject_id, r.tag_name, r.attribute_name,
|
||||||
|
r.tag_name + '.' + r.attribute_name
|
||||||
|
+ CASE WHEN r.is_array = 1 THEN '[]' ELSE '' END AS full_tag_reference,
|
||||||
|
r.mx_data_type, dt.description AS data_type_name, r.is_array, r.array_dimension,
|
||||||
|
r.mx_attribute_category, r.security_classification,
|
||||||
|
CASE WHEN r.src_pri = 0 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 = 'HistoryExtension'
|
||||||
|
WHERE dpc2.gobject_id = r.gobject_id
|
||||||
|
) THEN 1 ELSE 0 END AS is_historized,
|
||||||
|
CASE WHEN r.src_pri = 0 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
|
||||||
|
) THEN 1 ELSE 0 END AS is_alarm
|
||||||
|
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";
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
namespace ZB.MOM.WW.GalaxyRepository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Connection settings for the AVEVA System Platform Galaxy Repository database.
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="SectionName"/> is a generic default; the DI extension accepts an explicit
|
||||||
|
/// configuration section path so a consumer can bind from its own section (e.g.
|
||||||
|
/// <c>MxGateway:Galaxy</c>).
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GalaxyRepositoryOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic default configuration section name. The DI extension accepts an explicit
|
||||||
|
/// section path, so a consumer may bind from a different section (e.g.
|
||||||
|
/// <c>MxGateway:Galaxy</c>).
|
||||||
|
/// </summary>
|
||||||
|
public const string SectionName = "GalaxyRepository";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default SQL Server connection string for the Galaxy Repository database.
|
||||||
|
/// Single source of truth shared with the integration-test fallback so the
|
||||||
|
/// production default and the live-test default cannot drift.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultConnectionString =
|
||||||
|
"Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;";
|
||||||
|
|
||||||
|
/// <summary>The SQL Server connection string for the Galaxy Repository database.</summary>
|
||||||
|
public string ConnectionString { get; init; } = DefaultConnectionString;
|
||||||
|
|
||||||
|
/// <summary>The timeout in seconds for SQL commands executed against the Galaxy Repository.</summary>
|
||||||
|
public int CommandTimeoutSeconds { get; init; } = 60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interval (seconds) between background refreshes of the dashboard Galaxy summary
|
||||||
|
/// cache. SQL is hit at most once per interval regardless of dashboard render rate.
|
||||||
|
/// </summary>
|
||||||
|
public int DashboardRefreshIntervalSeconds { get; init; } = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the latest successful Galaxy browse dataset is persisted to disk. When
|
||||||
|
/// enabled, the cache reloads that snapshot at startup so clients can still browse
|
||||||
|
/// last-known data while the Galaxy database is unreachable.
|
||||||
|
/// </summary>
|
||||||
|
public bool PersistSnapshot { get; init; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File path for the persisted Galaxy browse snapshot. Ignored when
|
||||||
|
/// <see cref="PersistSnapshot"/> is <see langword="false"/>. There is no built-in
|
||||||
|
/// default path — the consumer supplies a cross-platform-friendly path appropriate to
|
||||||
|
/// its host. When left empty and <see cref="PersistSnapshot"/> is enabled, the
|
||||||
|
/// snapshot store (a later task) decides where to write.
|
||||||
|
/// </summary>
|
||||||
|
public string SnapshotCachePath { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace ZB.MOM.WW.GalaxyRepository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstraction over <see cref="GalaxyRepository"/>: the read-only SQL surface over the
|
||||||
|
/// AVEVA System Platform Galaxy Repository database. Exists so consumers (and the cache
|
||||||
|
/// layer, a later task) can be unit-tested against an in-memory fake without standing up a
|
||||||
|
/// real <c>Microsoft.Data.SqlClient</c> <c>SqlConnection</c> against a bogus host/port.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGalaxyRepository
|
||||||
|
{
|
||||||
|
/// <summary>Tests the connection to the Galaxy Repository database.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
Task<bool> TestConnectionAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
|
/// <summary>Retrieves the last deployment time from the Galaxy Repository.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
|
/// <summary>Retrieves the complete hierarchy of Galaxy objects from the repository.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
|
/// <summary>Retrieves all attributes for Galaxy objects from the repository.</summary>
|
||||||
|
/// <param name="ct">Token to cancel the asynchronous operation.</param>
|
||||||
|
Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user