Cosmos: Adds Binary Encoding Support for Queries and Streaming order_by - #5040
Draft
Debdatta Kunda (kundadebdatta) wants to merge 16 commits into
Draft
Cosmos: Adds Binary Encoding Support for Queries and Streaming order_by#5040Debdatta Kunda (kundadebdatta) wants to merge 16 commits into
order_by#5040Debdatta Kunda (kundadebdatta) wants to merge 16 commits into
Conversation
Queries now advertise a binary response via x-ms-cosmos-supported-serialization-formats while keeping their application/query+json request body as text. Splits the driver binary gate into request-body encoding (point item ops) and response negotiation (item ops + query), sets the header at the plan_operation choke point every query page flows through, wires binary resolution into query_items, and honors the negotiation in the in-memory emulator feed responses. Adds a driver unit test and an emulator end-to-end binary query round-trip test, and updates the binary-encoding SPEC/HLD docs.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Adds an in-memory-emulator test that runs a full-container SELECT * over a 3-partition container with binary encoding enabled, proving the passthrough cross-partition query path round-trips binary Documents envelopes per page with no additional code beyond response negotiation.
added 13 commits
August 10, 2026 18:59
Records a per-operation comparison of Cosmos binary JSON support (request encode / response negotiate / response decode) between the Rust SDK+driver and azure-cosmos-dotnet-v3, including the ORDER BY/aggregate query-engine gap, the Delete negotiation divergence, the patch mechanism difference, and the header-value nuance, with source references on both sides.
Make the streaming ORDER BY merge binary-aware: parse_envelope_page now transcodes a binary-negotiated page to text before the envelope parse (no-op for text). Adds unit + integration coverage, extends the e2e round-trip fuzzer with single-partition and cross-partition ORDER BY query round-trips, and updates the .NET parity doc.
… gaps Addresses re-review findings #3 (correctness), #8, #9, #10. #3 — binary ORDER BY lost the native integral-Double->integer coercion that passthrough binary queries get, so a typed model with a wide integer field could round-trip through a passthrough query but fail on an ORDER BY query for the same document (the text/binary divergence, #5028). The streaming merge transcoded each binary page to text and rebuilt a *text* envelope, so the SDK decoded it with the text deserializer (which hard-fails on a service-echoed integral double for an integer field). - PageAggregator now tracks whether the source pages were binary and, when so, re-encodes the assembled envelope to Cosmos binary JSON in build_page, so the SDK's binary deserializer runs its integral-Double->integer coercion — matching passthrough. Text sources keep the zero-copy text envelope. - New unit tests prove the emitted format follows the source and that an integral-Double u64 payload now decodes (and would fail as text). - ids_in_page test helper is now format-aware (transcodes binary pages). - Live fuzzer: the typed IntProbe now also decodes through a cross-partition binary ORDER BY, exercising the merge coercion end to end. #8 — binary_cross_partition_query_round_trips could pass even if negotiation silently broke (text decodes fine). build_multi_partition_container now attaches a QueryRequestRecorder and the test asserts every fan-out page advertised a binary response with a text body. #9 — added binary_cross_partition_order_by_merges_and_round_trips: an always-run emulator test that exercises the real k-way merge over binary pages (previously only a mocked driver test + the live-only fuzzer covered it). #10 — the fuzzer's cross-partition ORDER BY fan-out (the most expensive query shape) now runs only on the binary configs, where it adds coverage; the single-partition passthrough query still runs on every config.
A - BINARY_NEGOTIATION_FORMATS doc comment was written in #4671 and never updated when this PR wired query negotiation. It claimed the constant applied 'on point operations' and that query negotiation was 'not yet wired' - the exact thing this PR ships. Rewrote it to cover point ops + query and to state explicitly why Rust forces CosmosBinary for query (vs .NET's JsonText,CosmosBinary), matching the SPEC. B - BINARY_ENCODING_SPEC.md listed 'delete' in the request-body gate in two places, but the code (supports_binary_request_body) and its unit test exclude delete. Dropped delete from both lists and noted the .NET divergence (.NET's IsPointOperationSupportedForBinaryEncoding does include delete). C - the rewritten query unit test only re-asserted two booleans already covered elsewhere and lost the behavioral guarantee its predecessor had. Replaced it with a behavioral test that drives a real query operation through apply_response_negotiation (the actual header owner) and asserts the application/query+json body stays text while the response advertises binary.
#7 - avoid resolving the binary-encoding options view twice per point op. execute_operation already resolves BinaryEncodingOptions for the request-body gate, but plan_operation -> apply_response_negotiation re-resolved the same layered view. Thread the resolved value through a private plan_operation_resolved into apply_response_negotiation; the public plan_operation (and the query path, which reaches the driver there directly) passes None and resolves lazily as before. Also corrected the now-stale comment claiming execute_operation sets the negotiation header (it no longer does after #6 - apply_response_negotiation owns it). #12 - replace the bare positional 'binary: bool' on success_feed_response / success_document_feed_response with a ResponseFormat { Text, Binary } enum, so the four read-feed/change-feed call sites read ResponseFormat::Text instead of a naked 'false' that a future edit could transpose (restores #4733's positional-creep guard). Query call sites use ResponseFormat::from( parsed.binary_response). Also documented the emulator's binary-response fidelity note in dispatch.rs (derives the flag from the header alone vs the real gateway honoring it only for Query - unreachable since Rust only advertises binary for point ops + query).
- #1 (blocking): make the binary-emit flag sticky on StreamingOrderedMerge so buffer-only pages (no backend fetch) still emit binary; a per-page flag left them text with float-widened integers that failed typed decode. + regression test. - #2: scope the BINARY_NEGOTIATION_FORMATS doc comment to note request_text_response is honored only for point ops, not queries. - #4: correct the stale into_items splitter comment (real reason it stays inert). - #11: assert query-plan requests carry no binary header instead of skipping them. - #14: move ResponseFormat below success_response_with_format to fix its orphaned doc. - #8: add CHANGELOG entries (SDK + driver) for query binary negotiation.
…ding_for_query # Conflicts: # sdk/cosmos/azure_data_cosmos_driver/src/driver/cosmos_driver.rs # sdk/cosmos/azure_data_cosmos_driver/src/driver/dataflow/integration_tests/order_by_resume.rs # sdk/cosmos/azure_data_cosmos_driver/src/driver/dataflow/query_response.rs
assert_query_roundtrip gained an 8th parameter (the ORDER-BY gate) that trips clippy::too_many_arguments under -Dwarnings. Bundling the args would only move the count into assert_query_hit/assert_roundtrip, so allow it on this test-only helper.
Closes the remaining review findings on binary response negotiation for queries, and covers two gaps the review surfaced. Correctness: - split_feed_envelope now handles Cosmos binary pages. OFFSET/LIMIT and TOP route through the SkipTake node, whose splitter was text-only, so those queries hard-failed with binary enabled. - A query with request_text_response no longer negotiates binary. Queries bypass the execute_operation transcode, so negotiating would hand a text-requesting caller binary pages. - Response negotiation no longer clobbers a caller-set format header. - emit_binary is promoted on the merge fill-error path and OR-assigned at the page bottom, so a sticky binary flag cannot be cleared. - build_page transcode failures are classified as SERIALIZATION_RESPONSE_BODY_INVALID (client-side re-encode) rather than a 500 service error, and carry the item ordinal. Tests: - End-to-end binary OFFSET/LIMIT and TOP round-trip against the emulator, verified load-bearing by mutation. - Byte-level query response assertions for both CosmosBinary and JsonText. - A binary-disabled query advertises no serialization format. - The sampled-corpus test now issues passthrough and ORDER BY queries per document; it previously only exercised create and read, so it was not validating query binary support at all. - Removed two vacuous assertions (empty-page format checks, unverified request bodies). Docs: - The Gateway 2.0 / thin-client text fallback is documented as a customer-visible limitation in both the SPEC and the HLD; the repeated per-document transcode is recorded as deferred work. - Added a comment-brevity convention to AGENTS.md and trimmed the verbose comment blocks this PR had accumulated.
Replace the "Deferred work" prose section with "Binary encoding support status": a per-operation table (request encode / response negotiate / response decode) and a numbered pending-work table with severity and size. Corrections: - The deferred-work note recommended slicing a document out of a binary page and re-prefixing it with 0x80. That is unsound: reference strings (STR_R1-STR_R4) resolve against absolute page offsets and the interning scope is the whole page, so a detached sub-slice mis-resolves any reference pointing outside it, silently returning wrong text. Replaced with a view-based design (refcounted page Bytes plus an offset). - The "binary feed responses" bullet claimed the feed splitter is text-only and cannot handle binary envelopes. split_feed_envelope handles them as of this branch. Rewritten as the invariant future splitters must keep. - Aggregate / GROUP BY / DISTINCT were listed as a binary gap. They are rejected cross-partition in any encoding, so they are blocked on the query engine rather than pending binary work. Restore the Rust vs .NET parity matrix (dropped in e7af8fda59) as a section here rather than a separate internal doc, updated for this branch: TOP / LIMIT / OFFSET now ship, and the Gateway 2.0 row carries the customer-visible framing instead of "still decodes".
order_by
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extends Cosmos binary JSON (the
0x80-preamble wire format) from point operations to the query path. Previously aquery_itemscall always received text pages, even with binary encoding enabled — which meant queries kept the integral-Double→ integer deserialization divergence (#5028) that binary encoding exists to fix.Opt-in and off by default; with the flag unset, behavior is byte-for-byte unchanged.
What changed
Negotiation. Queries now advertise
x-ms-cosmos-supported-serialization-formats: CosmosBinary, set once at theplan_operationchoke point that every per-page request flows through. An explicitly caller-set header is never clobbered, andrequest_text_responsestill forces text. The query request body stays text by design —application/query+jsonis a query spec, not a document.All three query pipelines handle binary pages:
into_singleparse_envelope_pagedecodes binary envelopes; merged items emitted as binaryOFFSET/LIMIT/TOPsplit_feed_envelopesplits binary pages into per-document binaryOFFSET/LIMIT/TOPwas a blocker: cross-partition skip/take failed outright on binary pages before this.Emitting merged ORDER BY items as binary is semantically load-bearing, not cosmetic. The binary deserializer coerces a service-echoed integral
Doubleinto an integer target; the text deserializer hard-fails on it. Emitting text would reintroduce the exact divergence for ORDER BY that passthrough queries no longer have.How a binary query page flows
sequenceDiagram participant SDK participant D as Driver participant S as Service SDK->>D: query_items (binary enabled) D->>S: page request + CosmosBinary header S-->>D: binary page (0x80 preamble) alt ORDER BY D->>D: decode envelope, merge on sort keys D->>D: re-encode merged items to binary else OFFSET / LIMIT / TOP D->>D: split envelope, re-encode each document standalone else passthrough D->>D: forward page unchanged end D-->>SDK: binary items SDK->>SDK: decode (auto-detect by preamble)Every producer emits each document standalone-encoded, so
into_itemsauto-detects format per item by preamble.Testing
0x80body and a text query does not