All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.cosmos.implementation.query.DefaultDocumentQueryExecutionContext Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.cosmos.implementation.query;

import com.azure.cosmos.implementation.caches.IPartitionKeyRangeCache;
import com.azure.cosmos.implementation.caches.RxCollectionCache;
import com.azure.cosmos.implementation.query.metrics.ClientSideMetrics;
import com.azure.cosmos.implementation.query.metrics.FetchExecutionRangeAccumulator;
import com.azure.cosmos.implementation.query.metrics.SchedulingStopwatch;
import com.azure.cosmos.implementation.query.metrics.SchedulingTimeSpan;
import com.azure.cosmos.implementation.routing.PartitionKeyInternal;
import com.azure.cosmos.implementation.routing.PartitionKeyRangeIdentity;
import com.azure.cosmos.implementation.routing.Range;
import com.azure.cosmos.BridgeInternal;
import com.azure.cosmos.FeedOptions;
import com.azure.cosmos.FeedResponse;
import com.azure.cosmos.Resource;
import com.azure.cosmos.SqlQuerySpec;
import com.azure.cosmos.implementation.BackoffRetryUtility;
import com.azure.cosmos.implementation.Constants;
import com.azure.cosmos.implementation.HttpConstants;
import com.azure.cosmos.implementation.IDocumentClientRetryPolicy;
import com.azure.cosmos.implementation.InvalidPartitionExceptionRetryPolicy;
import com.azure.cosmos.implementation.PartitionKeyRange;
import com.azure.cosmos.implementation.PartitionKeyRangeGoneRetryPolicy;
import com.azure.cosmos.implementation.PathsHelper;
import com.azure.cosmos.implementation.QueryMetrics;
import com.azure.cosmos.implementation.ResourceType;
import com.azure.cosmos.implementation.RxDocumentServiceRequest;
import com.azure.cosmos.implementation.Strings;
import com.azure.cosmos.implementation.Utils.ValueHolder;
import com.azure.cosmos.implementation.routing.RoutingMapProviderHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;

import static com.azure.cosmos.CommonsBridgeInternal.partitionKeyRangeIdInternal;

/**
 * While this class is public, but it is not part of our published public APIs.
 * This is meant to be internally used only by our sdk.
 */
public class DefaultDocumentQueryExecutionContext extends DocumentQueryExecutionContextBase {

    private boolean isContinuationExpected;
    private volatile int retries = -1;

    private final SchedulingStopwatch fetchSchedulingMetrics;
    private final FetchExecutionRangeAccumulator fetchExecutionRangeAccumulator;
    private static final String DEFAULT_PARTITION_KEY_RANGE_ID = "0";

    public DefaultDocumentQueryExecutionContext(IDocumentQueryClient client, ResourceType resourceTypeEnum,
            Class resourceType, SqlQuerySpec query, FeedOptions feedOptions, String resourceLink,
            UUID correlatedActivityId, boolean isContinuationExpected) {

        super(client,
                resourceTypeEnum,
                resourceType,
                query,
                feedOptions,
                resourceLink,
                false,
                correlatedActivityId);

        this.isContinuationExpected = isContinuationExpected;
        this.fetchSchedulingMetrics = new SchedulingStopwatch();
        this.fetchSchedulingMetrics.ready();
        this.fetchExecutionRangeAccumulator = new FetchExecutionRangeAccumulator(DEFAULT_PARTITION_KEY_RANGE_ID);
    }

    protected PartitionKeyInternal getPartitionKeyInternal() {
        return this.feedOptions.partitionKey() == null ? null : BridgeInternal.getPartitionKeyInternal(feedOptions.partitionKey());
    }

    @Override
    public Flux> executeAsync() {

        if (feedOptions == null) {
            feedOptions = new FeedOptions();
        }

        FeedOptions newFeedOptions = new FeedOptions(feedOptions);

        // We can not go to backend with the composite continuation token,
        // but we still need the gateway for the query plan.
        // The workaround is to try and parse the continuation token as a composite continuation token.
        // If it is, then we send the query to the gateway with max degree of parallelism to force getting back the query plan

        String originalContinuation = newFeedOptions.requestContinuation();

        if (isClientSideContinuationToken(originalContinuation)) {
            // At this point we know we want back a query plan
            newFeedOptions.requestContinuation(null);
            newFeedOptions.setMaxDegreeOfParallelism(Integer.MAX_VALUE);
        }

        int maxPageSize = newFeedOptions.maxItemCount() != null ? newFeedOptions.maxItemCount() : Constants.Properties.DEFAULT_MAX_PAGE_SIZE;

        BiFunction createRequestFunc = (continuationToken, pageSize) -> this.createRequestAsync(continuationToken, pageSize);

        // TODO: clean up if we want to use single vs observable.
        Function>> executeFunc = executeInternalAsyncFunc();

        return Paginator
    			.getPaginatedQueryResultAsObservable(newFeedOptions, createRequestFunc, executeFunc, resourceType, maxPageSize);
    }

    public Mono> getTargetPartitionKeyRanges(String resourceId, List> queryRanges) {
        return RoutingMapProviderHelper.getOverlappingRanges(client.getPartitionKeyRangeCache(), resourceId, queryRanges);
    }

    public Mono> getTargetPartitionKeyRangesById(String resourceId,
                                                                                      String partitionKeyRangeIdInternal) {
        return client.getPartitionKeyRangeCache()
                   .tryGetPartitionKeyRangeByIdAsync(resourceId,
                                                     partitionKeyRangeIdInternal,
                                                     false,
                                                     null)
                   .flatMap(partitionKeyRange -> Mono.just(Collections.singletonList(partitionKeyRange.v)));
    }

