From c05f8c8c558fe8f13b7f6fceb78f6ec42e02eac3 Mon Sep 17 00:00:00 2001 From: DmitriMelnikov <53013944+DmitriMelnikov@users.noreply.github.com> Date: Fri, 11 Sep 2020 17:18:19 -0700 Subject: [PATCH] Internal QueryPartitionProvider: Adds Singleton QueryPartitionProvider (#1842) * Shared QueryPartitionProvider. Original version creates and initializes one provider per query request. * Reduce number of array allocations * Allocate on heap for a larger arrays * Make the call to check if client available * Make property that returns provider to return the task * Return ReturnsAsync to touch less files * DocumentClient unit test for validating QueryPartitionProvider is singleton Co-authored-by: REDMOND\dmmelnik Co-authored-by: j82w --- Microsoft.Azure.Cosmos/src/DocumentClient.cs | 19 +++++++--- .../Core/QueryPlan/QueryPartitionProvider.cs | 36 +++++++++++++------ .../Query/Core/QueryPlan/QueryPlanHandler.cs | 6 ++-- .../DefaultDocumentQueryExecutionContext.cs | 3 +- .../src/Query/v2Query/DocumentQueryClient.cs | 24 ++----------- .../DocumentQueryExecutionContextBase.cs | 2 +- .../src/Query/v2Query/IDocumentQueryClient.cs | 2 +- .../Query/v3Query/CosmosQueryClientCore.cs | 22 +----------- .../src/Routing/DocumentAnalyzer.cs | 11 +++--- .../src/Routing/PartitionRoutingHelper.cs | 1 - .../src/Routing/PathParser.cs | 4 +-- .../DocumentClientUnitTests.cs | 17 +++++++++ .../Utils/MockDocumentClient.cs | 10 ++++-- 13 files changed, 80 insertions(+), 77 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index a2dd628c51..e45a7fd010 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -21,6 +21,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Common; using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Query; + using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -166,6 +167,7 @@ internal partial class DocumentClient : IDisposable, IAuthorizationTokenProvider private readonly bool hasAuthKeyResourceToken; private readonly string authKeyResourceToken = string.Empty; + private AsyncLazy queryPartitionProvider; private DocumentClientEventSource eventSource; internal Task initializeTask; @@ -797,6 +799,12 @@ internal virtual void Initialize(Uri serviceEndpoint, DefaultTrace.InitEventListener(); + this.queryPartitionProvider = new AsyncLazy(async () => + { + await this.EnsureValidClientAsync(); + return new QueryPartitionProvider(this.accountServiceConfiguration.QueryEngineConfiguration); + }, CancellationToken.None); + #if !(NETSTANDARD15 || NETSTANDARD16) #if NETSTANDARD20 // GetEntryAssembly returns null when loaded from native netstandard2.0 @@ -1401,6 +1409,11 @@ public void Dispose() this.GlobalEndpointManager = null; } + if (this.queryPartitionProvider.IsValueCreated) + { + this.queryPartitionProvider.Value.Dispose(); + } + DefaultTrace.TraceInformation("DocumentClient with id {0} disposed.", this.traceId); DefaultTrace.Flush(); @@ -1446,11 +1459,7 @@ public void Dispose() /// internal Action OnExecuteScalarQueryCallback { get; set; } - internal virtual async Task> GetQueryEngineConfigurationAsync() - { - await this.EnsureValidClientAsync(); - return this.accountServiceConfiguration.QueryEngineConfiguration; - } + internal virtual Task QueryPartitionProvider => this.queryPartitionProvider.Value; internal virtual async Task GetDefaultConsistencyLevelAsync() { diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPartitionProvider.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPartitionProvider.cs index 621a8f33f8..35da857819 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPartitionProvider.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPartitionProvider.cs @@ -166,21 +166,33 @@ internal TryCatch TryGetPartitionedQueryE string queryText = JsonConvert.SerializeObject(querySpec); List paths = new List(partitionKeyDefinition.Paths); + List> pathPartsList = new List>(paths.Count); + uint[] partsLengths = new uint[paths.Count]; + int allPartsLength = 0; - List pathParts = new List(); - paths.ForEach(path => - { - pathParts.Add(PathParser.GetPathParts(path)); - }); + for (int i = 0; i < paths.Count; i++) + { + IReadOnlyList pathParts = PathParser.GetPathParts(paths[i]); + partsLengths[i] = (uint)pathParts.Count; + pathPartsList.Add(pathParts); + allPartsLength += pathParts.Count; + } - string[] allParts = pathParts.SelectMany(parts => parts).ToArray(); - uint[] partsLengths = pathParts.Select(parts => (uint)parts.Length).ToArray(); + string[] allParts = new string[allPartsLength]; + int allPartsIndex = 0; + foreach (IReadOnlyList pathParts in pathPartsList) + { + foreach (string part in pathParts) + { + allParts[allPartsIndex++] = part; + } + } PartitionKind partitionKind = partitionKeyDefinition.Kind; this.Initialize(); - byte[] buffer = new byte[InitialBufferSize]; + Span buffer = stackalloc byte[QueryPartitionProvider.InitialBufferSize]; uint errorCode; uint serializedQueryExecutionInfoResultLength; @@ -205,7 +217,11 @@ internal TryCatch TryGetPartitionedQueryE if (errorCode == DISP_E_BUFFERTOOSMALL) { - buffer = new byte[serializedQueryExecutionInfoResultLength]; + // Allocate on stack for smaller arrays, otherwise use heap. + buffer = serializedQueryExecutionInfoResultLength < 4096 + ? stackalloc byte[(int)serializedQueryExecutionInfoResultLength] + : new byte[serializedQueryExecutionInfoResultLength]; + fixed (byte* bytePtr2 = buffer) { errorCode = ServiceInteropWrapper.GetPartitionKeyRangesFromQuery( @@ -227,7 +243,7 @@ internal TryCatch TryGetPartitionedQueryE } } - string serializedQueryExecutionInfo = Encoding.UTF8.GetString(buffer, 0, (int)serializedQueryExecutionInfoResultLength); + string serializedQueryExecutionInfo = Encoding.UTF8.GetString(buffer.Slice(0, (int)serializedQueryExecutionInfoResultLength)); Exception exception = Marshal.GetExceptionForHR((int)errorCode); if (exception != null) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanHandler.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanHandler.cs index 0ab8737eee..d55c886113 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanHandler.cs @@ -100,7 +100,7 @@ public async Task> TryGetQueryPlanAsync( return TryCatch<(PartitionedQueryExecutionInfo, bool)>.FromResult((tryGetQueryInfo.Result, neededQueryFeatures == QueryFeatures.None)); } - private async Task> TryGetQueryInfoAsync( + private Task> TryGetQueryInfoAsync( SqlQuerySpec sqlQuerySpec, PartitionKeyDefinition partitionKeyDefinition, bool hasLogicalPartitionKey, @@ -108,7 +108,7 @@ private async Task> TryGetQueryInfoAsync { cancellationToken.ThrowIfCancellationRequested(); - TryCatch tryGetPartitoinedQueryExecutionInfo = await this.queryClient.TryGetPartitionedQueryExecutionInfoAsync( + return this.queryClient.TryGetPartitionedQueryExecutionInfoAsync( sqlQuerySpec: sqlQuerySpec, partitionKeyDefinition: partitionKeyDefinition, requireFormattableOrderByQuery: true, @@ -116,8 +116,6 @@ private async Task> TryGetQueryInfoAsync allowNonValueAggregateQuery: true, hasLogicalPartitionKey: hasLogicalPartitionKey, cancellationToken: cancellationToken); - - return tryGetPartitoinedQueryExecutionInfo; } private static class QueryPlanSupportChecker diff --git a/Microsoft.Azure.Cosmos/src/Query/v2Query/DefaultDocumentQueryExecutionContext.cs b/Microsoft.Azure.Cosmos/src/Query/v2Query/DefaultDocumentQueryExecutionContext.cs index 76ba25382e..66426a9121 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v2Query/DefaultDocumentQueryExecutionContext.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v2Query/DefaultDocumentQueryExecutionContext.cs @@ -160,7 +160,7 @@ private async Task, string>> ExecuteOn // Get the routing map provider CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync(); ContainerProperties collection = await collectionCache.ResolveCollectionAsync(request, CancellationToken.None); - QueryPartitionProvider queryPartitionProvider = await this.Client.GetQueryPartitionProviderAsync(cancellationToken); + QueryPartitionProvider queryPartitionProvider = await this.Client.GetQueryPartitionProviderAsync(); IRoutingMapProvider routingMapProvider = await this.Client.GetRoutingMapProviderAsync(); // Figure out what partition you are going to based on the range from the continuation token @@ -306,7 +306,6 @@ private static bool ServiceInteropAvailable() } providedRanges = PartitionRoutingHelper.GetProvidedPartitionKeyRanges( - (errorMessage) => new BadRequestException(errorMessage), this.QuerySpec, enableCrossPartitionQuery, false, diff --git a/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryClient.cs b/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryClient.cs index 3f26ede9b1..22d7af54fc 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryClient.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryClient.cs @@ -17,8 +17,6 @@ namespace Microsoft.Azure.Cosmos.Query internal sealed class DocumentQueryClient : IDocumentQueryClient { private readonly DocumentClient innerClient; - private readonly SemaphoreSlim semaphore; - private QueryPartitionProvider queryPartitionProvider; public DocumentQueryClient(DocumentClient innerClient) { @@ -28,16 +26,11 @@ public DocumentQueryClient(DocumentClient innerClient) } this.innerClient = innerClient; - this.semaphore = new SemaphoreSlim(1, 1); } public void Dispose() { this.innerClient.Dispose(); - if (this.queryPartitionProvider != null) - { - this.queryPartitionProvider.Dispose(); - } } QueryCompatibilityMode IDocumentQueryClient.QueryCompatibilityMode @@ -92,22 +85,9 @@ async Task IDocumentQueryClient.GetRoutingMapProviderAsync( return await this.innerClient.GetPartitionKeyRangeCacheAsync(); } - public async Task GetQueryPartitionProviderAsync(CancellationToken cancellationToken) + public Task GetQueryPartitionProviderAsync() { - if (this.queryPartitionProvider == null) - { - await this.semaphore.WaitAsync(cancellationToken); - - if (this.queryPartitionProvider == null) - { - cancellationToken.ThrowIfCancellationRequested(); - this.queryPartitionProvider = new QueryPartitionProvider(await this.innerClient.GetQueryEngineConfigurationAsync()); - } - - this.semaphore.Release(); - } - - return this.queryPartitionProvider; + return this.innerClient.QueryPartitionProvider; } public Task ExecuteQueryAsync(DocumentServiceRequest request, IDocumentClientRetryPolicy retryPolicyInstance, CancellationToken cancellationToken) diff --git a/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextBase.cs b/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextBase.cs index deae8a40cf..cb0cead5ad 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextBase.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextBase.cs @@ -163,7 +163,7 @@ public async Task GetPartitionedQueryExecutionInf cancellationToken.ThrowIfCancellationRequested(); // $ISSUE-felixfan-2016-07-13: We should probably get PartitionedQueryExecutionInfo from Gateway in GatewayMode - QueryPartitionProvider queryPartitionProvider = await this.Client.GetQueryPartitionProviderAsync(cancellationToken); + QueryPartitionProvider queryPartitionProvider = await this.Client.GetQueryPartitionProviderAsync(); TryCatch tryGetPartitionedQueryExecutionInfo = queryPartitionProvider.TryGetPartitionedQueryExecutionInfo( this.QuerySpec, partitionKeyDefinition, diff --git a/Microsoft.Azure.Cosmos/src/Query/v2Query/IDocumentQueryClient.cs b/Microsoft.Azure.Cosmos/src/Query/v2Query/IDocumentQueryClient.cs index af761b158d..21ab01570e 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v2Query/IDocumentQueryClient.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v2Query/IDocumentQueryClient.cs @@ -29,7 +29,7 @@ internal interface IDocumentQueryClient : IDisposable Task GetRoutingMapProviderAsync(); - Task GetQueryPartitionProviderAsync(CancellationToken cancellationToken); + Task GetQueryPartitionProviderAsync(); Task ExecuteQueryAsync(DocumentServiceRequest request, IDocumentClientRetryPolicy retryPolicyInstance, CancellationToken cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs index 1d639d11a0..33e132ef2f 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs @@ -33,7 +33,6 @@ internal class CosmosQueryClientCore : CosmosQueryClient private readonly ContainerInternal cosmosContainerCore; private readonly DocumentClient documentClient; private readonly SemaphoreSlim semaphore; - private QueryPartitionProvider queryPartitionProvider; public CosmosQueryClientCore( CosmosClientContext clientContext, @@ -88,26 +87,7 @@ public override async Task> TryGetPartit bool hasLogicalPartitionKey, CancellationToken cancellationToken) { - if (this.queryPartitionProvider == null) - { - try - { - await this.semaphore.WaitAsync(cancellationToken); - - if (this.queryPartitionProvider == null) - { - cancellationToken.ThrowIfCancellationRequested(); - IDictionary queryConfiguration = await this.documentClient.GetQueryEngineConfigurationAsync(); - this.queryPartitionProvider = new QueryPartitionProvider(queryConfiguration); - } - } - finally - { - this.semaphore.Release(); - } - } - - return this.queryPartitionProvider.TryGetPartitionedQueryExecutionInfo( + return (await this.documentClient.QueryPartitionProvider).TryGetPartitionedQueryExecutionInfo( sqlQuerySpec, partitionKeyDefinition, requireFormattableOrderByQuery, diff --git a/Microsoft.Azure.Cosmos/src/Routing/DocumentAnalyzer.cs b/Microsoft.Azure.Cosmos/src/Routing/DocumentAnalyzer.cs index b18b669cf6..83e5ebc5f1 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/DocumentAnalyzer.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/DocumentAnalyzer.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Routing { using System; + using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.Azure.Documents; @@ -39,10 +40,10 @@ public static PartitionKeyInternal ExtractPartitionKeyValue(Document document, P return PartitionKeyInternal.FromObjectArray( partitionKeyDefinition.Paths.Select(path => { - string[] parts = PathParser.GetPathParts(path); - Debug.Assert(parts.Length >= 1, "Partition key component definition path is invalid."); + IReadOnlyList parts = PathParser.GetPathParts(path); + Debug.Assert(parts.Count >= 1, "Partition key component definition path is invalid."); - return document.GetValueByPath(parts, Undefined.Value); + return document.GetValueByPath(parts.ToArray(), Undefined.Value); }).ToArray(), false); } @@ -73,8 +74,8 @@ internal static PartitionKeyInternal ExtractPartitionKeyValue(T data, Partiti return PartitionKeyInternal.FromObjectArray( partitionKeyDefinition.Paths.Select(path => { - string[] parts = PathParser.GetPathParts(path); - Debug.Assert(parts.Length >= 1, "Partition key component definition path is invalid."); + IReadOnlyList parts = PathParser.GetPathParts(path); + Debug.Assert(parts.Count >= 1, "Partition key component definition path is invalid."); JToken token = convertToJToken(data); diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs index 9147aa979f..0b4c79f618 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs @@ -26,7 +26,6 @@ namespace Microsoft.Azure.Cosmos.Routing internal class PartitionRoutingHelper { public static IReadOnlyList> GetProvidedPartitionKeyRanges( - Func createBadRequestException, SqlQuerySpec querySpec, bool enableCrossPartitionQuery, bool parallelizeCrossPartitionQuery, diff --git a/Microsoft.Azure.Cosmos/src/Routing/PathParser.cs b/Microsoft.Azure.Cosmos/src/Routing/PathParser.cs index 13c3a38146..aae5c354a1 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PathParser.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PathParser.cs @@ -21,7 +21,7 @@ internal sealed class PathParser /// /// A path string /// An array of parts of path - public static string[] GetPathParts(string path) + public static IReadOnlyList GetPathParts(string path) { List tokens = new List(); int currentIndex = 0; @@ -45,7 +45,7 @@ public static string[] GetPathParts(string path) } } - return tokens.ToArray(); + return tokens; } private static string GetEscapedToken(string path, ref int currentIndex) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DocumentClientUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DocumentClientUnitTests.cs index db36b7e1fe..77068c8631 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DocumentClientUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/DocumentClientUnitTests.cs @@ -9,8 +9,10 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Net; using System.Net.Http; using System.Threading; + using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Internal; using Microsoft.Azure.Cosmos.Linq; + using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.Utils; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -79,6 +81,21 @@ public void RetryExceedingMaxTimeLimit() Assert.IsTrue(throttled); } + [TestMethod] + public async Task QueryPartitionProviderSingletonTestAsync() + { + DocumentClient client = new DocumentClient( + new Uri(ConfigurationManager.AppSettings["GatewayEndpoint"]), + ConfigurationManager.AppSettings["MasterKey"], + (HttpMessageHandler)null, + new ConnectionPolicy()); + + Task queryPartitionProviderTaskOne = client.QueryPartitionProvider; + Task queryPartitionProviderTaskTwo = client.QueryPartitionProvider; + Assert.AreSame(queryPartitionProviderTaskOne, queryPartitionProviderTaskTwo, "QueryPartitionProvider property is not a singleton"); + Assert.AreSame(await queryPartitionProviderTaskOne, await queryPartitionProviderTaskTwo, "QueryPartitionProvider property is not a singleton"); + } + private void TestRetryOnThrottled(int? numberOfRetries) { Mock mockStoreModel = new Mock(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs index 889cd1d1af..2fd3b4582d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs @@ -14,6 +14,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Common; + using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Collections; @@ -126,10 +127,13 @@ internal override Task GetPartitionKeyRangeCacheAsync() return Task.FromResult(this.partitionKeyRangeCache.Object); } - internal override Task> GetQueryEngineConfigurationAsync() + internal override Task QueryPartitionProvider { - Dictionary queryEngineConfiguration = JsonConvert.DeserializeObject< Dictionary>("{\"maxSqlQueryInputLength\":262144,\"maxJoinsPerSqlQuery\":5,\"maxLogicalAndPerSqlQuery\":500,\"maxLogicalOrPerSqlQuery\":500,\"maxUdfRefPerSqlQuery\":10,\"maxInExpressionItemsCount\":16000,\"queryMaxInMemorySortDocumentCount\":500,\"maxQueryRequestTimeoutFraction\":0.9,\"sqlAllowNonFiniteNumbers\":false,\"sqlAllowAggregateFunctions\":true,\"sqlAllowSubQuery\":true,\"sqlAllowScalarSubQuery\":true,\"allowNewKeywords\":true,\"sqlAllowLike\":false,\"sqlAllowGroupByClause\":false,\"maxSpatialQueryCells\":12,\"spatialMaxGeometryPointCount\":256,\"sqlAllowTop\":true,\"enableSpatialIndexing\":true}"); - return Task.FromResult((IDictionary)queryEngineConfiguration); + get + { + return Task.FromResult(new QueryPartitionProvider( +JsonConvert.DeserializeObject>("{\"maxSqlQueryInputLength\":262144,\"maxJoinsPerSqlQuery\":5,\"maxLogicalAndPerSqlQuery\":500,\"maxLogicalOrPerSqlQuery\":500,\"maxUdfRefPerSqlQuery\":10,\"maxInExpressionItemsCount\":16000,\"queryMaxInMemorySortDocumentCount\":500,\"maxQueryRequestTimeoutFraction\":0.9,\"sqlAllowNonFiniteNumbers\":false,\"sqlAllowAggregateFunctions\":true,\"sqlAllowSubQuery\":true,\"sqlAllowScalarSubQuery\":true,\"allowNewKeywords\":true,\"sqlAllowLike\":false,\"sqlAllowGroupByClause\":false,\"maxSpatialQueryCells\":12,\"spatialMaxGeometryPointCount\":256,\"sqlAllowTop\":true,\"enableSpatialIndexing\":true}"))); + } } ValueTask<(string token, string payload)> IAuthorizationTokenProvider.GetUserAuthorizationAsync(