-
Notifications
You must be signed in to change notification settings - Fork 533
[ThinClient Integration]: Adds support for QueryPlan in thinclient mode. #5614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
microsoft-github-policy-service
merged 22 commits into
master
from
users/aavasthy/thinclientqueryplan
Mar 18, 2026
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
96413b7
Add QueryPlan support
aavasthy 3ae9d60
Merge with master.
aavasthy 25cb4bb
Update tests for QueryPlan thinclient
aavasthy 1221556
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy 80b9eeb
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy 563bc5e
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy 4ee8687
Fix tests for thinclient
aavasthy 152523f
Merge with master
aavasthy 2bd0264
Fix tests for thinclient
aavasthy 75c92c7
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy c0b3c0e
Update tests and logic for query by passing
aavasthy e248c42
Merge with Master
aavasthy c1574cb
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy fe47693
Add a separate class for thinclient parsing response
aavasthy 97045ac
Merge with master.
aavasthy d83421f
Revert changes in PartitionedQueryExecutionInfo
aavasthy f223861
Update flag check
aavasthy 230343a
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy f107e1c
Test code cleanup
aavasthy 9062eec
Update query plan serialization repsonse logic
aavasthy d4083f3
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy a1e8e07
Merge branch 'master' into users/aavasthy/thinclientqueryplan
aavasthy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
162 changes: 162 additions & 0 deletions
162
Microsoft.Azure.Cosmos/src/ThinClientQueryPlanHelper.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| //------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| //------------------------------------------------------------ | ||
|
|
||
| namespace Microsoft.Azure.Cosmos.Query.Core.QueryPlan | ||
| { | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Text.Json; | ||
| using Newtonsoft.Json; | ||
|
kirankumarkolli marked this conversation as resolved.
|
||
| using PartitionKeyDefinition = Documents.PartitionKeyDefinition; | ||
| using PartitionKeyInternal = Documents.Routing.PartitionKeyInternal; | ||
|
|
||
| /// <summary> | ||
| /// Handles conversion of thin client query plan responses where query ranges | ||
| /// are returned in PartitionKeyInternal format instead of EPK hex strings. | ||
| /// Mirrors the conversion logic in <see cref="QueryPartitionProvider.ConvertPartitionedQueryExecutionInfo"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Uses System.Text.Json for primary parsing and structural validation. | ||
| /// Newtonsoft.Json is used only for deserializing QueryInfo, HybridSearchQueryInfo, | ||
| /// and Range<PartitionKeyInternal> because these types and their deep type hierarchies | ||
| /// (including the external Direct package types) use Newtonsoft [JsonProperty] attributes | ||
| /// and [JsonObject(MemberSerialization.OptIn)] semantics that have no System.Text.Json equivalent. | ||
| /// </remarks> | ||
| internal static class ThinClientQueryPlanHelper | ||
| { | ||
| private static readonly Newtonsoft.Json.JsonSerializerSettings NewtonsoftSettings = | ||
|
kirankumarkolli marked this conversation as resolved.
|
||
| new Newtonsoft.Json.JsonSerializerSettings | ||
| { | ||
| DateParseHandling = Newtonsoft.Json.DateParseHandling.None, | ||
| MaxDepth = 64, | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Deserializes a thin client query plan response stream into a | ||
| /// <see cref="PartitionedQueryExecutionInfo"/> with EPK string ranges. | ||
| /// The response contains query ranges in PartitionKeyInternal format | ||
| /// which are converted to EPK hex strings and sorted. | ||
| /// </summary> | ||
| /// <param name="stream">The response stream containing the raw query plan JSON.</param> | ||
| /// <param name="partitionKeyDefinition">The partition key definition for the container.</param> | ||
| /// <returns><see cref="PartitionedQueryExecutionInfo"/> with sorted EPK string ranges.</returns> | ||
| /// <exception cref="ArgumentNullException">Thrown when <paramref name="stream"/> or <paramref name="partitionKeyDefinition"/> is null.</exception> | ||
| /// <exception cref="FormatException">Thrown when the response JSON is malformed or missing required properties.</exception> | ||
| public static PartitionedQueryExecutionInfo DeserializeQueryPlanResponse( | ||
|
aavasthy marked this conversation as resolved.
|
||
| Stream stream, | ||
| PartitionKeyDefinition partitionKeyDefinition) | ||
| { | ||
| if (stream == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(stream)); | ||
| } | ||
|
|
||
| if (partitionKeyDefinition == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(partitionKeyDefinition)); | ||
| } | ||
|
|
||
| using JsonDocument doc = JsonDocument.Parse(stream); | ||
| JsonElement root = doc.RootElement; | ||
|
|
||
| if (root.ValueKind != JsonValueKind.Object) | ||
| { | ||
| throw new FormatException( | ||
| $"Thin client query plan response must be a JSON object, but was {root.ValueKind}."); | ||
| } | ||
|
|
||
| // Validate and extract queryRanges (required) | ||
| if (!root.TryGetProperty("queryRanges", out JsonElement queryRangesElement)) | ||
| { | ||
| throw new FormatException( | ||
| "Thin client query plan response is missing the required 'queryRanges' property."); | ||
| } | ||
|
|
||
| if (queryRangesElement.ValueKind != JsonValueKind.Array) | ||
| { | ||
| throw new FormatException( | ||
| $"Expected 'queryRanges' to be a JSON array, but was {queryRangesElement.ValueKind}."); | ||
| } | ||
|
|
||
| if (queryRangesElement.GetArrayLength() == 0) | ||
| { | ||
| throw new FormatException( | ||
| "Thin client query plan response 'queryRanges' array must not be empty."); | ||
| } | ||
|
|
||
| // Deserialize QueryInfo using Newtonsoft because QueryInfo uses | ||
| // [JsonObject(MemberSerialization.OptIn)] and Newtonsoft-only [JsonProperty] attributes. | ||
| QueryInfo queryInfo = null; | ||
| if (root.TryGetProperty("queryInfo", out JsonElement queryInfoElement) | ||
| && queryInfoElement.ValueKind != JsonValueKind.Null) | ||
| { | ||
| queryInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QueryInfo>( | ||
| queryInfoElement.GetRawText(), | ||
| ThinClientQueryPlanHelper.NewtonsoftSettings); | ||
| } | ||
|
|
||
| // Deserialize HybridSearchQueryInfo using Newtonsoft (same constraint as QueryInfo). | ||
| HybridSearchQueryInfo hybridSearchQueryInfo = null; | ||
| if (root.TryGetProperty("hybridSearchQueryInfo", out JsonElement hybridElement) | ||
| && hybridElement.ValueKind != JsonValueKind.Null) | ||
| { | ||
| hybridSearchQueryInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<HybridSearchQueryInfo>( | ||
| hybridElement.GetRawText(), | ||
| ThinClientQueryPlanHelper.NewtonsoftSettings); | ||
| } | ||
|
|
||
| // Parse and convert query ranges to EPK string ranges. | ||
| // Range<PartitionKeyInternal> requires Newtonsoft because PartitionKeyInternal | ||
| // is from the external Direct package with Newtonsoft-based serialization. | ||
| List<Documents.Routing.Range<string>> effectiveRanges = | ||
| new List<Documents.Routing.Range<string>>(queryRangesElement.GetArrayLength()); | ||
|
|
||
| foreach (JsonElement rangeElement in queryRangesElement.EnumerateArray()) | ||
| { | ||
| if (rangeElement.ValueKind != JsonValueKind.Object) | ||
| { | ||
| throw new FormatException( | ||
| $"Each query range must be a JSON object, but was {rangeElement.ValueKind}."); | ||
| } | ||
|
|
||
| if (!rangeElement.TryGetProperty("min", out _)) | ||
| { | ||
| throw new FormatException( | ||
| "Query range is missing the required 'min' property."); | ||
| } | ||
|
|
||
| if (!rangeElement.TryGetProperty("max", out _)) | ||
| { | ||
| throw new FormatException( | ||
| "Query range is missing the required 'max' property."); | ||
| } | ||
|
|
||
| Documents.Routing.Range<PartitionKeyInternal> internalRange = | ||
| Newtonsoft.Json.JsonConvert.DeserializeObject<Documents.Routing.Range<PartitionKeyInternal>>( | ||
| rangeElement.GetRawText(), | ||
| ThinClientQueryPlanHelper.NewtonsoftSettings); | ||
|
|
||
| if (internalRange == null) | ||
| { | ||
| throw new FormatException( | ||
| "Failed to deserialize query range from thin client response."); | ||
| } | ||
|
|
||
| effectiveRanges.Add(PartitionKeyInternal.GetEffectivePartitionKeyRange( | ||
| partitionKeyDefinition, | ||
| internalRange)); | ||
| } | ||
|
|
||
| effectiveRanges.Sort(Documents.Routing.Range<string>.MinComparer.Instance); | ||
|
|
||
| return new PartitionedQueryExecutionInfo() | ||
| { | ||
| QueryInfo = queryInfo, | ||
| QueryRanges = effectiveRanges, | ||
| HybridSearchQueryInfo = hybridSearchQueryInfo, | ||
| }; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.