diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CosmosQueryExecutionContextFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CosmosQueryExecutionContextFactory.cs index feff750f35..5c97571e01 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CosmosQueryExecutionContextFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CosmosQueryExecutionContextFactory.cs @@ -131,6 +131,13 @@ private static async Task> TryCreateCoreContextAsy cancellationToken); cosmosQueryContext.ContainerResourceId = containerQueryProperties.ResourceId; + inputParameters.SqlQuerySpec.PassThrough = IsPassThroughCandidate(inputParameters, queryPlanFromContinuationToken); + + if (inputParameters.SqlQuerySpec.PassThrough) + { + //TODO: Add new pass through pipeline code here + } + PartitionedQueryExecutionInfo partitionedQueryExecutionInfo; if (inputParameters.ForcePassthrough) { @@ -232,7 +239,7 @@ private static async Task> TryCreateCoreContextAsy } } - return await TryCreateFromPartitionedQuerExecutionInfoAsync( + return await TryCreateFromPartitionedQueryExecutionInfoAsync( documentContainer, partitionedQueryExecutionInfo, containerQueryProperties, @@ -243,7 +250,7 @@ private static async Task> TryCreateCoreContextAsy } } - public static async Task> TryCreateFromPartitionedQuerExecutionInfoAsync( + public static async Task> TryCreateFromPartitionedQueryExecutionInfoAsync( DocumentContainer documentContainer, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo, ContainerQueryProperties containerQueryProperties, @@ -265,7 +272,7 @@ public static async Task> TryCreateFromPartitioned bool singleLogicalPartitionKeyQuery = inputParameters.PartitionKey.HasValue || ((partitionedQueryExecutionInfo.QueryRanges.Count == 1) - && partitionedQueryExecutionInfo.QueryRanges[0].IsSingleValue); + && partitionedQueryExecutionInfo.QueryRanges[0].IsSingleValue); bool serverStreamingQuery = !partitionedQueryExecutionInfo.QueryInfo.HasAggregates && !partitionedQueryExecutionInfo.QueryInfo.HasDistinct && !partitionedQueryExecutionInfo.QueryInfo.HasGroupBy; @@ -279,10 +286,19 @@ public static async Task> TryCreateFromPartitioned && !partitionedQueryExecutionInfo.QueryInfo.HasOffset; bool streamingCrossContinuationQuery = !singleLogicalPartitionKeyQuery && clientStreamingQuery; - bool createPassthoughQuery = streamingSinglePartitionQuery || streamingCrossContinuationQuery; - + bool createPassthroughQuery = streamingSinglePartitionQuery || streamingCrossContinuationQuery; + TryCatch tryCreatePipelineStage; - if (createPassthoughQuery) + + // After getting the Query Plan if we find out that the query is single logical partition, then short circuit and send straight to Backend + inputParameters.SqlQuerySpec.PassThrough = IsPassThroughCandidate(inputParameters, partitionedQueryExecutionInfo); + + if (inputParameters.SqlQuerySpec.PassThrough) + { + //TODO: Add new pass through pipeline code here + } + + if (createPassthroughQuery) { TestInjections.ResponseStats responseStats = inputParameters?.TestInjections?.Stats; if (responseStats != null) @@ -532,6 +548,29 @@ private static Documents.PartitionKeyDefinition GetPartitionKeyDefinition(InputP return partitionKeyDefinition; } + private static bool IsPassThroughCandidate(InputParameters inputParameters, PartitionedQueryExecutionInfo partitionedQueryExecutionInfo) + { + // case 1: Is query going to a single partition + bool hasPartitionKey = inputParameters.PartitionKey.HasValue + && inputParameters.PartitionKey != PartitionKey.Null + && inputParameters.PartitionKey != PartitionKey.None; + + if (hasPartitionKey) return true; + + // case 2: does query execution plan have a single query range + if (partitionedQueryExecutionInfo != null) + { + bool hasQueryRanges = (partitionedQueryExecutionInfo.QueryRanges.Count == 1) + && partitionedQueryExecutionInfo.QueryRanges[0].IsSingleValue; + + if (hasQueryRanges) return hasQueryRanges; + } + + // TODO: case 3: does collection have only one physical partition + + return false; + } + public sealed class InputParameters { private const int DefaultMaxConcurrency = 0; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/SqlQuerySpec.cs b/Microsoft.Azure.Cosmos/src/Query/Core/SqlQuerySpec.cs index fe51d3b3b0..81a773acfd 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/SqlQuerySpec.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/SqlQuerySpec.cs @@ -14,7 +14,8 @@ namespace Microsoft.Azure.Cosmos.Query.Core internal sealed class SqlQuerySpec { private SqlParameterCollection parameters; - + private bool passThrough; + /// /// Initializes a new instance of the class for the Azure Cosmos DB service. /// @@ -40,11 +41,23 @@ public SqlQuerySpec(string queryText) /// The text of the database query. /// The instance, which represents the collection of query parameters. public SqlQuerySpec(string queryText, SqlParameterCollection parameters) + : this(queryText, parameters, false) + { + } + + /// + /// Initializes a new instance of the class for the Azure Cosmos DB service. + /// + /// The text of the database query. + /// The instance, which represents the collection of query parameters. + /// A Boolean value that indicates whether the query should be executed as pass-through. + public SqlQuerySpec(string queryText, SqlParameterCollection parameters, bool passThrough) { this.QueryText = queryText; this.parameters = parameters ?? throw new ArgumentNullException("parameters"); + this.passThrough = passThrough; } - + /// /// Gets or sets the text of the Azure Cosmos DB database query. /// @@ -69,6 +82,23 @@ public SqlParameterCollection Parameters } } + /// + /// Gets or sets the boolean value that indicates whether the query should be executed as pass-through.. + /// + /// A boolean value that indicates whether the query should be executed as pass-through. + [DataMember(Name = "passThrough")] + public bool PassThrough + { + get + { + return this.passThrough; + } + set + { + this.passThrough = value; + } + } + /// /// Returns a value that indicates whether the Azure Cosmos DB database property should be serialized. /// @@ -76,5 +106,13 @@ public bool ShouldSerializeParameters() { return this.parameters.Count > 0; } + + /// + /// Returns a value that indicates whether the Azure Cosmos DB database property should be serialized. + /// + public bool ShouldSerializePassThrough() + { + return this.passThrough; + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTests.cs index 0b2d129247..6eba048998 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTests.cs @@ -393,6 +393,24 @@ public void SqlQuerySpecSerializationTest() QueryText = "SELECT 1", Parameters = new SqlParameterCollection() { new SqlParameter("@p1", new JRaw("{\"a\":[1,2,3]}")) } }); + verifyJsonSerialization("{\"query\":\"SELECT 1\",\"parameters\":[" + + "{\"name\":\"@p1\",\"value\":{\"a\":[1,2,3]}}" + + "]}", + new SqlQuerySpec() + { + QueryText = "SELECT 1", + Parameters = new SqlParameterCollection() { new SqlParameter("@p1", new JRaw("{\"a\":[1,2,3]}")) }, + PassThrough = false + }); + verifyJsonSerialization("{\"query\":\"SELECT 1\",\"parameters\":[" + + "{\"name\":\"@p1\",\"value\":{\"a\":[1,2,3]}}" + + "]," + "\"passThrough\":true}", + new SqlQuerySpec() + { + QueryText = "SELECT 1", + Parameters = new SqlParameterCollection() { new SqlParameter("@p1", new JRaw("{\"a\":[1,2,3]}")) }, + PassThrough = true + }); // Verify roundtrips verifyJsonSerializationText("{\"query\":null}"); @@ -416,7 +434,7 @@ public void SqlQuerySpecSerializationTest() "\"query\":\"SELECT 1\"," + "\"parameters\":[" + "{\"name\":\"@p1\",\"value\":true}" + - "]" + + "]" + "}"); verifyJsonSerializationText( "{" + @@ -446,6 +464,13 @@ public void SqlQuerySpecSerializationTest() "{\"name\":\"@p1\",\"value\":{\"a\":[1,2,\"abc\"]}}" + "]" + "}"); + verifyJsonSerializationText( + "{" + + "\"query\":\"SELECT 1\"," + + "\"parameters\":[" + + "{\"name\":\"@p1\",\"value\":{\"a\":[1,2,\"abc\"]}}" + + "]," + "\"passThrough\":true" + + "}"); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/PassThroughQueryBaselineTests.NegativePassThroughOutput.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/PassThroughQueryBaselineTests.NegativePassThroughOutput.xml new file mode 100644 index 0000000000..e7e4d603fc --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/PassThroughQueryBaselineTests.NegativePassThroughOutput.xml @@ -0,0 +1,41 @@ + + + + Null Partition Key Value + SELECT * FROM c + + /pk + + Hash + + + false + + + + + None Partition Key Value + SELECT * FROM c + + /pk + + Hash + + + false + + + + + C# Null Partition Key Value + SELECT * FROM c + + /pk + + Hash + + + false + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/PassThroughQueryBaselineTests.PositivePassThroughOutput.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/PassThroughQueryBaselineTests.PositivePassThroughOutput.xml new file mode 100644 index 0000000000..94ed03c8b9 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/PassThroughQueryBaselineTests.PositivePassThroughOutput.xml @@ -0,0 +1,41 @@ + + + + Partition Key + Value and Distinct + SELECT DISTINCT c.key FROM c + + /pk + + Hash + + + true + + + + + Partition Key + Value and Min Aggregate + SELECT VALUE MIN(c.key) FROM c + + /pk + + Hash + + + true + + + + + Partition Key + Value Fields + SELECT c.key FROM c + + /pk + + Hash + + + true + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj index 6bae5a26e0..b55ed001ec 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj @@ -228,6 +228,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + Always diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/PassThroughQueryBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/PassThroughQueryBaselineTests.cs new file mode 100644 index 0000000000..3f1ffd6e87 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/PassThroughQueryBaselineTests.cs @@ -0,0 +1,339 @@ +namespace Microsoft.Azure.Cosmos.Tests.Query +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Xml; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Routing; + using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Test.BaselineTest; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; + using Microsoft.Azure.Cosmos.Query.Core.Monads; + using Microsoft.Azure.Cosmos.Query.Core.Pipeline.Aggregate; + using Microsoft.Azure.Cosmos.Query.Core.Pipeline.CrossPartition.OrderBy; + using Microsoft.Azure.Cosmos.Query.Core.Pipeline.Distinct; + using Microsoft.Azure.Cosmos.Query.Core.ExecutionContext; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Pagination; + using Microsoft.Azure.Cosmos.Tests.Pagination; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Moq; + using Microsoft.Azure.Cosmos.Query; + using Microsoft.Azure.Cosmos.Query.Core.Pipeline; + using Microsoft.Azure.Cosmos.Routing; + using System.Threading; + using System.Linq; + using Microsoft.Azure.Cosmos.Query.Core.Pipeline.Pagination; + + [TestClass] + public class PassThroughQueryBaselineTests : BaselineTests + { + [TestMethod] + [Owner("akotalwar")] + public void PositivePassThroughOutput() + { + List testVariations = new List + { + CreateInput( + @"Partition Key + Value and Distinct", + "SELECT DISTINCT c.key FROM c", + true, + @"/pk", + @"/value"), + + CreateInput( + @"Partition Key + Value and Min Aggregate", + "SELECT VALUE MIN(c.key) FROM c", + true, + @"/pk", + @"/value"), + + CreateInput( + @"Partition Key + Value Fields", + "SELECT c.key FROM c", + true, + @"/pk", + @"/value"), + }; + this.ExecuteTestSuite(testVariations); + } + + [TestMethod] + [Owner("akotalwar")] + public void NegativePassThroughOutput() + { + List testVariations = new List + { + CreateInput( + @"Null Partition Key Value", + "SELECT * FROM c", + false, + @"/pk", + Cosmos.PartitionKey.Null), + + CreateInput( + @"None Partition Key Value", + "SELECT * FROM c", + false, + @"/pk", + Cosmos.PartitionKey.None), + + CreateInput( + @"C# Null Partition Key Value", + "SELECT * FROM c", + false, + @"/pk", + null), + }; + this.ExecuteTestSuite(testVariations); + } + + private static PassThroughQueryTestInput CreateInput( + string description, + string query, + bool expectedPassThrough, + string partitionKeyPath, + string partitionKeyValue) + { + PartitionKeyBuilder pkBuilder = new PartitionKeyBuilder(); + pkBuilder.Add(partitionKeyValue); + + return CreateInput(description, query, expectedPassThrough, partitionKeyPath, pkBuilder.Build()); + } + + private static PassThroughQueryTestInput CreateInput( + string description, + string query, + bool expectedPassThrough, + string partitionKeyPath, + Cosmos.PartitionKey partitionKeyValue) + { + return new PassThroughQueryTestInput(description, query, new SqlQuerySpec(query), expectedPassThrough, partitionKeyPath, partitionKeyValue); + } + + private static PartitionedQueryExecutionInfo GetPartitionedQueryExecutionInfo(SqlQuerySpec sqlQuerySpec, PartitionKeyDefinition pkDefinition) + { + TryCatch tryGetQueryPlan = QueryPartitionProviderTestInstance.Object.TryGetPartitionedQueryExecutionInfo( + querySpec: sqlQuerySpec, + partitionKeyDefinition: pkDefinition, + requireFormattableOrderByQuery: true, + isContinuationExpected: false, + allowNonValueAggregateQuery: true, + hasLogicalPartitionKey: false, + allowDCount: true); + + return tryGetQueryPlan.Result; + } + + public override PassThroughQueryTestOutput ExecuteTest(PassThroughQueryTestInput input) + { + // gets DocumentContainer + IMonadicDocumentContainer monadicDocumentContainer = new InMemoryContainer(input.PartitionKeyDefinition); + DocumentContainer documentContainer = new DocumentContainer(monadicDocumentContainer); + + SqlQuerySpec sqlQuerySpec = new SqlQuerySpec(input.Query); + + // gets query context + string databaseId = "db1234"; + string resourceLink = string.Format("dbs/{0}/colls", databaseId); + CosmosQueryContextCore cosmosQueryContextCore = new CosmosQueryContextCore( + client: new TestCosmosQueryClient(), + resourceTypeEnum: Documents.ResourceType.Document, + operationType: Documents.OperationType.Query, + resourceType: typeof(QueryResponseCore), + resourceLink: resourceLink, + isContinuationExpected: false, + allowNonValueAggregateQuery: true, + correlatedActivityId: Guid.NewGuid()); + + // gets input parameters + QueryRequestOptions queryRequestOptions = new QueryRequestOptions(); + PartitionedQueryExecutionInfo partitionedQueryExecutionInfo = GetPartitionedQueryExecutionInfo(sqlQuerySpec, input.PartitionKeyDefinition); + if (input.PartitionKeyValue == null || input.PartitionKeyValue == Cosmos.PartitionKey.None) + { + input.PartitionKeyValue = Cosmos.PartitionKey.Null; + } + + CosmosQueryExecutionContextFactory.InputParameters inputParameters = new CosmosQueryExecutionContextFactory.InputParameters( + sqlQuerySpec: sqlQuerySpec, + initialUserContinuationToken: null, + initialFeedRange: null, + maxConcurrency: queryRequestOptions.MaxConcurrency, + maxItemCount: queryRequestOptions.MaxItemCount, + maxBufferedItemCount: queryRequestOptions.MaxBufferedItemCount, + partitionKey: input.PartitionKeyValue, + properties: queryRequestOptions.Properties, + partitionedQueryExecutionInfo: partitionedQueryExecutionInfo, + executionEnvironment: null, + returnResultsInDeterministicOrder: null, + forcePassthrough: true, + testInjections: null); + + IQueryPipelineStage queryPipelineStage = CosmosQueryExecutionContextFactory.Create( + documentContainer, + cosmosQueryContextCore, + inputParameters, + NoOpTrace.Singleton); + bool result = queryPipelineStage.MoveNextAsync(NoOpTrace.Singleton).Result; + + Assert.AreEqual(input.ExpectedPassThrough, inputParameters.SqlQuerySpec.PassThrough); + Assert.IsNotNull(queryPipelineStage); + Assert.IsTrue(result); + + return new PassThroughQueryTestOutput(inputParameters.SqlQuerySpec.PassThrough); + } + } + + public sealed class PassThroughQueryTestOutput : BaselineTestOutput + { + public PassThroughQueryTestOutput(bool executeAsPassThrough) + { + this.ExecuteAsPassThrough = executeAsPassThrough; + } + + public bool ExecuteAsPassThrough { get; } + + public override void SerializeAsXml(XmlWriter xmlWriter) + { + xmlWriter.WriteStartElement(nameof(this.ExecuteAsPassThrough)); + xmlWriter.WriteValue(this.ExecuteAsPassThrough); + xmlWriter.WriteEndElement(); + } + } + + public sealed class PassThroughQueryTestInput : BaselineTestInput + { + internal PartitionKeyDefinition PartitionKeyDefinition { get; set; } + internal SqlQuerySpec SqlQuerySpec { get; set; } + internal Cosmos.PartitionKey PartitionKeyValue { get; set; } + internal bool ExpectedPassThrough { get; set; } + internal PartitionKeyRangeIdentity PartitionKeyRangeId { get; set; } + internal string Query { get; set; } + + internal PassThroughQueryTestInput( + string description, + string query, + SqlQuerySpec sqlQuerySpec, + bool expectedPassThrough, + string partitionKeyPath, + Cosmos.PartitionKey partitionKeyValue) + : base(description) + { + this.PartitionKeyDefinition = new PartitionKeyDefinition() + { + Paths = new Collection() + { + partitionKeyPath + }, + Kind = PartitionKind.Hash, + Version = PartitionKeyDefinitionVersion.V2, + }; + this.SqlQuerySpec = sqlQuerySpec; + this.ExpectedPassThrough = expectedPassThrough; + this.Query = query; + this.PartitionKeyValue = partitionKeyValue; + } + + public override void SerializeAsXml(XmlWriter xmlWriter) + { + xmlWriter.WriteElementString("Description", this.Description); + xmlWriter.WriteElementString("Query", this.SqlQuerySpec.QueryText); + xmlWriter.WriteStartElement("PartitionKeys"); + if (this.PartitionKeyDefinition != null) + { + foreach (string path in this.PartitionKeyDefinition.Paths) + { + xmlWriter.WriteElementString("Key", path); + } + } + + xmlWriter.WriteEndElement(); + if (this.PartitionKeyDefinition != null) + { + xmlWriter.WriteElementString( + "PartitionKeyType", + this.PartitionKeyDefinition.Kind == PartitionKind.Hash ? "Hash" : ( + this.PartitionKeyDefinition.Kind == PartitionKind.MultiHash ? "MultiHash" : "Range")); + } + + if (this.SqlQuerySpec.ShouldSerializeParameters()) + { + xmlWriter.WriteStartElement("QueryParameters"); + xmlWriter.WriteCData(JsonConvert.SerializeObject( + this.SqlQuerySpec.Parameters, + Newtonsoft.Json.Formatting.Indented)); + xmlWriter.WriteEndElement(); + } + } + } + + internal class TestCosmosQueryClient : CosmosQueryClient + { + public override Action OnExecuteScalarQueryCallback => throw new NotImplementedException(); + + public override bool ByPassQueryParsing() + { + throw new NotImplementedException(); + } + + public override void ClearSessionTokenCache(string collectionFullName) + { + throw new NotImplementedException(); + } + + public override Task> ExecuteItemQueryAsync(string resourceUri, ResourceType resourceType, OperationType operationType, Guid clientQueryCorrelationId, Cosmos.FeedRange feedRange, QueryRequestOptions requestOptions, SqlQuerySpec sqlQuerySpec, string continuationToken, bool isContinuationExpected, int pageSize, ITrace trace, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override Task ExecuteQueryPlanRequestAsync(string resourceUri, ResourceType resourceType, OperationType operationType, SqlQuerySpec sqlQuerySpec, Cosmos.PartitionKey? partitionKey, string supportedQueryFeatures, Guid clientQueryCorrelationId, ITrace trace, CancellationToken cancellationToken) + { + return Task.FromResult(new PartitionedQueryExecutionInfo()); + } + + public override Task ForceRefreshCollectionCacheAsync(string collectionLink, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override Task GetCachedContainerQueryPropertiesAsync(string containerLink, Cosmos.PartitionKey? partitionKey, ITrace trace, CancellationToken cancellationToken) + { + return Task.FromResult(new ContainerQueryProperties()); + } + + public override Task> GetTargetPartitionKeyRangeByFeedRangeAsync(string resourceLink, string collectionResourceId, PartitionKeyDefinition partitionKeyDefinition, FeedRangeInternal feedRangeInternal, bool forceRefresh, ITrace trace) + { + throw new NotImplementedException(); + } + + public override Task> GetTargetPartitionKeyRangesAsync(string resourceLink, string collectionResourceId, List> providedRanges, bool forceRefresh, ITrace trace) + { + return Task.FromResult(new List{new PartitionKeyRange() + { + MinInclusive = PartitionKeyHash.V2.Hash("abc").ToString(), + MaxExclusive = PartitionKeyHash.V2.Hash("def").ToString() + } + }); + } + + public override Task> GetTargetPartitionKeyRangesByEpkStringAsync(string resourceLink, string collectionResourceId, string effectivePartitionKeyString, bool forceRefresh, ITrace trace) + { + throw new NotImplementedException(); + } + + public override Task> TryGetOverlappingRangesAsync(string collectionResourceId, Range range, bool forceRefresh = false) + { + throw new NotImplementedException(); + } + + public override Task> TryGetPartitionedQueryExecutionInfoAsync(SqlQuerySpec sqlQuerySpec, PartitionKeyDefinition partitionKeyDefinition, bool requireFormattableOrderByQuery, bool isContinuationExpected, bool allowNonValueAggregateQuery, bool hasLogicalPartitionKey, bool allowDCount, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +}