Files
histsdk/docs/plans/r1.8-r1.9-summary-queries.md
T
Joseph Doherty 085f01123c docs: scope R1.8/R1.9 summary queries (decode targets located)
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>
2026-06-20 15:53:59 -04:00

4.8 KiB
Raw Blame History

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 0x0600038A92 wire field set: StartDateTime, Min, Max, First, Last, ValueCount, TimeGood, Integral, IntegralOfSquares
CAnalogSummaryStruct setters 0x0600036977 fuller field set: adds MinDateTime, MaxDateTime, FirstDateTime, LastDateTime, FirstNullDateTime, LastNullFlag, LinearIntegral
CStateSummaryStruct setters 0x0600039BA0 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)

  1. Request params. Recover the exact QueryType + SummaryType (+ whether ColumnSelectorFlags must change) for analog vs state summary. Source: decompile INSQL_QUERYTYPE/HISTORIAN_SUMMARYTYPE enum members and Select{Analog,State}SummaryColumns (the GetColumnFlag column-name ldstr operands → the flag set). The standard QueryType map (Cyclic=0 … EndBound=14) is already verified; summary is expected to be a SummaryType≠0 with an existing base QueryType, not a new mode ordinal — confirm.
  2. Row layout. Capture a real GetNextQueryResultBuffer2 buffer for an analog summary of a data-bearing tag (SysTimeSec) over a multi-hour window with an interval, then decode against the CAnalogSummaryValue field set. Likely each row = StartDateTime FILETIME + the 8 typed fields.

Implementation steps (per the project's two-tests discipline)

  1. Add request params to HistorianDataQueryRequest builders (a BuildAnalogSummaryRequest / BuildStateSummaryRequest alongside BuildAggregateQueryRequest).
  2. Live-probe SysTimeSec via a gated diagnostic; sanitize the response into fixtures/protocol/analog-summary/ using the CW-1 pipeline.
  3. Write TryParseGetNextQueryResultBufferAnalogSummaryRows (+ state variant) against the fixture.
  4. Public API: ReadAnalogSummaryAsync / ReadStateSummaryAsync returning new models HistorianAnalogSummary (Min/Max/First/Last/Avg=Integral÷TimeGood/ValueCount/…) and HistorianStateSummary (per-state contained/partial/entry-count). Reuse RunQuery plumbing.
  5. 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.