public static async Task <TryCatch <CosmosOrderByItemQueryExecutionContext> > TryCreateAsync( CosmosQueryContext queryContext, CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams, string requestContinuationToken, CancellationToken cancellationToken) { Debug.Assert( initParams.PartitionedQueryExecutionInfo.QueryInfo.HasOrderBy, "OrderBy~Context must have order by query info."); if (queryContext == null) { throw new ArgumentNullException(nameof(queryContext)); } cancellationToken.ThrowIfCancellationRequested(); CosmosOrderByItemQueryExecutionContext context = new CosmosOrderByItemQueryExecutionContext( initPararms: queryContext, maxConcurrency: initParams.MaxConcurrency, maxItemCount: initParams.MaxItemCount, maxBufferedItemCount: initParams.MaxBufferedItemCount, consumeComparer: new OrderByConsumeComparer(initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderBy)); return((await context.TryInitializeAsync( sqlQuerySpec: initParams.SqlQuerySpec, requestContinuation: requestContinuationToken, collectionRid: initParams.CollectionRid, partitionKeyRanges: initParams.PartitionKeyRanges, initialPageSize: initParams.InitialPageSize, sortOrders: initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderBy, orderByExpressions: initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderByExpressions, cancellationToken: cancellationToken)) .Try <CosmosOrderByItemQueryExecutionContext>((ignore) => context)); }
/// <summary> /// Creates an CosmosOrderByItemQueryExecutionContext /// </summary> /// <param name="constructorParams">The parameters for the base class constructor.</param> /// <param name="initParams">The parameters to initialize the base class.</param> /// <param name="token">The cancellation token.</param> /// <returns>A task to await on, which in turn creates an CosmosOrderByItemQueryExecutionContext.</returns> public static async Task <CosmosOrderByItemQueryExecutionContext> CreateAsync( CosmosQueryContext constructorParams, CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams, CancellationToken token) { Debug.Assert( initParams.PartitionedQueryExecutionInfo.QueryInfo.HasOrderBy, "OrderBy~Context must have order by query info."); CosmosOrderByItemQueryExecutionContext context = new CosmosOrderByItemQueryExecutionContext( constructorParams, new OrderByConsumeComparer(initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderBy)); await context.InitializeAsync( constructorParams.SqlQuerySpec, initParams.RequestContinuation, initParams.CollectionRid, initParams.PartitionKeyRanges, initParams.InitialPageSize, initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderBy, initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderByExpressions, token); return(context); }
/// <summary> /// Creates an CosmosOrderByItemQueryExecutionContext /// </summary> /// <param name="queryContext">The parameters for the base class constructor.</param> /// <param name="initParams">The parameters to initialize the base class.</param> /// <param name="requestContinuationToken">The request continuation.</param> /// <param name="token">The cancellation token.</param> /// <returns>A task to await on, which in turn creates an CosmosOrderByItemQueryExecutionContext.</returns> public static async Task <CosmosOrderByItemQueryExecutionContext> CreateAsync( CosmosQueryContext queryContext, CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams, string requestContinuationToken, CancellationToken token) { Debug.Assert( initParams.PartitionedQueryExecutionInfo.QueryInfo.HasOrderBy, "OrderBy~Context must have order by query info."); CosmosOrderByItemQueryExecutionContext context = new CosmosOrderByItemQueryExecutionContext( initPararms: queryContext, maxConcurrency: initParams.MaxConcurrency, maxItemCount: initParams.MaxItemCount, maxBufferedItemCount: initParams.MaxBufferedItemCount, consumeComparer: new OrderByConsumeComparer(initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderBy)); await context.InitializeAsync( sqlQuerySpec : initParams.SqlQuerySpec, requestContinuation : requestContinuationToken, collectionRid : initParams.CollectionRid, partitionKeyRanges : initParams.PartitionKeyRanges, initialPageSize : initParams.InitialPageSize, sortOrders : initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderBy, orderByExpressions : initParams.PartitionedQueryExecutionInfo.QueryInfo.OrderByExpressions, cancellationToken : token); return(context); }
/// <summary> /// Creates a CosmosPipelinedItemQueryExecutionContext. /// </summary> /// <param name="queryContext">The parameters for constructing the base class.</param> /// <param name="initParams">The initial parameters</param> /// <param name="requestContinuationToken">The request continuation.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task to await on, which in turn returns a CosmosPipelinedItemQueryExecutionContext.</returns> public static async Task <CosmosQueryExecutionContext> CreateAsync( CosmosQueryContext queryContext, CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams, string requestContinuationToken, CancellationToken cancellationToken) { DefaultTrace.TraceInformation( string.Format( CultureInfo.InvariantCulture, "{0}, CorrelatedActivityId: {1} | Pipelined~Context.CreateAsync", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture), queryContext.CorrelatedActivityId)); QueryInfo queryInfo = initParams.PartitionedQueryExecutionInfo.QueryInfo; int actualPageSize = initParams.InitialPageSize; int initialPageSize = initParams.InitialPageSize; CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams parameters = initParams; if (queryInfo.HasGroupBy) { initialPageSize = int.MaxValue; initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: initParams.SqlQuerySpec, collectionRid: initParams.CollectionRid, partitionedQueryExecutionInfo: initParams.PartitionedQueryExecutionInfo, partitionKeyRanges: initParams.PartitionKeyRanges, initialPageSize: initialPageSize, maxConcurrency: initParams.MaxConcurrency, maxItemCount: int.MaxValue, maxBufferedItemCount: initParams.MaxBufferedItemCount); } Func <string, Task <IDocumentQueryExecutionComponent> > createOrderByComponentFunc = async(continuationToken) => { return(await CosmosOrderByItemQueryExecutionContext.CreateAsync( queryContext, initParams, continuationToken, cancellationToken)); }; Func <string, Task <IDocumentQueryExecutionComponent> > createParallelComponentFunc = async(continuationToken) => { return(await CosmosParallelItemQueryExecutionContext.CreateAsync( queryContext, initParams, continuationToken, cancellationToken)); }; return((CosmosQueryExecutionContext)await PipelinedDocumentQueryExecutionContext.CreateHelperAsync( queryContext.QueryClient, initParams.PartitionedQueryExecutionInfo.QueryInfo, initialPageSize, requestContinuationToken, createOrderByComponentFunc, createParallelComponentFunc)); }
/// <summary> /// Creates a CosmosPipelinedItemQueryExecutionContext. /// </summary> /// <param name="executionEnvironment">The environment to execute on.</param> /// <param name="queryContext">The parameters for constructing the base class.</param> /// <param name="initParams">The initial parameters</param> /// <param name="requestContinuationToken">The request continuation.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task to await on, which in turn returns a CosmosPipelinedItemQueryExecutionContext.</returns> public static async Task <CosmosQueryExecutionContext> CreateAsync( ExecutionEnvironment executionEnvironment, CosmosQueryContext queryContext, CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams, string requestContinuationToken, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); QueryInfo queryInfo = initParams.PartitionedQueryExecutionInfo.QueryInfo; int initialPageSize = initParams.InitialPageSize; CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams parameters = initParams; if (queryInfo.HasGroupBy) { // The query will block until all groupings are gathered so we might as well speed up the process. initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: initParams.SqlQuerySpec, collectionRid: initParams.CollectionRid, partitionedQueryExecutionInfo: initParams.PartitionedQueryExecutionInfo, partitionKeyRanges: initParams.PartitionKeyRanges, initialPageSize: int.MaxValue, maxConcurrency: initParams.MaxConcurrency, maxItemCount: int.MaxValue, maxBufferedItemCount: initParams.MaxBufferedItemCount); } Func <string, Task <IDocumentQueryExecutionComponent> > createOrderByComponentFunc = async(continuationToken) => { return(await CosmosOrderByItemQueryExecutionContext.CreateAsync( queryContext, initParams, continuationToken, cancellationToken)); }; Func <string, Task <IDocumentQueryExecutionComponent> > createParallelComponentFunc = async(continuationToken) => { return(await CosmosParallelItemQueryExecutionContext.CreateAsync( queryContext, initParams, continuationToken, cancellationToken)); }; return((CosmosQueryExecutionContext)await PipelinedDocumentQueryExecutionContext.CreateHelperAsync( executionEnvironment, queryContext.QueryClient, initParams.PartitionedQueryExecutionInfo.QueryInfo, initialPageSize, requestContinuationToken, createOrderByComponentFunc, createParallelComponentFunc)); }
/// <summary> /// Creates a CosmosPipelinedItemQueryExecutionContext. /// </summary> /// <param name="constructorParams">The parameters for constructing the base class.</param> /// <param name="collectionRid">The collection rid.</param> /// <param name="partitionedQueryExecutionInfo">The partitioned query execution info.</param> /// <param name="partitionKeyRanges">The partition key ranges.</param> /// <param name="initialPageSize">The initial page size.</param> /// <param name="requestContinuation">The request continuation.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task to await on, which in turn returns a CosmosPipelinedItemQueryExecutionContext.</returns> public static async Task <CosmosQueryExecutionContext> CreateAsync( CosmosQueryContext constructorParams, string collectionRid, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo, List <PartitionKeyRange> partitionKeyRanges, int initialPageSize, string requestContinuation, CancellationToken cancellationToken) { DefaultTrace.TraceInformation( string.Format( CultureInfo.InvariantCulture, "{0}, CorrelatedActivityId: {1} | Pipelined~Context.CreateAsync", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture), constructorParams.CorrelatedActivityId)); Func <string, Task <IDocumentQueryExecutionComponent> > createOrderByComponentFunc = async(continuationToken) => { CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( collectionRid, partitionedQueryExecutionInfo, partitionKeyRanges, initialPageSize, continuationToken); return(await CosmosOrderByItemQueryExecutionContext.CreateAsync( constructorParams, initParams, cancellationToken)); }; Func <string, Task <IDocumentQueryExecutionComponent> > createParallelComponentFunc = async(continuationToken) => { CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( collectionRid, partitionedQueryExecutionInfo, partitionKeyRanges, initialPageSize, continuationToken); return(await CosmosParallelItemQueryExecutionContext.CreateAsync( constructorParams, initParams, cancellationToken)); }; return((CosmosQueryExecutionContext)(await PipelinedDocumentQueryExecutionContext.CreateHelperAsync( partitionedQueryExecutionInfo.QueryInfo, initialPageSize, requestContinuation, createOrderByComponentFunc, createParallelComponentFunc))); }
/// <summary> /// Gets the formatted filters for every partition. /// </summary> /// <param name="expressions">The filter expressions.</param> /// <param name="continuationTokens">The continuation token.</param> /// <param name="sortOrders">The sort orders.</param> /// <returns>The formatted filters for every partition.</returns> private static FormattedFilterInfo GetFormattedFilters( string[] expressions, OrderByContinuationToken[] continuationTokens, SortOrder[] sortOrders) { // Validate the inputs for (int index = 0; index < continuationTokens.Length; index++) { Debug.Assert(continuationTokens[index].OrderByItems.Count == sortOrders.Length, "Expect values and orders are the same size."); Debug.Assert(expressions.Length == sortOrders.Length, "Expect expressions and orders are the same size."); } Tuple <string, string, string> filters = CosmosOrderByItemQueryExecutionContext.GetFormattedFilters( expressions, continuationTokens[0].OrderByItems.Select(orderByItem => orderByItem.Item).ToArray(), sortOrders); return(new FormattedFilterInfo(filters.Item1, filters.Item2, filters.Item3)); }
/// <summary> /// Gets the filters for every partition. /// </summary> private static TryCatch <OrderByInitInfo> TryGetOrderByPartitionKeyRangesInitializationInfo( OrderByContinuationToken[] suppliedContinuationTokens, List <PartitionKeyRange> partitionKeyRanges, SortOrder[] sortOrders, string[] orderByExpressions) { TryCatch <InitInfo <OrderByContinuationToken> > tryFindRangeAndContinuationTokensMonad = CosmosCrossPartitionQueryExecutionContext.TryFindTargetRangeAndExtractContinuationTokens( partitionKeyRanges, suppliedContinuationTokens .Select(token => Tuple.Create(token, token.CompositeContinuationToken.Range))); return(tryFindRangeAndContinuationTokensMonad.Try <OrderByInitInfo>((indexAndContinuationTokens) => { int minIndex = indexAndContinuationTokens.TargetIndex; IReadOnlyDictionary <string, OrderByContinuationToken> partitionKeyRangeToContinuationToken = indexAndContinuationTokens.ContinuationTokens; FormattedFilterInfo formattedFilterInfo = CosmosOrderByItemQueryExecutionContext.GetFormattedFilters( orderByExpressions, suppliedContinuationTokens, sortOrders); RangeFilterInitializationInfo[] filters = new RangeFilterInitializationInfo[] { new RangeFilterInitializationInfo( filter: formattedFilterInfo.FilterForRangesLeftOfTargetRanges, startIndex: 0, endIndex: minIndex - 1), new RangeFilterInitializationInfo( filter: formattedFilterInfo.FiltersForTargetRange, startIndex: minIndex, endIndex: minIndex), new RangeFilterInitializationInfo( filter: formattedFilterInfo.FilterForRangesRightOfTargetRanges, startIndex: minIndex + 1, endIndex: partitionKeyRanges.Count - 1), }; return new OrderByInitInfo( filters, partitionKeyRangeToContinuationToken); })); }
/// <summary> /// Creates a CosmosPipelinedItemQueryExecutionContext. /// </summary> /// <param name="executionEnvironment">The environment to execute on.</param> /// <param name="queryContext">The parameters for constructing the base class.</param> /// <param name="initParams">The initial parameters</param> /// <param name="requestContinuationToken">The request continuation.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task to await on, which in turn returns a CosmosPipelinedItemQueryExecutionContext.</returns> public static async Task <CosmosQueryExecutionContext> CreateAsync( ExecutionEnvironment executionEnvironment, CosmosQueryContext queryContext, CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams, string requestContinuationToken, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (requestContinuationToken != null) { if (!PipelineContinuationToken.TryParse( requestContinuationToken, out PipelineContinuationToken pipelineContinuationToken)) { throw queryContext.QueryClient.CreateBadRequestException( $"Malformed {nameof(PipelineContinuationToken)}: {requestContinuationToken}."); } if (PipelineContinuationToken.IsTokenFromTheFuture(pipelineContinuationToken)) { throw queryContext.QueryClient.CreateBadRequestException( $"{nameof(PipelineContinuationToken)} Continuation token is from a newer version of the SDK. Upgrade the SDK to avoid this issue.\n {requestContinuationToken}."); } if (!PipelineContinuationToken.TryConvertToLatest( pipelineContinuationToken, out PipelineContinuationTokenV1_1 latestVersionPipelineContinuationToken)) { throw queryContext.QueryClient.CreateBadRequestException( $"{nameof(PipelineContinuationToken)}: '{requestContinuationToken}' is no longer supported."); } requestContinuationToken = latestVersionPipelineContinuationToken.SourceContinuationToken; } QueryInfo queryInfo = initParams.PartitionedQueryExecutionInfo.QueryInfo; int initialPageSize = initParams.InitialPageSize; CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams parameters = initParams; if (queryInfo.HasGroupBy) { // The query will block until all groupings are gathered so we might as well speed up the process. initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( sqlQuerySpec: initParams.SqlQuerySpec, collectionRid: initParams.CollectionRid, partitionedQueryExecutionInfo: initParams.PartitionedQueryExecutionInfo, partitionKeyRanges: initParams.PartitionKeyRanges, initialPageSize: int.MaxValue, maxConcurrency: initParams.MaxConcurrency, maxItemCount: int.MaxValue, maxBufferedItemCount: initParams.MaxBufferedItemCount); } Func <string, Task <IDocumentQueryExecutionComponent> > createOrderByComponentFunc = async(continuationToken) => { return(await CosmosOrderByItemQueryExecutionContext.CreateAsync( queryContext, initParams, continuationToken, cancellationToken)); }; Func <string, Task <IDocumentQueryExecutionComponent> > createParallelComponentFunc = async(continuationToken) => { return(await CosmosParallelItemQueryExecutionContext.CreateAsync( queryContext, initParams, continuationToken, cancellationToken)); }; return((CosmosQueryExecutionContext)await PipelinedDocumentQueryExecutionContext.CreateHelperAsync( executionEnvironment, queryContext.QueryClient, initParams.PartitionedQueryExecutionInfo.QueryInfo, initialPageSize, requestContinuationToken, createOrderByComponentFunc, createParallelComponentFunc)); }
/// <summary> /// Creates a CosmosPipelinedItemQueryExecutionContext. /// </summary> /// <param name="constructorParams">The parameters for constructing the base class.</param> /// <param name="collectionRid">The collection rid.</param> /// <param name="partitionedQueryExecutionInfo">The partitioned query execution info.</param> /// <param name="partitionKeyRanges">The partition key ranges.</param> /// <param name="initialPageSize">The initial page size.</param> /// <param name="requestContinuation">The request continuation.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task to await on, which in turn returns a CosmosPipelinedItemQueryExecutionContext.</returns> public static async Task <IDocumentQueryExecutionContext> CreateAsync( CosmosQueryContext constructorParams, string collectionRid, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo, List <PartitionKeyRange> partitionKeyRanges, int initialPageSize, string requestContinuation, CancellationToken cancellationToken) { DefaultTrace.TraceInformation( string.Format( CultureInfo.InvariantCulture, "{0}, CorrelatedActivityId: {1} | Pipelined~Context.CreateAsync", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture), constructorParams.CorrelatedActivityId)); Func <string, Task <IDocumentQueryExecutionComponent> > createComponentFunc; QueryInfo queryInfo = partitionedQueryExecutionInfo.QueryInfo; if (queryInfo.HasOrderBy) { createComponentFunc = async(continuationToken) => { CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( collectionRid, partitionedQueryExecutionInfo, partitionKeyRanges, initialPageSize, continuationToken); return(await CosmosOrderByItemQueryExecutionContext.CreateAsync( constructorParams, initParams, cancellationToken)); }; } else { createComponentFunc = async(continuationToken) => { CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams initParams = new CosmosCrossPartitionQueryExecutionContext.CrossPartitionInitParams( collectionRid, partitionedQueryExecutionInfo, partitionKeyRanges, initialPageSize, continuationToken); return(await CosmosParallelItemQueryExecutionContext.CreateAsync( constructorParams, initParams, cancellationToken)); }; } if (queryInfo.HasAggregates) { Func <string, Task <IDocumentQueryExecutionComponent> > createSourceCallback = createComponentFunc; createComponentFunc = async(continuationToken) => { return(await AggregateDocumentQueryExecutionComponent.CreateAsync( queryInfo.Aggregates, continuationToken, createSourceCallback)); }; } if (queryInfo.HasDistinct) { Func <string, Task <IDocumentQueryExecutionComponent> > createSourceCallback = createComponentFunc; createComponentFunc = async(continuationToken) => { return(await DistinctDocumentQueryExecutionComponent.CreateAsync( continuationToken, createSourceCallback, queryInfo.DistinctType)); }; } if (queryInfo.HasOffset) { if (!constructorParams.QueryRequestOptions.EnableCrossPartitionSkipTake) { throw new ArgumentException("Cross Partition OFFSET / LIMIT is not supported."); } Func <string, Task <IDocumentQueryExecutionComponent> > createSourceCallback = createComponentFunc; createComponentFunc = async(continuationToken) => { return(await SkipDocumentQueryExecutionComponent.CreateAsync( queryInfo.Offset.Value, continuationToken, createSourceCallback)); }; } if (queryInfo.HasLimit) { if (!constructorParams.QueryRequestOptions.EnableCrossPartitionSkipTake) { throw new ArgumentException("Cross Partition OFFSET / LIMIT is not supported."); } Func <string, Task <IDocumentQueryExecutionComponent> > createSourceCallback = createComponentFunc; createComponentFunc = async(continuationToken) => { return(await TakeDocumentQueryExecutionComponent.CreateLimitDocumentQueryExecutionComponentAsync( queryInfo.Limit.Value, continuationToken, createSourceCallback)); }; } if (queryInfo.HasTop) { Func <string, Task <IDocumentQueryExecutionComponent> > createSourceCallback = createComponentFunc; createComponentFunc = async(continuationToken) => { return(await TakeDocumentQueryExecutionComponent.CreateTopDocumentQueryExecutionComponentAsync( queryInfo.Top.Value, continuationToken, createSourceCallback)); }; } return(new CosmosPipelinedItemQueryExecutionContext( await createComponentFunc(requestContinuation), initialPageSize)); }
private static Tuple <string, string, string> GetFormattedFilters( string[] expressions, CosmosElement[] orderByItems, SortOrder[] sortOrders) { // When we run cross partition queries, // we only serialize the continuation token for the partition that we left off on. // The only problem is that when we resume the order by query, // we don't have continuation tokens for all other partition. // The saving grace is that the data has a composite sort order(query sort order, partition key range id) // so we can generate range filters which in turn the backend will turn into rid based continuation tokens, // which is enough to get the streams of data flowing from all partitions. // The details of how this is done is described below: int numOrderByItems = expressions.Length; bool isSingleOrderBy = numOrderByItems == 1; StringBuilder left = new StringBuilder(); StringBuilder target = new StringBuilder(); StringBuilder right = new StringBuilder(); Tuple <StringBuilder, StringBuilder, StringBuilder> builders = new Tuple <StringBuilder, StringBuilder, StringBuilder>(left, right, target); if (isSingleOrderBy) { //For a single order by query we resume the continuations in this manner // Suppose the query is SELECT* FROM c ORDER BY c.string ASC // And we left off on partition N with the value "B" // Then // All the partitions to the left will have finished reading "B" // Partition N is still reading "B" // All the partitions to the right have let to read a "B // Therefore the filters should be // > "B" , >= "B", and >= "B" respectively // Repeat the same logic for DESC and you will get // < "B", <= "B", and <= "B" respectively // The general rule becomes // For ASC // > for partitions to the left // >= for the partition we left off on // >= for the partitions to the right // For DESC // < for partitions to the left // <= for the partition we left off on // <= for the partitions to the right string expression = expressions.First(); SortOrder sortOrder = sortOrders.First(); CosmosElement orderByItem = orderByItems.First(); string orderByItemToString = JsonConvert.SerializeObject(orderByItem, DefaultJsonSerializationSettings.Value); left.Append($"{expression} {(sortOrder == SortOrder.Descending ? "<" : ">")} {orderByItemToString}"); target.Append($"{expression} {(sortOrder == SortOrder.Descending ? "<=" : ">=")} {orderByItemToString}"); right.Append($"{expression} {(sortOrder == SortOrder.Descending ? "<=" : ">=")} {orderByItemToString}"); } else { //For a multi order by query // Suppose the query is SELECT* FROM c ORDER BY c.string ASC, c.number ASC // And we left off on partition N with the value("A", 1) // Then // All the partitions to the left will have finished reading("A", 1) // Partition N is still reading("A", 1) // All the partitions to the right have let to read a "(A", 1) // The filters are harder to derive since their are multiple columns // But the problem reduces to "How do you know one document comes after another in a multi order by query" // The answer is to just look at it one column at a time. // For this particular scenario: // If a first column is greater ex. ("B", blah), then the document comes later in the sort order // Therefore we want all documents where the first column is greater than "A" which means > "A" // Or if the first column is a tie, then you look at the second column ex. ("A", blah). // Therefore we also want all documents where the first column was a tie but the second column is greater which means = "A" AND > 1 // Therefore the filters should be // (> "A") OR (= "A" AND > 1), (> "A") OR (= "A" AND >= 1), (> "A") OR (= "A" AND >= 1) // Notice that if we repeated the same logic we for single order by we would have gotten // > "A" AND > 1, >= "A" AND >= 1, >= "A" AND >= 1 // which is wrong since we missed some documents // Repeat the same logic for ASC, DESC // (> "A") OR (= "A" AND < 1), (> "A") OR (= "A" AND <= 1), (> "A") OR (= "A" AND <= 1) // Again for DESC, ASC // (< "A") OR (= "A" AND > 1), (< "A") OR (= "A" AND >= 1), (< "A") OR (= "A" AND >= 1) // And again for DESC DESC // (< "A") OR (= "A" AND < 1), (< "A") OR (= "A" AND <= 1), (< "A") OR (= "A" AND <= 1) // The general we look at all prefixes of the order by columns to look for tie breakers. // Except for the full prefix whose last column follows the rules for single item order by // And then you just OR all the possibilities together for (int prefixLength = 1; prefixLength <= numOrderByItems; prefixLength++) { ArraySegment <string> expressionPrefix = new ArraySegment <string>(expressions, 0, prefixLength); ArraySegment <SortOrder> sortOrderPrefix = new ArraySegment <SortOrder>(sortOrders, 0, prefixLength); ArraySegment <CosmosElement> orderByItemsPrefix = new ArraySegment <CosmosElement>(orderByItems, 0, prefixLength); bool lastPrefix = prefixLength == numOrderByItems; bool firstPrefix = prefixLength == 1; CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, "("); for (int index = 0; index < prefixLength; index++) { string expression = expressionPrefix.ElementAt(index); SortOrder sortOrder = sortOrderPrefix.ElementAt(index); CosmosElement orderByItem = orderByItemsPrefix.ElementAt(index); bool lastItem = index == prefixLength - 1; // Append Expression CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, expression); CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " "); // Append binary operator if (lastItem) { string inequality = sortOrder == SortOrder.Descending ? "<" : ">"; CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, inequality); if (lastPrefix) { CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, string.Empty, "=", "="); } } else { CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, "="); } // Append SortOrder string orderByItemToString = JsonConvert.SerializeObject(orderByItem, DefaultJsonSerializationSettings.Value); CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " "); CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, orderByItemToString); CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " "); if (!lastItem) { CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, "AND "); } } CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, ")"); if (!lastPrefix) { CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, " OR "); } } } return(new Tuple <string, string, string>(left.ToString(), target.ToString(), right.ToString())); }
private static void AppendToBuilders(Tuple <StringBuilder, StringBuilder, StringBuilder> builders, object str) { CosmosOrderByItemQueryExecutionContext.AppendToBuilders(builders, str, str, str); }
private async Task <TryCatch <bool> > TryInitializeAsync( SqlQuerySpec sqlQuerySpec, string requestContinuation, string collectionRid, List <PartitionKeyRange> partitionKeyRanges, int initialPageSize, SortOrder[] sortOrders, string[] orderByExpressions, CancellationToken cancellationToken) { if (sqlQuerySpec == null) { throw new ArgumentNullException(nameof(sqlQuerySpec)); } if (collectionRid == null) { throw new ArgumentNullException(nameof(collectionRid)); } if (partitionKeyRanges == null) { throw new ArgumentNullException(nameof(partitionKeyRanges)); } if (sortOrders == null) { throw new ArgumentNullException(nameof(sortOrders)); } if (orderByExpressions == null) { throw new ArgumentNullException(nameof(orderByExpressions)); } cancellationToken.ThrowIfCancellationRequested(); if (requestContinuation == null) { SqlQuerySpec sqlQuerySpecForInit = new SqlQuerySpec( sqlQuerySpec.QueryText.Replace(oldValue: FormatPlaceHolder, newValue: True), sqlQuerySpec.Parameters); TryCatch <bool> tryInitialize = await base.TryInitializeAsync( collectionRid, partitionKeyRanges, initialPageSize, sqlQuerySpecForInit, cancellationToken : cancellationToken, targetRangeToContinuationMap : null, deferFirstPage : false, filter : null, tryFilterAsync : null); if (!tryInitialize.Succeeded) { return(tryInitialize); } } else { TryCatch <OrderByContinuationToken[]> tryExtractContinuationTokens = CosmosOrderByItemQueryExecutionContext.TryExtractContinuationTokens( requestContinuation, sortOrders, orderByExpressions); if (!tryExtractContinuationTokens.Succeeded) { return(TryCatch <bool> .FromException(tryExtractContinuationTokens.Exception)); } TryCatch <OrderByInitInfo> tryGetOrderByInitInfo = CosmosOrderByItemQueryExecutionContext.TryGetOrderByPartitionKeyRangesInitializationInfo( tryExtractContinuationTokens.Result, partitionKeyRanges, sortOrders, orderByExpressions); if (!tryGetOrderByInitInfo.Succeeded) { return(TryCatch <bool> .FromException(tryGetOrderByInitInfo.Exception)); } OrderByInitInfo initiaizationInfo = tryGetOrderByInitInfo.Result; RangeFilterInitializationInfo[] orderByInfos = initiaizationInfo.Filters; IReadOnlyDictionary <string, OrderByContinuationToken> targetRangeToOrderByContinuationMap = initiaizationInfo.ContinuationTokens; Debug.Assert( targetRangeToOrderByContinuationMap != null, "If targetRangeToOrderByContinuationMap can't be null is valid continuation is supplied"); // For ascending order-by, left of target partition has filter expression > value, // right of target partition has filter expression >= value, // and target partition takes the previous filter from continuation (or true if no continuation) foreach (RangeFilterInitializationInfo info in orderByInfos) { if (info.StartIndex > info.EndIndex) { continue; } PartialReadOnlyList <PartitionKeyRange> partialRanges = new PartialReadOnlyList <PartitionKeyRange>( partitionKeyRanges, info.StartIndex, info.EndIndex - info.StartIndex + 1); SqlQuerySpec sqlQuerySpecForInit = new SqlQuerySpec( sqlQuerySpec.QueryText.Replace(FormatPlaceHolder, info.Filter), sqlQuerySpec.Parameters); await base.TryInitializeAsync( collectionRid, partialRanges, initialPageSize, sqlQuerySpecForInit, targetRangeToOrderByContinuationMap.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.CompositeContinuationToken.Token), false, info.Filter, async (itemProducerTree) => { if (targetRangeToOrderByContinuationMap.TryGetValue( itemProducerTree.Root.PartitionKeyRange.Id, out OrderByContinuationToken continuationToken)) { TryCatch <bool> tryFilter = await this.TryFilterAsync( itemProducerTree, sortOrders, continuationToken, cancellationToken); if (!tryFilter.Succeeded) { return(tryFilter); } } return(TryCatch <bool> .FromResult(true)); }, cancellationToken); } } return(TryCatch <bool> .FromResult(true)); }