Established that analog/state-summary queries are reachable on 2020 WCF — they
ride the proven uint-handle StartQuery2 path, and the request serializer already
carries QueryType/SummaryType/ColumnSelectorFlags. Located every decode target in
aahClientManaged.dll:
- CAnalogSummaryValue.UnpackFromValueBuffer (0x06000394) — row decoder
- CAnalogSummaryValue/Struct fields — Min/Max/First/Last/ValueCount/TimeGood/
Integral/IntegralOfSquares (+ per-field DateTimes, LinearIntegral)
- CStateSummaryStruct — MinContained/MaxContained/TotalContained/PartialStart/
PartialEnd/StateEntryCount
- QueryColumnSelector.Select{Analog,State,NonSummary}Columns — column flags
- INSQL_QUERYTYPE / HISTORIAN_SUMMARYTYPE — the query/summary enum values
UnpackFromValueBuffer is reader-call-based (no literal offsets), so a correct
parser needs a captured real buffer. Per project discipline no guessed summary
code was added to src/. New plan doc lays out the recover-params -> live-capture
-> decode -> implement+test path. Roadmap R1.8/R1.9 marked scoped/ready.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.8 KiB
R1.8 / R1.9 — Analog-summary & State-summary queries (implementation plan)
Status (2026-06-20): scoped + de-risked. Reachable on 2020 WCF. Decode targets located in
the native dll. Ready to implement; not yet started in src/ (no guessed code shipped).
Unlike the M1 read items gated by the string-handle wall,
summary queries ride the proven uint-handle StartQuery2 path — the same call the working
raw/aggregate reads use. So they are genuinely reachable here; the only work is (a) the right
request parameters and (b) decoding the summary row buffer.
What's already in place
HistorianDataQueryRequest + SerializeFullHistoryRequest
(Wcf/HistorianDataQueryProtocol.cs) already serialize every field a summary query needs:
QueryType (INSQL_QUERYTYPE), SummaryType (HISTORIAN_SUMMARYTYPE), AggregationType,
ColumnSelectorFlags, Resolution. Normal reads send SummaryType=0 and
ColumnSelectorFlags=0x0000_8182_0007_82FF. A summary query is the same request with summary
values in those three fields, then a different row parser on the result buffer.
Decode targets recovered from current/aahClientManaged.dll
Found via methods … Summary + dnlib-method:
| Native artifact | Token | Use |
|---|---|---|
CAnalogSummaryValue.UnpackFromValueBuffer |
0x06000394 |
the analog-summary row decoder — a chain of buffer-reader calls (not literal offsets), so decode empirically against a captured buffer |
CAnalogSummaryValue.PackToVtq |
0x06000395 |
inverse (for a future write path) |
CAnalogSummaryValue setters |
0x0600038A‑92 |
wire field set: StartDateTime, Min, Max, First, Last, ValueCount, TimeGood, Integral, IntegralOfSquares |
CAnalogSummaryStruct setters |
0x06000369‑77 |
fuller field set: adds MinDateTime, MaxDateTime, FirstDateTime, LastDateTime, FirstNullDateTime, LastNullFlag, LinearIntegral |
CStateSummaryStruct setters |
0x0600039B‑A0 |
state-summary fields: MinContained, MaxContained, TotalContained, PartialStart, PartialEnd, StateEntryCount |
QueryColumnSelector.SelectAnalogSummaryColumns |
0x0600004B |
builds ColumnSelectorFlags for analog summary via CColumnNameMap.GetColumnFlag(name) per column |
QueryColumnSelector.SelectStateSummaryColumns |
0x0600004C |
same, state summary |
QueryColumnSelector.SelectNonSummaryColumns |
0x0600004D |
the default (matches the 0x…82FF flags reads already send) |
CTypeMetadata.IsAnalogSummary / IsStateSummary |
0x060001A4/A5 |
server-side type gating |
INSQL_QUERYTYPE / HISTORIAN_SUMMARYTYPE |
enums 0200013F / 02000191 |
the QueryType / SummaryType values to send |
Open questions (nail these next, in order)
- Request params. Recover the exact
QueryType+SummaryType(+ whetherColumnSelectorFlagsmust change) for analog vs state summary. Source: decompileINSQL_QUERYTYPE/HISTORIAN_SUMMARYTYPEenum members andSelect{Analog,State}SummaryColumns(theGetColumnFlagcolumn-nameldstroperands → the flag set). The standard QueryType map (Cyclic=0 … EndBound=14) is already verified; summary is expected to be aSummaryType≠0 with an existing baseQueryType, not a new mode ordinal — confirm. - Row layout. Capture a real
GetNextQueryResultBuffer2buffer for an analog summary of a data-bearing tag (SysTimeSec) over a multi-hour window with an interval, then decode against theCAnalogSummaryValuefield set. Likely each row = StartDateTime FILETIME + the 8 typed fields.
Implementation steps (per the project's two-tests discipline)
- Add request params to
HistorianDataQueryRequestbuilders (aBuildAnalogSummaryRequest/BuildStateSummaryRequestalongsideBuildAggregateQueryRequest). - Live-probe
SysTimeSecvia a gated diagnostic; sanitize the response intofixtures/protocol/analog-summary/using the CW-1 pipeline. - Write
TryParseGetNextQueryResultBufferAnalogSummaryRows(+ state variant) against the fixture. - Public API:
ReadAnalogSummaryAsync/ReadStateSummaryAsyncreturning new modelsHistorianAnalogSummary(Min/Max/First/Last/Avg=Integral÷TimeGood/ValueCount/…) andHistorianStateSummary(per-state contained/partial/entry-count). ReuseRunQueryplumbing. - Golden-byte test on the parser + gated live test on
localhost(assert non-empty, fields sane).
Why stop here this session
UnpackFromValueBuffer is reader-call-based (no literal offset table), so a correct parser needs a
captured real buffer to decode against — that's the next concrete action, not a guess. Per
project rule ("never guess wire bytes; leave throwing until evidence supports it") no summary code
was added to src/ yet. Everything above is the evidence needed to implement directly.