R1.7: server-side event filters — ReadEventsAsync(HistorianEventFilter), live-honored
Roadmap M1 R1.7. Filters are set on the native EventQuery object via AddEventFilter(property, HistorianComparisionType, value) — NOT EventQueryArgs (time/count/order only). Found via a new harness --dump-type-members command. Captured the native filtered StartEventQuery pRequestBuff (Capture-EventFilter.ps1 + harness --event-filter knob) and diffed Equal(0) vs Contains(12) to isolate the operator field. Filter block (decoded byte-for-byte): ushort 0 + uint filterCount + uint condCount + uint nameLen + name(UTF-16) + uint 1 + ushort op + uint 1 + value(0x09-LEN-0x00 compact-ASCII) + byte 0 The filter is REAL, not inert (unlike the analog-summary knobs): a non-matching predicate returns 0 events; Type=Equal=User.Write returns only User.Write events. Verified live via both the native harness and the SDK. - HistorianClient.ReadEventsAsync(start, end, HistorianEventFilter, ct) overload - HistorianEventFilter + HistorianEventComparison (18 ops, ordinals = native) - Filter encoding in HistorianEventQueryProtocol (empty-filter path unchanged) - Golden-byte tests (block match, op field, empty-filter regression) + gated live test Single string-valued predicate only; multi-filter (OR) / multi-condition (AND via AddEventFilterCondition) framing is partially captured and not shipped. 216 unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -84,6 +84,31 @@ internal static class Program
|
||||
return 0;
|
||||
}
|
||||
|
||||
string? dumpTypeName = GetArg(args, "--dump-type-members");
|
||||
if (dumpTypeName is not null)
|
||||
{
|
||||
Type dumpType = GetType(assembly, dumpTypeName);
|
||||
if (dumpType.IsEnum)
|
||||
{
|
||||
var values = Enum.GetValues(dumpType).Cast<object>()
|
||||
.Select(v => $"{v} = {Convert.ToInt64(v)}").OrderBy(s => s).ToArray();
|
||||
Console.WriteLine(Serialize(new { Type = dumpType.FullName, EnumValues = values }));
|
||||
return 0;
|
||||
}
|
||||
BindingFlags df = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
Console.WriteLine(Serialize(new
|
||||
{
|
||||
Type = dumpType.FullName,
|
||||
Properties = dumpType.GetProperties(df).Select(p => $"{p.PropertyType.Name} {p.Name}").OrderBy(s => s).ToArray(),
|
||||
Fields = dumpType.GetFields(df).Select(f => $"{f.FieldType.Name} {f.Name}").OrderBy(s => s).ToArray(),
|
||||
Methods = dumpType.GetMethods(df)
|
||||
.Where(m => !m.IsSpecialName)
|
||||
.Select(m => $"{m.ReturnType.Name} {m.Name}({string.Join(", ", m.GetParameters().Select(p => p.ParameterType.Name))})")
|
||||
.OrderBy(s => s).ToArray(),
|
||||
}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
Type accessType = GetType(assembly, "ArchestrA.HistorianAccess");
|
||||
Type connectionArgsType = GetType(assembly, "ArchestrA.HistorianConnectionArgs");
|
||||
Type connectionStatusType = GetType(assembly, "ArchestrA.HistorianConnectionStatus");
|
||||
@@ -224,6 +249,41 @@ internal static class Program
|
||||
SetProperty(queryArgs, "EventOrder", Enum.Parse(eventOrderType, "Ascending"));
|
||||
snapshots["EventQueryArgsBeforeStart"] = SnapshotObject(queryArgs);
|
||||
|
||||
// R1.7 event-filter capture: --event-filter "Property:Op:Value" (repeatable via ';').
|
||||
// Calls EventQuery.AddEventFilter(name, HistorianComparisionType, value, out err) so the
|
||||
// filter predicate rides StartEventQuery's request buffer for instrument-wcf capture.
|
||||
string? eventFilterSpec = GetArg(args, "--event-filter");
|
||||
if (!string.IsNullOrWhiteSpace(eventFilterSpec))
|
||||
{
|
||||
Type comparisonType = GetType(assembly, "ArchestrA.HistorianComparisionType");
|
||||
MethodInfo addFilterMethod = queryType.GetMethod("AddEventFilter",
|
||||
new[] { typeof(string), comparisonType, typeof(object), errorType.MakeByRefType() })
|
||||
?? throw new MissingMethodException("EventQuery.AddEventFilter");
|
||||
foreach (string clause in eventFilterSpec!.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
string[] parts = clause.Split(new[] { ':' }, 3);
|
||||
if (parts.Length < 3)
|
||||
{
|
||||
throw new ArgumentException($"--event-filter clause '{clause}' must be Property:Op:Value.");
|
||||
}
|
||||
|
||||
object filterError = Activator.CreateInstance(errorType)!;
|
||||
object?[] addFilterArgs = [parts[0], Enum.Parse(comparisonType, parts[1], ignoreCase: true), parts[2], filterError];
|
||||
object addFilterResult = addFilterMethod.Invoke(query, addFilterArgs)!;
|
||||
filterError = addFilterArgs[3]!;
|
||||
rows.Add(new
|
||||
{
|
||||
Kind = "AddEventFilter",
|
||||
Property = parts[0],
|
||||
Op = parts[1],
|
||||
Value = parts[2],
|
||||
FilterId = addFilterResult,
|
||||
ErrorDescription = GetPropertyText(filterError, "ErrorDescription"),
|
||||
});
|
||||
}
|
||||
snapshots["EventQueryAfterAddFilter"] = SnapshotObject(query);
|
||||
}
|
||||
|
||||
startError = Activator.CreateInstance(errorType)!;
|
||||
MethodInfo startMethod = queryType.GetMethod("StartQuery", new[] { eventQueryArgsType, errorType.MakeByRefType() })
|
||||
?? throw new MissingMethodException("EventQuery.StartQuery");
|
||||
|
||||
Reference in New Issue
Block a user