feat(grpc): …OnSession seams on HistorianGrpcTagClient (browse + metadata)
DRY split mirroring HistorianGrpcReadOrchestrator.RunRawQueryOnSession: browse + GetTagInfos(metadata) gain externally-supplied connection+session seams; per-call wrappers delegate. Behaviour-preserving (pending.md A1 broadening). Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
@@ -39,6 +39,27 @@ internal static class HistorianGrpcTagClient
|
|||||||
private static HistorianTagMetadata? GetTagMetadata(HistorianClientOptions options, string tag, CancellationToken cancellationToken)
|
private static HistorianTagMetadata? GetTagMetadata(HistorianClientOptions options, string tag, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
byte[] tagInfos = GetTagInfosRaw(options, [tag], cancellationToken);
|
byte[] tagInfos = GetTagInfosRaw(options, [tag], cancellationToken);
|
||||||
|
return ParseTagMetadata(tagInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): resolve tag metadata against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + session — i.e. NO Create()/handshake here. The per-call
|
||||||
|
// GetTagMetadata and this seam share the parse tail (ParseTagMetadata) so neither duplicates the
|
||||||
|
// decode logic (DRY).
|
||||||
|
internal static HistorianTagMetadata? GetTagMetadataOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
string tag,
|
||||||
|
HistorianClientOptions options,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
byte[] tagInfos = GetTagInfosRawOnSession(connection, session, [tag], options, cancellationToken);
|
||||||
|
return ParseTagMetadata(tagInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared parse tail for both the per-call GetTagMetadata and the reuse-path GetTagMetadataOnSession.
|
||||||
|
private static HistorianTagMetadata? ParseTagMetadata(byte[] tagInfos)
|
||||||
|
{
|
||||||
if (tagInfos.Length < 4)
|
if (tagInfos.Length < 4)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -69,7 +90,19 @@ internal static class HistorianGrpcTagClient
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
||||||
|
return GetTagInfosRawOnSession(connection, session, tags, options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): issue GetTagInfosFromName against an EXTERNALLY-supplied,
|
||||||
|
// already-authenticated connection + session — i.e. NO Create()/handshake here. GetTagInfosRaw
|
||||||
|
// delegates to this so the per-call path and the reuse path share one query implementation (DRY).
|
||||||
|
internal static byte[] GetTagInfosRawOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
IReadOnlyList<string> tags,
|
||||||
|
HistorianClientOptions options,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
|
var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
|
||||||
byte[] requestBuffer = BuildTagNamesBuffer(tags);
|
byte[] requestBuffer = BuildTagNamesBuffer(tags);
|
||||||
GrpcRetrieval.GetTagInfosFromNameResponse response = retrievalClient.GetTagInfosFromName(
|
GrpcRetrieval.GetTagInfosFromNameResponse response = retrievalClient.GetTagInfosFromName(
|
||||||
@@ -112,6 +145,8 @@ internal static class HistorianGrpcTagClient
|
|||||||
return Task.Run(() => GetTagExtendedProperties(options, tag, cancellationToken), cancellationToken);
|
return Task.Run(() => GetTagExtendedProperties(options, tag, cancellationToken), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No …OnSession seam: extended-properties browse stays per-call (not amortized through the session
|
||||||
|
// pool — out of A1-broadening scope). Add a seam here only if the pool ever needs to route it.
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Issues a single page-0 <c>GetTagExtendedPropertiesFromName</c> call and returns the raw native
|
/// Issues a single page-0 <c>GetTagExtendedPropertiesFromName</c> call and returns the raw native
|
||||||
/// <c>btTeps</c> response buffer (empty when the server reports no rows / non-success). Internal so
|
/// <c>btTeps</c> response buffer (empty when the server reports no rows / non-success). Internal so
|
||||||
@@ -222,6 +257,20 @@ internal static class HistorianGrpcTagClient
|
|||||||
{
|
{
|
||||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
|
||||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
|
||||||
|
return BrowseTagNamesOnSession(connection, session, filter, options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spike/Phase-1 seam (pending.md A1): drive StartTagQuery → paged QueryTag → EndTagQuery against an
|
||||||
|
// EXTERNALLY-supplied, already-authenticated connection + session — i.e. NO Create()/handshake here.
|
||||||
|
// BrowseTagNames delegates to this so the per-call path and the reuse path share one browse
|
||||||
|
// implementation (DRY).
|
||||||
|
internal static List<string> BrowseTagNamesOnSession(
|
||||||
|
HistorianGrpcConnection connection,
|
||||||
|
HistorianGrpcHandshake.Session session,
|
||||||
|
string filter,
|
||||||
|
HistorianClientOptions options,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
|
var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
|
||||||
DateTime Deadline() => DateTime.UtcNow.Add(options.RequestTimeout);
|
DateTime Deadline() => DateTime.UtcNow.Add(options.RequestTimeout);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using AVEVA.Historian.Client.Grpc;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AVEVA.Historian.Client.Tests;
|
||||||
|
|
||||||
|
public class TagClientOnSessionSeamTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("BrowseTagNamesOnSession")]
|
||||||
|
[InlineData("GetTagInfosRawOnSession")]
|
||||||
|
[InlineData("GetTagMetadataOnSession")]
|
||||||
|
public void TagClient_ExposesOnSessionSeam(string name)
|
||||||
|
{
|
||||||
|
MethodInfo? m = typeof(HistorianGrpcTagClient).GetMethod(
|
||||||
|
name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public);
|
||||||
|
Assert.NotNull(m);
|
||||||
|
ParameterInfo[] ps = m!.GetParameters();
|
||||||
|
Assert.Equal("HistorianGrpcConnection", ps[0].ParameterType.Name);
|
||||||
|
Assert.Equal("Session", ps[1].ParameterType.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user