    protected Function>> executeInternalAsyncFunc() {
        RxCollectionCache collectionCache = this.client.getCollectionCache();
        IPartitionKeyRangeCache partitionKeyRangeCache =  this.client.getPartitionKeyRangeCache();
        IDocumentClientRetryPolicy retryPolicyInstance = this.client.getResetSessionTokenRetryPolicy().getRequestPolicy();

        retryPolicyInstance = new InvalidPartitionExceptionRetryPolicy(collectionCache, retryPolicyInstance, resourceLink, feedOptions);
        if (super.resourceTypeEnum.isPartitioned()) {
            retryPolicyInstance = new PartitionKeyRangeGoneRetryPolicy(
                    collectionCache,
                    partitionKeyRangeCache,
                    PathsHelper.getCollectionPath(super.resourceLink),
                    retryPolicyInstance,
                    feedOptions);
        }

        final IDocumentClientRetryPolicy finalRetryPolicyInstance = retryPolicyInstance;

        return req -> {
            finalRetryPolicyInstance.onBeforeSendRequest(req);
            this.fetchExecutionRangeAccumulator.beginFetchRange();
            this.fetchSchedulingMetrics.start();
            return BackoffRetryUtility.executeRetry(() -> {
                ++this.retries;
                return executeRequestAsync(req);
            }, finalRetryPolicyInstance).flux()
                    .map(tFeedResponse -> {
                        this.fetchSchedulingMetrics.stop();
                        this.fetchExecutionRangeAccumulator.endFetchRange(tFeedResponse.getActivityId(),
                                tFeedResponse.getResults().size(),
                                this.retries);
                        ImmutablePair schedulingTimeSpanMap =
                                new ImmutablePair<>(DEFAULT_PARTITION_KEY_RANGE_ID, this.fetchSchedulingMetrics.getElapsedTime());
                        if (!StringUtils.isEmpty(tFeedResponse.getResponseHeaders().get(HttpConstants.HttpHeaders.QUERY_METRICS))) {
                            QueryMetrics qm =
                                    BridgeInternal.createQueryMetricsFromDelimitedStringAndClientSideMetrics(tFeedResponse.getResponseHeaders()
                                                    .get(HttpConstants.HttpHeaders.QUERY_METRICS),
                                            new ClientSideMetrics(this.retries,
                                                    tFeedResponse.getRequestCharge(),
                                                    this.fetchExecutionRangeAccumulator.getExecutionRanges(),
                                                    Arrays.asList(schedulingTimeSpanMap)),
                                            tFeedResponse.getActivityId());
                            BridgeInternal.putQueryMetricsIntoMap(tFeedResponse, DEFAULT_PARTITION_KEY_RANGE_ID, qm);
                        }
                        return tFeedResponse;
                    });
        };
    }

    private Mono> executeOnceAsync(IDocumentClientRetryPolicy retryPolicyInstance, String continuationToken) {
        // Don't reuse request, as the rest of client SDK doesn't reuse requests between retries.
        // The code leaves some temporary garbage in request (in RequestContext etc.),
        // which shold be erased during retries.

        RxDocumentServiceRequest request = this.createRequestAsync(continuationToken, this.feedOptions.maxItemCount());
        if (retryPolicyInstance != null) {
            retryPolicyInstance.onBeforeSendRequest(request);
        }

        if (!Strings.isNullOrEmpty(request.getHeaders().get(HttpConstants.HttpHeaders.PARTITION_KEY))
                || !request.getResourceType().isPartitioned()) {
            return this.executeRequestAsync(request);
        }


        // TODO: remove this as partition key range id is not relevant
        // TODO; has to be rx async
        //CollectionCache collectionCache =  this.client.getCollectionCache();

        // TODO: has to be rx async
        //DocumentCollection collection =
        //        collectionCache.resolveCollection(request);

        // TODO: this code is not relevant because partition key range id should not be exposed
        //            if (!Strings.isNullOrEmpty(super.getPartitionKeyId()))
        //            {
        //                request.RouteTo(new PartitionKeyRangeIdentity(collection.ResourceId, base.PartitionKeyRangeId));
        //                return await this.ExecuteRequestAsync(request);
        //            }

        request.UseGatewayMode = true;
        return this.executeRequestAsync(request);
    }

    public RxDocumentServiceRequest createRequestAsync(String continuationToken, Integer maxPageSize) {

        // TODO this should be async
        Map requestHeaders = this.createCommonHeadersAsync(
                this.getFeedOptions(continuationToken, maxPageSize));

        // TODO: add support for simple continuation for single partition query
        //requestHeaders.put(keyHttpConstants.HttpHeaders.IsContinuationExpected, isContinuationExpected.ToString())

        RxDocumentServiceRequest request = this.createDocumentServiceRequest(
                requestHeaders,
                this.query,
                this.getPartitionKeyInternal());

        if (!StringUtils.isEmpty(partitionKeyRangeIdInternal(feedOptions))) {
            request.routeTo(new PartitionKeyRangeIdentity(partitionKeyRangeIdInternal(feedOptions)));
        }

        return request;
    }

    private static boolean isClientSideContinuationToken(String continuationToken) {
        if (continuationToken != null) {
            ValueHolder outCompositeContinuationToken = new ValueHolder();
            if (CompositeContinuationToken.tryParse(continuationToken, outCompositeContinuationToken)) {
                return true;
            }

            ValueHolder outOrderByContinuationToken = new ValueHolder();
            if (OrderByContinuationToken.tryParse(continuationToken, outOrderByContinuationToken)) {
                return true;
            }

            ValueHolder outTakeContinuationToken = new ValueHolder();
            if (TakeContinuationToken.tryParse(continuationToken, outTakeContinuationToken)) {
                return true;
            }
        }

        return false;
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy