Пример #1
0
        /// <summary>
        /// When resuming an order by query we need to filter the document producers.
        /// </summary>
        /// <param name="producer">The producer to filter down.</param>
        /// <param name="sortOrders">The sort orders.</param>
        /// <param name="continuationToken">The continuation token.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task to await on.</returns>
        private async Task FilterAsync(
            ItemProducerTree producer,
            SortOrder[] sortOrders,
            OrderByContinuationToken continuationToken,
            CancellationToken cancellationToken)
        {
            // When we resume a query on a partition there is a possibility that we only read a partial page from the backend
            // meaning that will we repeat some documents if we didn't do anything about it.
            // The solution is to filter all the documents that come before in the sort order, since we have already emitted them to the client.
            // The key is to seek until we get an order by value that matches the order by value we left off on.
            // Once we do that we need to seek to the correct _rid within the term,
            // since there might be many documents with the same order by value we left off on.

            foreach (ItemProducerTree tree in producer)
            {
                if (!ResourceId.TryParse(continuationToken.Rid, out ResourceId continuationRid))
                {
                    throw this.queryClient.CreateBadRequestException(
                              $"Invalid Rid in the continuation token {continuationToken.CompositeContinuationToken.Token} for OrderBy~Context.");
                }

                Dictionary <string, ResourceId> resourceIds = new Dictionary <string, ResourceId>();
                int  itemToSkip = continuationToken.SkipCount;
                bool continuationRidVerified = false;

                while (true)
                {
                    OrderByQueryResult orderByResult = new OrderByQueryResult(tree.Current);
                    // Throw away documents until it matches the item from the continuation token.
                    int cmp = 0;
                    for (int i = 0; i < sortOrders.Length; ++i)
                    {
                        cmp = ItemComparer.Instance.Compare(
                            continuationToken.OrderByItems[i].Item,
                            orderByResult.OrderByItems[i].Item);

                        if (cmp != 0)
                        {
                            cmp = sortOrders[i] != SortOrder.Descending ? cmp : -cmp;
                            break;
                        }
                    }

                    if (cmp < 0)
                    {
                        // We might have passed the item due to deletions and filters.
                        break;
                    }

                    if (cmp == 0)
                    {
                        if (!resourceIds.TryGetValue(orderByResult.Rid, out ResourceId rid))
                        {
                            if (!ResourceId.TryParse(orderByResult.Rid, out rid))
                            {
                                throw this.queryClient.CreateBadRequestException(
                                          message: $"Invalid Rid in the continuation token {continuationToken.CompositeContinuationToken.Token} for OrderBy~Context~TryParse.");
                            }

                            resourceIds.Add(orderByResult.Rid, rid);
                        }

                        if (!continuationRidVerified)
                        {
                            if (continuationRid.Database != rid.Database || continuationRid.DocumentCollection != rid.DocumentCollection)
                            {
                                throw this.queryClient.CreateBadRequestException(
                                          message: $"Invalid Rid in the continuation token {continuationToken.CompositeContinuationToken.Token} for OrderBy~Context.");
                            }

                            continuationRidVerified = true;
                        }

                        // Once the item matches the order by items from the continuation tokens
                        // We still need to remove all the documents that have a lower rid in the rid sort order.
                        // If there is a tie in the sort order the documents should be in _rid order in the same direction as the first order by field.
                        // So if it's ORDER BY c.age ASC, c.name DESC the _rids are ASC
                        // If ti's ORDER BY c.age DESC, c.name DESC the _rids are DESC
                        cmp = continuationRid.Document.CompareTo(rid.Document);
                        if (sortOrders[0] == SortOrder.Descending)
                        {
                            cmp = -cmp;
                        }

                        // We might have passed the item due to deletions and filters.
                        // We also have a skip count for JOINs
                        if (cmp < 0 || (cmp == 0 && itemToSkip-- <= 0))
                        {
                            break;
                        }
                    }

                    (bool successfullyMovedNext, QueryResponseCore? failureResponse)moveNextResponse = await tree.MoveNextAsync(cancellationToken);

                    if (!moveNextResponse.successfullyMovedNext)
                    {
                        if (moveNextResponse.failureResponse != null)
                        {
                            this.FailureResponse = moveNextResponse.failureResponse;
                        }

                        break;
                    }
                }
            }
        }
Пример #2
0
        public async Task TestGoneFromServiceScenarioAsync()
        {
            Mock <IHttpHandler> mockHttpHandler = new Mock <IHttpHandler>(MockBehavior.Strict);
            Uri endpoint = MockSetupsHelper.SetupSingleRegionAccount(
                "mockAccountInfo",
                consistencyLevel: ConsistencyLevel.Session,
                mockHttpHandler,
                out string primaryRegionEndpoint);

            string databaseName  = "mockDbName";
            string containerName = "mockContainerName";
            string containerRid  = "ccZ1ANCszwk=";

            Documents.ResourceId cRid = Documents.ResourceId.Parse(containerRid);
            MockSetupsHelper.SetupContainerProperties(
                mockHttpHandler: mockHttpHandler,
                regionEndpoint: primaryRegionEndpoint,
                databaseName: databaseName,
                containerName: containerName,
                containerRid: containerRid);

            MockSetupsHelper.SetupSinglePartitionKeyRange(
                mockHttpHandler,
                primaryRegionEndpoint,
                cRid,
                out IReadOnlyList <string> partitionKeyRanges);

            List <string> replicaIds1 = new List <string>()
            {
                "11111111111111111",
                "22222222222222222",
                "33333333333333333",
                "44444444444444444",
            };

            HttpResponseMessage replicaSet1 = MockSetupsHelper.CreateAddresses(
                replicaIds1,
                partitionKeyRanges.First(),
                "eastus",
                cRid);

            // One replica changed on the refresh
            List <string> replicaIds2 = new List <string>()
            {
                "11111111111111111",
                "22222222222222222",
                "33333333333333333",
                "55555555555555555",
            };

            HttpResponseMessage replicaSet2 = MockSetupsHelper.CreateAddresses(
                replicaIds2,
                partitionKeyRanges.First(),
                "eastus",
                cRid);

            bool delayCacheRefresh     = true;
            bool delayRefreshUnblocked = false;

            mockHttpHandler.SetupSequence(x => x.SendAsync(
                                              It.Is <HttpRequestMessage>(r => r.RequestUri.ToString().Contains("addresses")), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(replicaSet1))
            .Returns(async() =>
            {
                //block cache refresh to verify bad replica is not visited during refresh
                while (delayCacheRefresh)
                {
                    await Task.Delay(TimeSpan.FromMilliseconds(20));
                }

                delayRefreshUnblocked = true;
                return(replicaSet2);
            });

            int callBack = 0;
            List <Documents.TransportAddressUri> urisVisited         = new List <Documents.TransportAddressUri>();
            Mock <Documents.TransportClient>     mockTransportClient = new Mock <Documents.TransportClient>(MockBehavior.Strict);

            mockTransportClient.Setup(x => x.InvokeResourceOperationAsync(It.IsAny <Documents.TransportAddressUri>(), It.IsAny <Documents.DocumentServiceRequest>()))
            .Callback <Documents.TransportAddressUri, Documents.DocumentServiceRequest>((t, _) => urisVisited.Add(t))
            .Returns(() =>
            {
                callBack++;
                if (callBack == 1)
                {
                    throw Documents.Rntbd.TransportExceptions.GetGoneException(
                        new Uri("https://localhost:8081"),
                        Guid.NewGuid(),
                        new Documents.TransportException(Documents.TransportErrorCode.ConnectionBroken,
                                                         null,
                                                         Guid.NewGuid(),
                                                         new Uri("https://localhost:8081"),
                                                         "Mock",
                                                         userPayload: true,
                                                         payloadSent: false));
                }

                return(Task.FromResult(new Documents.StoreResponse()
                {
                    Status = 200,
                    Headers = new Documents.Collections.StoreResponseNameValueCollection()
                    {
                        ActivityId = Guid.NewGuid().ToString(),
                        LSN = "12345",
                        PartitionKeyRangeId = "0",
                        GlobalCommittedLSN = "12345",
                        SessionToken = "1#12345#1=12345"
                    },
                    ResponseBody = new MemoryStream()
                }));
            });

            CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
            {
                ConsistencyLevel              = Cosmos.ConsistencyLevel.Session,
                HttpClientFactory             = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)),
                TransportClientHandlerFactory = (original) => mockTransportClient.Object,
            };

            using (CosmosClient customClient = new CosmosClient(
                       endpoint.ToString(),
                       Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())),
                       cosmosClientOptions))
            {
                try
                {
                    Container container = customClient.GetContainer(databaseName, containerName);

                    for (int i = 0; i < 20; i++)
                    {
                        ResponseMessage response = await container.ReadItemStreamAsync(Guid.NewGuid().ToString(), new Cosmos.PartitionKey(Guid.NewGuid().ToString()));

                        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                    }

                    mockTransportClient.VerifyAll();
                    mockHttpHandler.VerifyAll();

                    Documents.TransportAddressUri failedReplica = urisVisited.First();
                    Assert.AreEqual(1, urisVisited.Count(x => x.Equals(failedReplica)));

                    urisVisited.Clear();
                    delayCacheRefresh = false;
                    do
                    {
                        await Task.Delay(TimeSpan.FromMilliseconds(100));
                    }while (!delayRefreshUnblocked);

                    for (int i = 0; i < 20; i++)
                    {
                        ResponseMessage response = await container.ReadItemStreamAsync(Guid.NewGuid().ToString(), new Cosmos.PartitionKey(Guid.NewGuid().ToString()));

                        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                    }

                    Assert.AreEqual(4, urisVisited.ToHashSet().Count());

                    // Clears all the setups. No network calls should be done on the next operation.
                    mockHttpHandler.Reset();
                    mockTransportClient.Reset();
                }
                finally
                {
                    mockTransportClient.Setup(x => x.Dispose());
                }
            }
        }