Skip to content

Fix binary type decoding of SimpleAggregateFunction/AggregateFunction (Dynamic, Variant, JSON) - #506

Open
polyglotAI-bot wants to merge 7 commits into
mainfrom
polyglot/fix-simple-aggregate-function-binary-decode
Open

Fix binary type decoding of SimpleAggregateFunction/AggregateFunction (Dynamic, Variant, JSON)#506
polyglotAI-bot wants to merge 7 commits into
mainfrom
polyglot/fix-simple-aggregate-function-binary-decode

Conversation

@polyglotAI-bot

Copy link
Copy Markdown
Collaborator

Description

Fixes #505.

Every value inside a Dynamic column carries its own binary type header, which the driver decodes in BinaryTypeDecoder.FromByteCode (the same entry point is used for Variant, JSON typed paths and nested Dynamic). Two dispatch branches were unimplemented stubs that threw NotImplementedException: SimpleAggregateFunction (0x2E) and AggregateFunction (0x25). So a query as simple as SELECT CAST(CAST(42, 'SimpleAggregateFunction(sum, UInt64)') AS Dynamic) failed, even though a top-level SimpleAggregateFunction column works (that path goes through the textual type-name grammar, not the binary decoder).

Both encodings are now decoded per the binary encoding spec: function name, the aggregate-function parameters, then the argument type encodings. SimpleAggregateFunction values are stored as the function's return type, which the server requires to equal the first argument type, so that type becomes the UnderlyingType used to read the value. AggregateFunction values are aggregation states that cannot be read directly, so the header is consumed and the existing AggregateFunctionType surfaces its usual actionable error (Use sumMerge() function to query this value) instead of NotImplementedException.

The function parameters (e.g. the 5 in SimpleAggregateFunction(groupArrayArray(5), Array(UInt32)), or quantiles(0.5, 0.9)) hold nothing needed to deserialize values, but their bytes must be consumed or the reader desyncs and silently corrupts the rest of the block — hence the parameter skipper, which implements the documented "aggregate function parameter binary encoding" table.

Changes

  • ClickHouse.Driver/Types/BinaryTypeDecoder.cs: implemented DecodeSimpleAggregateFunction and DecodeAggregateFunction (both now receive TypeSettings, so ReadStringsAsByteArrays/UseBigDecimal propagate into the argument type), plus SkipAggregateFunctionParameters / SkipAggregateFunctionParameter / SkipBytes / SkipVarInt helpers for the parameter grammar. Unknown parameter type codes raise NotSupportedException rather than silently desyncing the stream.
  • CHANGELOG.md / RELEASENOTES.md: bug-fix entry.

Test

  • ClickHouse.Driver.Tests/Types/DynamicTests.cs
    • Read_SimpleAggregateFunctionInDynamic_ReturnsUnderlyingValue (parametrized, 5 cases): sum, UInt64, anyLast, Decimal(18, 4), groupArrayArray, Array(UInt32), sumMap, Map(UInt32, UInt64) and the parameterized groupArrayArray(5), Array(UInt32). Each query selects a trailing Int32 column that is asserted too — it only decodes correctly if the type header consumed exactly its own bytes.
    • Read_AggregateFunctionInDynamic_ThrowsAggregateFunctionException (sumState, and quantilesState(0.5, 0.9) for the Float64 parameter path): asserts the <function>Merge() guidance is what surfaces.
  • ClickHouse.Driver.Tests/Types/BinaryTypeDecoderTests.cs: byte-level tests over headers captured from a real server's RowBinary output, asserting the decoded type and that decoding consumed exactly the header (stream.Position) — the desync failure mode is invisible to the AggregateFunction integration test, since its value bytes are never read. Also covers a multi-byte varint parameter (groupArrayArray(300)), TypeSettings propagation into the storage type, and the unknown-parameter-code error.

All 7 new integration cases fail on main with NotImplementedException; the full ClickHouse.Driver.Tests suite passes on this branch (9604 passed / 142 skipped, net10.0, ClickHouse 26.5). No existing test was modified.

Pre-PR validation gate

  • Deterministic repro confirmed (fails on main, passes here)
  • Root cause documented above
  • Fix targets the root cause
  • Test fails without fix, passes with fix
  • No existing tests broken or modified
  • Convention compliance verified per AGENTS.md (integration tests first, TestCaseSource parametrization, CHANGELOG + RELEASENOTES updated, no public API change)

Implement the SimpleAggregateFunction (0x2E) and AggregateFunction (0x25)
branches of BinaryTypeDecoder.FromByteCode, which were stubs throwing
NotImplementedException. Reading a SimpleAggregateFunction value out of a
Dynamic, Variant, JSON or nested Dynamic column now returns the value;
AggregateFunction reports the usual "use <function>Merge()" error instead.

Fixes: #505
Copilot AI review requested due to automatic review settings August 3, 2026 16:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes decoding of SimpleAggregateFunction and AggregateFunction type headers when they appear inside binary-encoded type contexts (Dynamic, Variant, JSON, and nested Dynamic), replacing previous NotImplementedException stubs with spec-compliant header consumption and adding regression tests to prevent RowBinary stream desynchronization.

Changes:

  • Implemented binary decoding for SimpleAggregateFunction and AggregateFunction headers in BinaryTypeDecoder, including skipping aggregate-function parameters to keep the stream aligned.
  • Added integration tests for reading SimpleAggregateFunction inside Dynamic and verifying AggregateFunction surfaces the existing actionable *Merge() guidance.
  • Added byte-level decoder tests to assert full header consumption (stream.Position) and error behavior on unknown parameter codes; updated CHANGELOG/RELEASENOTES.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ClickHouse.Driver/Types/BinaryTypeDecoder.cs Implements decoding for SimpleAggregateFunction/AggregateFunction and adds parameter-skipping helpers to avoid stream desync.
ClickHouse.Driver.Tests/Types/DynamicTests.cs Adds integration coverage for SAF-in-Dynamic roundtrip and AF-in-Dynamic error behavior.
ClickHouse.Driver.Tests/Types/BinaryTypeDecoderTests.cs Adds byte-level tests asserting exact header consumption and parameter-skipping behavior.
CHANGELOG.md Documents the bug fix in the Unreleased section.
RELEASENOTES.md Mirrors the Unreleased bug-fix note for release visibility.

Comment thread ClickHouse.Driver/Types/BinaryTypeDecoder.cs
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…explicitly

The varint is the aggregate function's own state serialization version, not a
header-layout version, so it is discarded rather than stored in an unused local.
Documented why it is not validated and pinned a non-zero version (as written for
AggregateFunction(1, uniqExact, UInt64)) with a decoder and a Dynamic read test.
The parameter skipper's per-type-code branches, the malformed-varint guard and
the missing-argument-type guard had no tests, so codecov/patch flagged the added
lines as uncovered. Each new case pins how many bytes one parameter type code
occupies -- consuming one byte too few or too many desynchronises the value (and
the next column) that follows the type header in the same stream.
…ggregate-function-binary-decode

# Conflicts:
#	CHANGELOG.md
#	RELEASENOTES.md
…ggregate-function-binary-decode

# Conflicts:
#	CHANGELOG.md
#	RELEASENOTES.md
…ggregate-function-binary-decode

# Conflicts:
#	CHANGELOG.md
#	RELEASENOTES.md
@polyglotAI-bot

Copy link
Copy Markdown
Collaborator Author

Rebased onto main — PR is MERGEABLE again, head efe6911.

Conflicts: CHANGELOG.md + RELEASENOTES.md only (both sides appended to the same Bug Fixes block after #503 merged). Resolved by keeping both sides — main's #502 entry first, then this PR's entry; nothing dropped. All code and test files (BinaryTypeDecoder.cs, BinaryTypeDecoderTests.cs, DynamicTests.cs) auto-merged cleanly.

Verification: git diff origin/main...HEAD is exactly this PR's delta (5 files, +496/−7) — no main-side content leaked. Build clean on net10.0; focused BinaryTypeDecoder + Dynamic + Variant + Json suite 1236 passed / 0 failed (74 pre-existing skips) against a live ClickHouse server. No existing test edited or weakened.

No approval existed on this PR, so none was dismissed by the merge.

…ggregate-function-binary-decode

# Conflicts:
#	CHANGELOG.md
#	RELEASENOTES.md
@polyglotAI-bot

Copy link
Copy Markdown
Collaborator Author

Rebase note: merged main to clear a conflict (round 3).

main advanced with #504, #490 and #501. #501 replaced direct edits to the Unreleased section with changelog.d/ fragments, so the conflict this time was not just an append collision — the whole Unreleased block moved out of CHANGELOG.md/RELEASENOTES.md.

Resolved by adopting the new convention rather than re-adding the entry to the old place:

No production or test code changed in this push. git diff main...HEAD is now exactly the #505 delta: BinaryTypeDecoder.cs, BinaryTypeDecoderTests.cs, DynamicTests.cs, plus the one fragment file. Driver build Release: 0 errors. Focused suite (BinaryTypeDecoder + Dynamic + Variant + JSON): 1237 passed / 0 failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reading SimpleAggregateFunction inside a Dynamic column throws NotImplementedException

2 participants