Skip to content

Commit 1845b7e

Browse files
[Cosmos] cross partition continuation token (#35511)
### Packages impacted by this PR @azure/cosmos ### Issues associated with this PR ### Describe the problem that is addressed by this PR Adds support for continuation token for cross partition queries ### What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen? DOC [Link](https://loop.cloud.microsoft/p/eyJ3Ijp7InUiOiJodHRwczovL21pY3Jvc29mdC5zaGFyZXBvaW50LmNvbS8%2FbmF2PWN6MGxNa1ltWkQxaUlWTlROemhDVTNaS2VVVlhhMjlZVEZrME1FazNhek5PVjFkek0xbGpVemxPYmtNeFoyOVpkRVkzTWpOYVMyTlBOVzVIT1VaVGNWWm5lbmxCUkhGeFEzY21aajB3TVZGRlIweFFWVlkwUlVkUVFVNUxNMGN6VWtnelMwbzNVak0yTmxrMFVVNUZKbU05Sm1ac2RXbGtQVEUlM0QiLCJyIjpmYWxzZX0sInAiOnsidSI6Imh0dHBzOi8vbWljcm9zb2Z0LnNoYXJlcG9pbnQuY29tLzpmbDovci9zaXRlcy9iZGJkZjc1NS03ZjViLTRjOTEtOTk5OS0wMjg3NzNhNTA5OGMvU2hhcmVkJTIwRG9jdW1lbnRzL0xvb3BBcHBEYXRhL1VudGl0bGVkJTIwMi5sb29wP2Q9dzZhNTA0Y2I1MTg1MzQ5NWFhZjg0NGFjYjJhNjFlMTM3JmNzZj0xJndlYj0xJm5hdj1jejBsTWtaemFYUmxjeVV5Um1Ka1ltUm1OelUxTFRkbU5XSXROR001TVMwNU9UazVMVEF5T0RjM00yRTFNRGs0WXlaa1BXSWhVMU0zT0VKVGRrcDVSVmRyYjFoTVdUUXdTVGRyTTA1WFYzTXpXV05UT1U1dVF6Rm5iMWwwUmpjeU0xcExZMDgxYmtjNVJsTnhWbWQ2ZVVGRWNYRkRkeVptUFRBeFVVVkhURkJWVmxaS1VrbEhWVlZaV1V4S1JUSTNRa05MV2sxV1IwUlpTbGdtWXowbE1rWW1abXgxYVdROU1TWmhQVXh2YjNCQmNIQW1jRDBsTkRCbWJIVnBaSGdsTWtac2IyOXdMWEJoWjJVdFkyOXVkR0ZwYm1WeUpuZzlKVGRDSlRJeWR5VXlNaVV6UVNVeU1sUXdVbFJWU0hoMFlWZE9lV0l6VG5aYWJsRjFZekpvYUdOdFZuZGlNbXgxWkVNMWFtSXlNVGhaYVVaVVZYcGpORkZzVGpKVGJteEdWakowZGxkRmVGcE9SRUpLVGpKemVsUnNaRmhqZWs1YVdURk5OVlJ0TlVSTlYyUjJWMWhTUjA1NlNYcFhhM1JxVkhwV2RWSjZiRWRWTTBaWFdqTndOVkZWVW5oalZVNHpaa1JCZUZWVlZraFVSa0pXVm1wU1JsSXhRa0pVYTNONlVucE9VMU5FVGt4VGFtUlRUWHBaTWxkVVVsSlVhMVVsTTBRbE1qSWxNa01sTWpKcEpUSXlKVE5CSlRJeVpqazFNakF6T1RndE5Ua3dOQzAwT0RCakxXRXhNRGd0TTJGbU56UmtNVEV6WmpGbEpUSXlKVGRFIiwiciI6ZmFsc2V9LCJpIjp7ImkiOiJmOTUyMDM5OC01OTA0LTQ4MGMtYTEwOC0zYWY3NGQxMTNmMWUifX0%3D) ### Are there test cases added in this PR? _(If not, why?)_ Yes ### Provide a list of related PRs _(if any)_ ### Command used to generate this PR:**_(Applicable only to SDK release request PRs)_ ### Checklists - [ ] Added impacted package name to the issue description - [ ] Does this PR needs any fixes in the SDK Generator?** _(If so, create an Issue in the [Autorest/typescript](https://github.com/Azure/autorest.typescript) repository and link it here)_ - [ ] Added a changelog (if necessary) --------- Co-authored-by: Manik Khandelwal <[email protected]>
1 parent 51d7883 commit 1845b7e

File tree

68 files changed

+11181
-538
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+11181
-538
lines changed

sdk/cosmosdb/cosmos/src/common/constants.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,23 @@ export const AAD_DEFAULT_SCOPE = "https://cosmos.azure.com/.default";
310310
export const AAD_AUTH_PREFIX = "type=aad&ver=1.0&sig=";
311311
export const AAD_RESOURCE_NOT_FOUND_ERROR = "AADSTS500011";
312312

313+
/**
314+
* @internal
315+
* Internal query execution constants - not part of public API
316+
*/
317+
const QueryExecution = {
318+
/** Default page size for query execution when maxItemCount is not specified */
319+
DEFAULT_PAGE_SIZE: 10,
320+
/** Default maximum buffer size for vector search queries */
321+
DEFAULT_MAX_VECTOR_SEARCH_BUFFER_SIZE: 50000,
322+
} as const;
323+
324+
/**
325+
* @internal
326+
* Export for internal SDK use only
327+
*/
328+
export { QueryExecution };
329+
313330
/**
314331
* @hidden
315332
*/
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { QueryRange } from "../../routing/QueryRange.js";
5+
import type { QueryRangeMapping } from "../../queryExecutionContext/queryRangeMapping.js";
6+
7+
/**
8+
* @hidden
9+
*/
10+
export interface RangeBoundary {
11+
/**
12+
* Minimum boundary (inclusive)
13+
*/
14+
min: string;
15+
/**
16+
* Maximum boundary (exclusive)
17+
*/
18+
max: string;
19+
}
20+
21+
/**
22+
* @hidden
23+
* Base interface for all continuation tokens containing common fields
24+
*/
25+
export interface BaseContinuationToken {
26+
/**
27+
* Resource ID of the container for which the continuation token is issued
28+
*/
29+
rid: string;
30+
31+
/**
32+
* List of query ranges with their continuation tokens
33+
*/
34+
rangeMappings: QueryRangeWithContinuationToken[];
35+
36+
/**
37+
* Current offset value for OFFSET/LIMIT queries
38+
*/
39+
offset?: number;
40+
41+
/**
42+
* Current limit value for OFFSET/LIMIT queries
43+
*/
44+
limit?: number;
45+
}
46+
47+
/**
48+
* @hidden
49+
* Composite continuation token for parallel query execution across multiple partition ranges
50+
*/
51+
export interface CompositeQueryContinuationToken extends BaseContinuationToken {}
52+
53+
/**
54+
* Creates a new CompositeQueryContinuationToken
55+
* @hidden
56+
*/
57+
export function createCompositeQueryContinuationToken(
58+
rid: string,
59+
rangeMappings: QueryRangeWithContinuationToken[],
60+
offset?: number,
61+
limit?: number,
62+
): CompositeQueryContinuationToken {
63+
if (!rangeMappings || rangeMappings.length === 0) {
64+
throw new Error(
65+
"Failed to create composite continuation token: No partition range mappings provided. " +
66+
"This typically indicates an issue with query execution context initialization or partition key range resolution. " +
67+
"Ensure the query is properly configured and the container has valid partition ranges.",
68+
);
69+
}
70+
71+
return {
72+
rid,
73+
rangeMappings: rangeMappings,
74+
offset,
75+
limit,
76+
};
77+
}
78+
79+
/**
80+
* Serializes the composite continuation token to a JSON string
81+
* @hidden
82+
*/
83+
export function serializeCompositeToken(token: CompositeQueryContinuationToken): string {
84+
return JSON.stringify(token);
85+
}
86+
87+
/**
88+
* Deserializes a JSON string to a CompositeQueryContinuationToken
89+
* @hidden
90+
*/
91+
export function parseCompositeQueryContinuationToken(
92+
tokenString: string,
93+
): CompositeQueryContinuationToken {
94+
return JSON.parse(tokenString);
95+
}
96+
97+
/**
98+
* Deserializes a JSON string to a CompositeQueryContinuationToken
99+
* @hidden
100+
*/
101+
export function parseBaseContinuationToken(tokenString: string): BaseContinuationToken {
102+
return JSON.parse(tokenString);
103+
}
104+
105+
/**
106+
* @hidden
107+
* Represents a query range with its associated continuation token
108+
*/
109+
export interface QueryRangeWithContinuationToken {
110+
/**
111+
* The simplified query range containing min/max boundaries
112+
*/
113+
queryRange: RangeBoundary;
114+
115+
/**
116+
* The continuation token for this specific range
117+
*/
118+
continuationToken: string | undefined;
119+
}
120+
121+
/**
122+
* Converts QueryRangeMapping to QueryRangeWithContinuationToken using simplified range format
123+
* @param rangeMapping - The QueryRangeMapping to convert
124+
* @returns QueryRangeWithContinuationToken with simplified boundaries and continuation token
125+
* @hidden
126+
*/
127+
export function convertRangeMappingToQueryRange(
128+
rangeMapping: QueryRangeMapping,
129+
): QueryRangeWithContinuationToken {
130+
if (!rangeMapping.partitionKeyRange) {
131+
throw new Error(
132+
"Failed to convert range mapping: Missing partition key range information. " +
133+
"The QueryRangeMapping object must contain a valid partitionKeyRange with min and max boundaries. " +
134+
"This may indicate an incomplete partition key range resolution during query setup.",
135+
);
136+
}
137+
138+
const pkRange = rangeMapping.partitionKeyRange;
139+
140+
// Create simplified range assuming min is inclusive and max is exclusive
141+
const simplifiedRange: RangeBoundary = {
142+
min: pkRange.minInclusive,
143+
max: pkRange.maxExclusive,
144+
};
145+
146+
return {
147+
queryRange: simplifiedRange,
148+
continuationToken: rangeMapping.continuationToken,
149+
};
150+
}
151+
152+
/**
153+
* Converts an array of QueryRangeMapping to an array of QueryRangeWithContinuationToken
154+
* @param rangeMappings - Array of QueryRangeMapping to convert
155+
* @returns Array of QueryRangeWithContinuationToken with simplified boundaries and continuation tokens
156+
* @hidden
157+
*/
158+
export function convertRangeMappingsToQueryRangesWithTokens(
159+
rangeMappings: QueryRangeMapping[],
160+
): QueryRangeWithContinuationToken[] {
161+
return rangeMappings.map((mapping) => convertRangeMappingToQueryRange(mapping));
162+
}
163+
164+
/**
165+
* Converts a SimplifiedQueryRange back to a QueryRange for internal use
166+
* @param simplifiedRange - The simplified range to convert
167+
* @returns QueryRange with standard assumptions (min inclusive, max exclusive)
168+
* @hidden
169+
*/
170+
export function convertSimplifiedRangeToQueryRange(simplifiedRange: RangeBoundary): QueryRange {
171+
return new QueryRange(
172+
simplifiedRange.min,
173+
simplifiedRange.max,
174+
true, // minInclusive = true (assumption)
175+
false, // maxInclusive = false (max is exclusive, assumption)
176+
);
177+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import type {
5+
QueryRangeWithContinuationToken,
6+
BaseContinuationToken,
7+
} from "./CompositeQueryContinuationToken.js";
8+
9+
/**
10+
* Continuation token for order by queries.
11+
* @hidden
12+
*/
13+
export interface OrderByQueryContinuationToken extends BaseContinuationToken {
14+
/**
15+
* Order by items for the query
16+
*/
17+
orderByItems: any[];
18+
19+
/**
20+
* Number of items to skip in the query
21+
*/
22+
skipCount: number;
23+
24+
/**
25+
* Document ID of the last document result
26+
*/
27+
documentRid: string;
28+
29+
/**
30+
* Hash of the last document result for distinct order queries
31+
* Used to ensure duplicates are not returned across continuation boundaries
32+
*/
33+
hashedLastResult?: string;
34+
}
35+
36+
/**
37+
* Creates an OrderByQueryContinuationToken
38+
* @hidden
39+
*/
40+
export function createOrderByQueryContinuationToken(
41+
rangeMappings: QueryRangeWithContinuationToken[],
42+
orderByItems: any[],
43+
rid: string,
44+
skipCount: number,
45+
documentRid?: string,
46+
offset?: number,
47+
limit?: number,
48+
hashedLastResult?: string,
49+
): OrderByQueryContinuationToken {
50+
if (!rangeMappings || rangeMappings.length === 0) {
51+
throw new Error("rangeMappings must contain at least one element");
52+
}
53+
54+
if (!orderByItems || orderByItems.length === 0) {
55+
throw new Error("orderByItems must contain at least one element");
56+
}
57+
58+
return {
59+
rangeMappings,
60+
orderByItems,
61+
rid,
62+
skipCount,
63+
documentRid,
64+
offset,
65+
limit,
66+
hashedLastResult,
67+
} as OrderByQueryContinuationToken;
68+
}
69+
70+
/**
71+
* Serializes an OrderByQueryContinuationToken to a JSON string
72+
* @hidden
73+
*/
74+
export function serializeOrderByQueryContinuationToken(
75+
token: OrderByQueryContinuationToken,
76+
): string {
77+
return JSON.stringify(token);
78+
}
79+
80+
/**
81+
* Deserializes a JSON string to an OrderByQueryContinuationToken
82+
* @hidden
83+
*/
84+
export function parseOrderByQueryContinuationToken(
85+
tokenString: string,
86+
): OrderByQueryContinuationToken {
87+
return JSON.parse(tokenString);
88+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import type { QueryRange } from "../../index.js";
5+
6+
/**
7+
* Represents information about a partition range update that occurred during query execution.
8+
* This includes the original range, new ranges after split/merge, and the continuation token.
9+
* @hidden
10+
*/
11+
export interface PartitionRangeUpdate {
12+
/** The original partition key range before the split/merge operation */
13+
oldRange: QueryRange;
14+
/** The new partition key ranges after the split/merge operation */
15+
newRanges: QueryRange[];
16+
/** The continuation token associated with this range update */
17+
continuationToken: string | undefined;
18+
}
19+
20+
/**
21+
* A collection of partition range updates indexed by range keys.
22+
* The key is typically in the format "minInclusive-maxExclusive".
23+
* @hidden
24+
*/
25+
export type PartitionRangeUpdates = Record<string, PartitionRangeUpdate>;

0 commit comments

Comments
 (0)