6ea3ba44da
IHistorianDataSource.SupportedAggregates (default: every HistoryAggregateType) is the one authority for both ServerCapabilities/AggregateFunctions (i=2997) and HistoryServerCapabilities/AggregateFunctions (i=11201) and for the up-front BadAggregateNotSupported in HistoryRead-Processed. Gateway declares AggregateModeMapper.Served (4), SqlServer/QuestDB AggregateFamilies.Served (17), Null none. Only the folder references are trimmed; the SDK's calculator factories stay so live AggregateFilter subscriptions keep working. Co-Authored-By: Claude <noreply@anthropic.com>
53 lines
2.8 KiB
C#
53 lines
2.8 KiB
C#
using System.Collections.Frozen;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common;
|
|
|
|
/// <summary>The query shape that serves an aggregate. One query per family.</summary>
|
|
public enum AggregateFamily
|
|
{
|
|
/// <summary>Average / Minimum / Maximum / Count / Range: one GROUP BY.</summary>
|
|
Simple,
|
|
|
|
/// <summary>Start / End / Delta: first + last usable sample per bucket.</summary>
|
|
StartEnd,
|
|
|
|
/// <summary>MinimumActualTime / MaximumActualTime: extreme usable sample per bucket, ties ⇒ earliest.</summary>
|
|
ActualTime,
|
|
|
|
/// <summary>
|
|
/// TimeAverage / TimeAverage2 / Total / Total2 / Interpolative, and the duration-based PercentGood / PercentBad
|
|
/// (#680): Part 13 time-weighted, with bounding values.
|
|
/// The backend streams the window's samples plus the bounding rows and <see cref="TimeWeightedAggregator"/>
|
|
/// evaluates them (identically for every backend).
|
|
/// </summary>
|
|
TimeWeighted,
|
|
}
|
|
|
|
/// <summary>Which aggregates the SQL historian backends serve, and by which family.</summary>
|
|
public static class AggregateFamilies
|
|
{
|
|
/// <summary>The family that serves <paramref name="aggregate"/>, or null when the backends do not.</summary>
|
|
/// <param name="aggregate">Requested aggregate.</param>
|
|
/// <returns>The family, or null for an unsupported aggregate.</returns>
|
|
public static AggregateFamily? FamilyOf(HistoryAggregateType aggregate) => aggregate switch
|
|
{
|
|
HistoryAggregateType.Average or HistoryAggregateType.Minimum or HistoryAggregateType.Maximum
|
|
or HistoryAggregateType.Count or HistoryAggregateType.Range => AggregateFamily.Simple,
|
|
HistoryAggregateType.Start or HistoryAggregateType.End or HistoryAggregateType.Delta => AggregateFamily.StartEnd,
|
|
HistoryAggregateType.MinimumActualTime or HistoryAggregateType.MaximumActualTime => AggregateFamily.ActualTime,
|
|
HistoryAggregateType.TimeAverage or HistoryAggregateType.TimeAverage2 or HistoryAggregateType.Total
|
|
or HistoryAggregateType.Total2 or HistoryAggregateType.Interpolative
|
|
or HistoryAggregateType.PercentGood or HistoryAggregateType.PercentBad => AggregateFamily.TimeWeighted,
|
|
_ => null,
|
|
};
|
|
|
|
/// <summary>
|
|
/// The seventeen aggregates the SQL backends serve — every member <see cref="FamilyOf"/> assigns a family, derived
|
|
/// from it so the set and the dispatch can never disagree. Both <c>SqlServerHistorianDataSource</c> and
|
|
/// <c>QuestDbHistorianDataSource</c> return it as <see cref="IHistorianDataSource.SupportedAggregates"/> (#682).
|
|
/// </summary>
|
|
public static readonly IReadOnlySet<HistoryAggregateType> Served =
|
|
Enum.GetValues<HistoryAggregateType>().Where(a => FamilyOf(a) is not null).ToFrozenSet();
|
|
}
|