From 5a096d11369124f85063747e7b767e0e9a400114 Mon Sep 17 00:00:00 2001 From: Matias Quaranta Date: Thu, 21 Dec 2023 16:28:38 -0800 Subject: [PATCH 1/5] Change Feed Processor: Fixes disposal of unused CancellationTokenSource (#4220) * Calling dispose * Tests * refactoring --- .../FeedManagement/PartitionControllerCore.cs | 7 ++++--- .../PartitionControllerSplitTests.cs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs index e181dadbec..7407477c21 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs @@ -89,8 +89,7 @@ public override async Task AddOrUpdateLeaseAsync(DocumentServiceLease lease) throw; } - PartitionSupervisor supervisor = this.partitionSupervisorFactory.Create(lease); - this.ProcessPartitionAsync(supervisor, lease).LogException(); + this.ProcessPartitionAsync(lease).LogException(); } public override async Task ShutdownAsync() @@ -146,8 +145,10 @@ private async Task RemoveLeaseAsync(DocumentServiceLease lease, bool wasAcquired } } - private async Task ProcessPartitionAsync(PartitionSupervisor partitionSupervisor, DocumentServiceLease lease) + private async Task ProcessPartitionAsync(DocumentServiceLease lease) { + using PartitionSupervisor partitionSupervisor = this.partitionSupervisorFactory.Create(lease); + try { await partitionSupervisor.RunAsync(this.shutdownCts.Token).ConfigureAwait(false); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerSplitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerSplitTests.cs index 25704e05c8..ba56424ee0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerSplitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionControllerSplitTests.cs @@ -48,6 +48,9 @@ public async Task Controller_ShouldSignalSynchronizerSplitPartition_IfPartitionS await sut.ShutdownAsync().ConfigureAwait(false); Mock.Get(synchronizer).VerifyAll(); + + Mock.Get(partitionSupervisor) + .Verify(s => s.Dispose(), Times.Once); } [TestMethod] @@ -76,6 +79,9 @@ public async Task Controller_ShouldPassLastKnownContinuationTokenToSynchronizer_ await sut.ShutdownAsync().ConfigureAwait(false); Mock.Get(synchronizer).VerifyAll(); + + Mock.Get(partitionSupervisor) + .Verify(s => s.Dispose(), Times.Once); } [TestMethod] @@ -108,6 +114,9 @@ public async Task Controller_ShouldCopyParentLeaseProperties_IfPartitionSplitHap .VerifySet(l => l.Properties = customProperties, Times.Once); Mock.Get(leaseChild2) .VerifySet(l => l.Properties = customProperties, Times.Once); + + Mock.Get(partitionSupervisor) + .Verify(s => s.Dispose(), Times.Once); } [TestMethod] @@ -132,6 +141,9 @@ public async Task Controller_ShouldKeepParentLease_IfSplitThrows() await sut.ShutdownAsync().ConfigureAwait(false); Mock.Get(leaseManager).Verify(manager => manager.DeleteAsync(lease), Times.Never); + + Mock.Get(partitionSupervisor) + .Verify(s => s.Dispose(), Times.Once); } [TestMethod] @@ -180,6 +192,13 @@ public async Task Controller_ShouldRunProcessingOnChildPartitions_IfHappyPath() monitor.Verify(m => m.NotifyLeaseAcquireAsync(leaseChild1.CurrentLeaseToken), Times.Once); monitor.Verify(m => m.NotifyLeaseReleaseAsync(leaseChild2.CurrentLeaseToken), Times.Once); + + Mock.Get(partitionSupervisor) + .Verify(s => s.Dispose(), Times.Once); + Mock.Get(partitionSupervisor1) + .Verify(s => s.Dispose(), Times.Once); + Mock.Get(partitionSupervisor2) + .Verify(s => s.Dispose(), Times.Once); } [TestMethod] From c5556853dbc8a63466d5bea5bc1f69ccdd0014e2 Mon Sep 17 00:00:00 2001 From: Debdatta Kunda <87335885+kundadebdatta@users.noreply.github.com> Date: Sat, 30 Dec 2023 04:51:20 -0800 Subject: [PATCH 2/5] [Internal] Per Partition Automatic Failover: Fixes Metadata Requests Retry Policy (#4205) * Code changes to retry on next preferred region for metadata reads on gateway timeouts. * Code changes to add retry for PK Ranges call. * Code changes to mark endpoint unavailable for read when cosmos exception occurs! * Code changes to fix unit tests. Added global endpoint manager in Pk Range Cache ctor. * Code changes to fix unit tests. * Code changes to fix build break. * Minor code clean-up. * Code changes to capture metadata location endpoint within on before send request. * Code changes to address review comments. * Code changes to fix build failure. * Code changes to refactor metadata timeout policy. * Code changes to add retry for request timeout. Fix emulator tests. * Code changes to add metadata retry policy unit tests. * Code changes to add more tests. * Code changes to refactor metadata retry policy logic to increment location index. Addressed review comments. * Code changes to address review comments. * Code changes to address review comments. * Code changes to add separate condition for pk range requests. --- .../src/ClientRetryPolicy.cs | 14 ++ Microsoft.Azure.Cosmos/src/DocumentClient.cs | 4 +- .../src/HttpClient/HttpTimeoutPolicy.cs | 13 +- .../src/MetadataRequestThrottleRetryPolicy.cs | 190 ++++++++++++++++++ .../src/Routing/PartitionKeyRangeCache.cs | 62 +++--- .../Mocks/MockDocumentClient.cs | 2 +- .../Batch/BatchAsyncBatcherTests.cs | 2 +- .../Batch/BatchAsyncContainerExecutorTests.cs | 2 +- .../Batch/BatchAsyncOperationContextTests.cs | 2 +- ...lkPartitionKeyRangeGoneRetryPolicyTests.cs | 2 +- .../PartitionSynchronizerCoreTests.cs | 43 +++- .../GatewayAddressCacheTests.cs | 4 +- .../GatewayStoreModelTest.cs | 24 +-- ...MetadataRequestThrottleRetryPolicyTests.cs | 129 ++++++++++++ .../PartitionKeyRangeCacheTest.cs | 141 ++++++++++++- .../PartitionKeyRangeHandlerTests.cs | 7 +- .../Utils/MockDocumentClient.cs | 9 +- 17 files changed, 578 insertions(+), 72 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/MetadataRequestThrottleRetryPolicy.cs create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MetadataRequestThrottleRetryPolicyTests.cs diff --git a/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs b/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs index 147977e130..e19433c17e 100644 --- a/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs @@ -98,6 +98,20 @@ public async Task ShouldRetryAsync( } } + // Any metadata request will throw a cosmos exception from CosmosHttpClientCore if + // it receives a 503 service unavailable from gateway. This check is to add retry + // mechanism for the metadata requests in such cases. + if (exception is CosmosException cosmosException) + { + ShouldRetryResult shouldRetryResult = await this.ShouldRetryInternalAsync( + cosmosException.StatusCode, + cosmosException.Headers.SubStatusCode); + if (shouldRetryResult != null) + { + return shouldRetryResult; + } + } + return await this.throttlingRetry.ShouldRetryAsync(exception, cancellationToken); } diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 632c158f96..8340df8207 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -662,7 +662,7 @@ private async Task OpenPrivateAsync(CancellationToken cancellationToken) tokenProvider: this, retryPolicy: this.retryPolicy, telemetryToServiceHelper: this.telemetryToServiceHelper); - this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache); + this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache, this.GlobalEndpointManager); DefaultTrace.TraceWarning("{0} occurred while OpenAsync. Exception Message: {1}", ex.ToString(), ex.Message); } @@ -1033,7 +1033,7 @@ private async Task GetInitializationTaskAsync(IStoreClientFactory storeCli tokenProvider: this, retryPolicy: this.retryPolicy, telemetryToServiceHelper: this.telemetryToServiceHelper); - this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache); + this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache, this.GlobalEndpointManager); this.ResetSessionTokenRetryPolicy = new ResetSessionTokenRetryPolicyFactory(this.sessionContainer, this.collectionCache, this.retryPolicy); gatewayStoreModel.SetCaches(this.partitionKeyRangeCache, this.collectionCache); diff --git a/Microsoft.Azure.Cosmos/src/HttpClient/HttpTimeoutPolicy.cs b/Microsoft.Azure.Cosmos/src/HttpClient/HttpTimeoutPolicy.cs index 42742f86c1..5b39a2c113 100644 --- a/Microsoft.Azure.Cosmos/src/HttpClient/HttpTimeoutPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/HttpClient/HttpTimeoutPolicy.cs @@ -29,8 +29,15 @@ public static HttpTimeoutPolicy GetTimeoutPolicy( return HttpTimeoutPolicyControlPlaneRetriableHotPath.InstanceShouldThrow503OnTimeout; } - //Partition Key Requests - if (documentServiceRequest.ResourceType == ResourceType.PartitionKeyRange) + //Get Partition Key Range Requests + if (documentServiceRequest.ResourceType == ResourceType.PartitionKeyRange + && documentServiceRequest.OperationType == OperationType.ReadFeed) + { + return HttpTimeoutPolicyControlPlaneRetriableHotPath.InstanceShouldThrow503OnTimeout; + } + + //Get Addresses Requests + if (documentServiceRequest.ResourceType == ResourceType.Address) { return HttpTimeoutPolicyControlPlaneRetriableHotPath.Instance; } @@ -44,7 +51,7 @@ public static HttpTimeoutPolicy GetTimeoutPolicy( //Meta Data Read if (HttpTimeoutPolicy.IsMetaData(documentServiceRequest) && documentServiceRequest.IsReadOnlyRequest) { - return HttpTimeoutPolicyDefault.InstanceShouldThrow503OnTimeout; + return HttpTimeoutPolicyControlPlaneRetriableHotPath.InstanceShouldThrow503OnTimeout; } //Default behavior diff --git a/Microsoft.Azure.Cosmos/src/MetadataRequestThrottleRetryPolicy.cs b/Microsoft.Azure.Cosmos/src/MetadataRequestThrottleRetryPolicy.cs new file mode 100644 index 0000000000..928d2f2e87 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/MetadataRequestThrottleRetryPolicy.cs @@ -0,0 +1,190 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Documents; + + /// + /// Metadata Request Throttle Retry Policy is combination of endpoint change retry + throttling retry. + /// + internal sealed class MetadataRequestThrottleRetryPolicy : IDocumentClientRetryPolicy + { + /// + /// A constant integer defining the default maximum retry wait time in seconds. + /// + private const int DefaultMaxWaitTimeInSeconds = 60; + + /// + /// A constant integer defining the default maximum retry count on service unavailable. + /// + private const int DefaultMaxServiceUnavailableRetryCount = 1; + + /// + /// An instance of . + /// + private readonly IGlobalEndpointManager globalEndpointManager; + + /// + /// Defines the throttling retry policy that is used as the underlying retry policy. + /// + private readonly IDocumentClientRetryPolicy throttlingRetryPolicy; + + /// + /// An integer defining the maximum retry count on service unavailable. + /// + private readonly int maxServiceUnavailableRetryCount; + + /// + /// An instance of containing the location endpoint where the partition key + /// range http request will be sent over. + /// + private MetadataRetryContext retryContext; + + /// + /// An integer capturing the current retry count on service unavailable. + /// + private int serviceUnavailableRetryCount; + + /// + /// The constructor to initialize an instance of . + /// + /// An instance of + /// An integer defining the maximum number + /// of attempts to retry when requests are throttled. + /// An integer defining the maximum wait time in seconds. + public MetadataRequestThrottleRetryPolicy( + IGlobalEndpointManager endpointManager, + int maxRetryAttemptsOnThrottledRequests, + int maxRetryWaitTimeInSeconds = DefaultMaxWaitTimeInSeconds) + { + this.globalEndpointManager = endpointManager; + this.maxServiceUnavailableRetryCount = Math.Max( + MetadataRequestThrottleRetryPolicy.DefaultMaxServiceUnavailableRetryCount, + this.globalEndpointManager.PreferredLocationCount); + + this.throttlingRetryPolicy = new ResourceThrottleRetryPolicy( + maxRetryAttemptsOnThrottledRequests, + maxRetryWaitTimeInSeconds); + + this.retryContext = new MetadataRetryContext + { + RetryLocationIndex = 0, + RetryRequestOnPreferredLocations = true, + }; + } + + /// + /// Should the caller retry the operation. + /// + /// Exception that occured when the operation was tried + /// An instance of . + /// True indicates caller should retry, False otherwise + public Task ShouldRetryAsync( + Exception exception, + CancellationToken cancellationToken) + { + if (exception is CosmosException cosmosException + && cosmosException.StatusCode == HttpStatusCode.ServiceUnavailable + && cosmosException.Headers.SubStatusCode == SubStatusCodes.TransportGenerated503) + { + if (this.IncrementRetryIndexOnServiceUnavailableForMetadataRead()) + { + return Task.FromResult(ShouldRetryResult.RetryAfter(TimeSpan.Zero)); + } + } + + return this.throttlingRetryPolicy.ShouldRetryAsync(exception, cancellationToken); + } + + /// + /// Should the caller retry the operation. + /// + /// in return of the request + /// An instance of . + /// True indicates caller should retry, False otherwise + public Task ShouldRetryAsync( + ResponseMessage cosmosResponseMessage, + CancellationToken cancellationToken) + { + if (cosmosResponseMessage?.StatusCode == HttpStatusCode.ServiceUnavailable + && cosmosResponseMessage?.Headers?.SubStatusCode == SubStatusCodes.TransportGenerated503) + { + if (this.IncrementRetryIndexOnServiceUnavailableForMetadataRead()) + { + return Task.FromResult(ShouldRetryResult.RetryAfter(TimeSpan.Zero)); + } + } + + return this.throttlingRetryPolicy.ShouldRetryAsync(cosmosResponseMessage, cancellationToken); + } + + /// + /// Method that is called before a request is sent to allow the retry policy implementation + /// to modify the state of the request. + /// + /// The request being sent to the service. + public void OnBeforeSendRequest(DocumentServiceRequest request) + { + // Clear the previous location-based routing directive. + request.RequestContext.ClearRouteToLocation(); + request.RequestContext.RouteToLocation( + this.retryContext.RetryLocationIndex, + this.retryContext.RetryRequestOnPreferredLocations); + + Uri metadataLocationEndpoint = this.globalEndpointManager.ResolveServiceEndpoint(request); + + DefaultTrace.TraceInformation("MetadataRequestThrottleRetryPolicy: Routing the metadata request to: {0} for operation type: {1} and resource type: {2}.", metadataLocationEndpoint, request.OperationType, request.ResourceType); + request.RequestContext.RouteToLocation(metadataLocationEndpoint); + } + + /// + /// Increments the location index when a service unavailable exception ocurrs, for any future read requests. + /// + /// A boolean flag indicating if the operation was successful. + private bool IncrementRetryIndexOnServiceUnavailableForMetadataRead() + { + if (this.serviceUnavailableRetryCount++ >= this.maxServiceUnavailableRetryCount) + { + DefaultTrace.TraceWarning("MetadataRequestThrottleRetryPolicy: Retry count: {0} has exceeded the maximum permitted retry count on service unavailable: {1}.", this.serviceUnavailableRetryCount, this.maxServiceUnavailableRetryCount); + return false; + } + + // Retrying on second PreferredLocations. + // RetryCount is used as zero-based index. + DefaultTrace.TraceWarning("MetadataRequestThrottleRetryPolicy: Incrementing the metadata retry location index to: {0}.", this.serviceUnavailableRetryCount); + this.retryContext = new MetadataRetryContext() + { + RetryLocationIndex = this.serviceUnavailableRetryCount, + RetryRequestOnPreferredLocations = true, + }; + + return true; + } + + /// + /// A helper class containing the required attributes for + /// metadata retry context. + /// + internal sealed class MetadataRetryContext + { + /// + /// An integer defining the current retry location index. + /// + public int RetryLocationIndex { get; set; } + + /// + /// A boolean flag indicating if the request should retry on + /// preferred locations. + /// + public bool RetryRequestOnPreferredLocations { get; set; } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs index d9e2854b2b..d849e4d3c0 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs @@ -30,17 +30,20 @@ internal class PartitionKeyRangeCache : IRoutingMapProvider, ICollectionRoutingM private readonly ICosmosAuthorizationTokenProvider authorizationTokenProvider; private readonly IStoreModel storeModel; private readonly CollectionCache collectionCache; + private readonly IGlobalEndpointManager endpointManager; public PartitionKeyRangeCache( ICosmosAuthorizationTokenProvider authorizationTokenProvider, IStoreModel storeModel, - CollectionCache collectionCache) + CollectionCache collectionCache, + IGlobalEndpointManager endpointManager) { this.routingMapCache = new AsyncCacheNonBlocking( keyEqualityComparer: StringComparer.Ordinal); this.authorizationTokenProvider = authorizationTokenProvider; this.storeModel = storeModel; this.collectionCache = collectionCache; + this.endpointManager = endpointManager; } public virtual async Task> TryGetOverlappingRangesAsync( @@ -121,10 +124,10 @@ public virtual async Task TryLookupAsync( return await this.routingMapCache.GetAsync( key: collectionRid, singleValueInitFunc: (_) => this.GetRoutingMapForCollectionAsync( - collectionRid, - previousValue, - trace, - request?.RequestContext?.ClientRequestStatistics), + collectionRid: collectionRid, + previousRoutingMap: previousValue, + trace: trace, + clientSideRequestStatistics: request?.RequestContext?.ClientRequestStatistics), forceRefresh: (currentValue) => PartitionKeyRangeCache.ShouldForceRefresh(previousValue, currentValue)); } catch (DocumentClientException ex) @@ -174,35 +177,6 @@ private static bool ShouldForceRefresh( return previousValue.ChangeFeedNextIfNoneMatch == currentValue.ChangeFeedNextIfNoneMatch; } - public async Task TryGetRangeByPartitionKeyRangeIdAsync(string collectionRid, - string partitionKeyRangeId, - ITrace trace, - IClientSideRequestStatistics clientSideRequestStatistics) - { - try - { - CollectionRoutingMap routingMap = await this.routingMapCache.GetAsync( - key: collectionRid, - singleValueInitFunc: (_) => this.GetRoutingMapForCollectionAsync( - collectionRid: collectionRid, - previousRoutingMap: null, - trace: trace, - clientSideRequestStatistics: clientSideRequestStatistics), - forceRefresh: (_) => false); - - return routingMap.TryGetRangeByPartitionKeyRangeId(partitionKeyRangeId); - } - catch (DocumentClientException ex) - { - if (ex.StatusCode == HttpStatusCode.NotFound) - { - return null; - } - - throw; - } - } - private async Task GetRoutingMapForCollectionAsync( string collectionRid, CollectionRoutingMap previousRoutingMap, @@ -213,6 +187,12 @@ private async Task GetRoutingMapForCollectionAsync( string changeFeedNextIfNoneMatch = previousRoutingMap?.ChangeFeedNextIfNoneMatch; HttpStatusCode lastStatusCode = HttpStatusCode.OK; + + RetryOptions retryOptions = new RetryOptions(); + MetadataRequestThrottleRetryPolicy metadataRetryPolicy = new ( + endpointManager: this.endpointManager, + maxRetryAttemptsOnThrottledRequests: retryOptions.MaxRetryAttemptsOnThrottledRequests, + maxRetryWaitTimeInSeconds: retryOptions.MaxRetryWaitTimeInSeconds); do { INameValueCollection headers = new RequestNameValueCollection(); @@ -224,10 +204,9 @@ private async Task GetRoutingMapForCollectionAsync( headers.Set(HttpConstants.HttpHeaders.IfNoneMatch, changeFeedNextIfNoneMatch); } - RetryOptions retryOptions = new RetryOptions(); using (DocumentServiceResponse response = await BackoffRetryUtility.ExecuteAsync( - () => this.ExecutePartitionKeyRangeReadChangeFeedAsync(collectionRid, headers, trace, clientSideRequestStatistics), - new ResourceThrottleRetryPolicy(retryOptions.MaxRetryAttemptsOnThrottledRequests, retryOptions.MaxRetryWaitTimeInSeconds))) + () => this.ExecutePartitionKeyRangeReadChangeFeedAsync(collectionRid, headers, trace, clientSideRequestStatistics, metadataRetryPolicy), + retryPolicy: metadataRetryPolicy)) { lastStatusCode = response.StatusCode; changeFeedNextIfNoneMatch = response.Headers[HttpConstants.HttpHeaders.ETag]; @@ -274,7 +253,8 @@ private async Task GetRoutingMapForCollectionAsync( private async Task ExecutePartitionKeyRangeReadChangeFeedAsync(string collectionRid, INameValueCollection headers, ITrace trace, - IClientSideRequestStatistics clientSideRequestStatistics) + IClientSideRequestStatistics clientSideRequestStatistics, + IDocumentClientRetryPolicy retryPolicy) { using (ITrace childTrace = trace.StartChild("Read PartitionKeyRange Change Feed", TraceComponent.Transport, Tracing.TraceLevel.Info)) { @@ -285,6 +265,7 @@ private async Task ExecutePartitionKeyRangeReadChangeFe AuthorizationTokenType.PrimaryMasterKey, headers)) { + retryPolicy.OnBeforeSendRequest(request); string authorizationToken = null; try { @@ -333,6 +314,11 @@ private async Task ExecutePartitionKeyRangeReadChangeFe childTrace.AddDatum("Exception Message", ex.Message); throw; } + catch (CosmosException ce) + { + childTrace.AddDatum("Exception Message", ce.Message); + throw; + } } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs index 7d268407de..c6b59b8cea 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs @@ -228,7 +228,7 @@ private void Init() }, string.Empty); - this.partitionKeyRangeCache = new Mock(null, null, null); + this.partitionKeyRangeCache = new Mock(null, null, null, null); this.partitionKeyRangeCache.Setup( m => m.TryLookupAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs index 044a735734..acc83d3ff4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs @@ -798,7 +798,7 @@ private class ClientWithSplitDetection : MockDocumentClient public ClientWithSplitDetection() { - this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null); + this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null, null); this.partitionKeyRangeCache.Setup( m => m.TryGetOverlappingRangesAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs index 993afa449b..c6bca20950 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs @@ -376,7 +376,7 @@ private class ClientWithSplitDetection : MockDocumentClient public ClientWithSplitDetection() { - this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null); + this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null, null); this.partitionKeyRangeCache.Setup( m => m.TryGetOverlappingRangesAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs index 5ec655d386..8e3c6bcbc3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs @@ -287,7 +287,7 @@ private class ClientWithSplitDetection : MockDocumentClient public ClientWithSplitDetection() { - this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null); + this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null, null); this.partitionKeyRangeCache.Setup( m => m.TryGetOverlappingRangesAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs index e1c8ad0cdf..1f87d40635 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs @@ -145,7 +145,7 @@ private class ClientWithSplitDetection : MockDocumentClient public ClientWithSplitDetection() { - this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null); + this.partitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null, null); this.partitionKeyRangeCache.Setup( m => m.TryGetOverlappingRangesAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs index b56a9b377a..166b0c52cf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs @@ -20,6 +20,28 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed.Tests [TestCategory("ChangeFeed")] public class PartitionSynchronizerCoreTests { + private GlobalEndpointManager endpointManager; + + [TestInitialize] + public void TestInitialize() + { + Mock mockDocumentClient = new(); + + mockDocumentClient + .Setup(client => client.ServiceEndpoint) + .Returns(new Uri("https://foo")); + + this.endpointManager = new( + mockDocumentClient.Object, + new ConnectionPolicy()); + } + + [TestCleanup] + public void Cleanup() + { + this.endpointManager.Dispose(); + } + /// /// Verifies handling of Splits on PKRange based leases /// @@ -39,7 +61,8 @@ public async Task HandlePartitionGoneAsync_PKRangeBasedLease_Split() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { @@ -102,7 +125,8 @@ public async Task HandlePartitionGoneAsync_EpkBasedLease_Split() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { @@ -170,7 +194,8 @@ public async Task HandlePartitionGoneAsync_PKRangeBasedLease_Merge() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { @@ -228,7 +253,8 @@ public async Task HandlePartitionGoneAsync_EpkBasedLease_Merge() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { @@ -276,7 +302,8 @@ public async Task CreateMissingLeases_NoLeases() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { @@ -321,7 +348,8 @@ public async Task CreateMissingLeases_SomePKRangeLeases() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { @@ -372,7 +400,8 @@ public async Task CreateMissingLeases_SomePKRangeAndEPKLeases() Mock pkRangeCache = new Mock( Mock.Of(), Mock.Of(), - Mock.Of()); + Mock.Of(), + this.endpointManager); List resultingRanges = new List() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs index fded897e42..4b49c29d8e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs @@ -60,7 +60,7 @@ public GatewayAddressCacheTests() } }; - this.partitionKeyRangeCache = new Mock(null, null, null); + this.partitionKeyRangeCache = new Mock(null, null, null, null); this.partitionKeyRangeCache .Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny(), @@ -755,7 +755,7 @@ public async Task GlobalAddressResolver_OpenConnectionsToAllReplicasAsync_WhenIn .Returns(Task.FromResult(containerProperties)); string exceptionMessage = "Failed to lookup partition key ranges."; - Mock partitionKeyRangeCache = new (null, null, null); + Mock partitionKeyRangeCache = new (null, null, null, null); partitionKeyRangeCache .Setup(m => m.TryGetOverlappingRangesAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs index fd9a8a3efd..c578aef620 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs @@ -274,7 +274,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsr, ConsistencyLevel.Session, new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); @@ -301,7 +301,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsrQueryPlan, ConsistencyLevel.Session, new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); @@ -355,7 +355,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsr, ConsistencyLevel.Session, new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); @@ -385,7 +385,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsrNoSessionToken, ConsistencyLevel.Session, sessionContainer, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); @@ -426,7 +426,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( It.IsAny(), NoOpTrace.Singleton)).Returns(Task.FromResult(containerProperties)); - Mock mockPartitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null); + Mock mockPartitionKeyRangeCache = new Mock(MockBehavior.Strict, null, null, null, null); mockPartitionKeyRangeCache.Setup(x => x.TryGetPartitionKeyRangeByIdAsync( containerProperties.ResourceId, partitionKeyRangeId, @@ -473,7 +473,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsrSprocExecute, ConsistencyLevel.Session, new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); @@ -512,7 +512,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsrNoSessionToken, ConsistencyLevel.Session, sessionContainer, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); @@ -972,7 +972,7 @@ public async Task GatewayStoreModel_AvoidGlobalSessionToken() null, MockCosmosUtil.CreateCosmosHttpClient(() => new HttpClient())); Mock clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null, null); - Mock partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache.Object); + Mock partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache.Object, endpointManager); sessionContainer.SetSessionToken( ResourceId.NewDocumentCollectionId(42, 129).DocumentCollectionId.ToString(), @@ -1068,7 +1068,7 @@ Task sendFunc(HttpRequestMessage request) Mock clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null, null); - Mock partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache.Object); + Mock partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache.Object, endpointManager); storeModel.SetCaches(partitionKeyRangeCache.Object, clientCollectionCache.Object); INameValueCollection headers = new RequestNameValueCollection(); @@ -1136,7 +1136,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( documentServiceRequestToChild, ConsistencyLevel.Session, sessionContainer, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(sessionContainer, gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); @@ -1202,7 +1202,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( documentServiceRequestToChild, ConsistencyLevel.Session, sessionContainer, - partitionKeyRangeCache: new Mock(null, null, null).Object, + partitionKeyRangeCache: new Mock(null, null, null, null).Object, clientCollectionCache: new Mock(sessionContainer, gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); @@ -1273,7 +1273,7 @@ static async Task messageHandler(HttpRequestMessage request MockCosmosUtil.CreateCosmosHttpClient(() => new HttpClient(httpMessageHandler))); ClientCollectionCache clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null, null).Object; - PartitionKeyRangeCache partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache).Object; + PartitionKeyRangeCache partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache, endpointManager).Object; storeModel.SetCaches(partitionKeyRangeCache, clientCollectionCache); await executeWithGatewayStoreModel(storeModel); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MetadataRequestThrottleRetryPolicyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MetadataRequestThrottleRetryPolicyTests.cs new file mode 100644 index 0000000000..40faac51e2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/MetadataRequestThrottleRetryPolicyTests.cs @@ -0,0 +1,129 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tests +{ + using System; + using System.Threading.Tasks; + using Microsoft.Azure.Documents; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Cosmos.Tracing; + using System.Net; + using System.Reflection; + using static Microsoft.Azure.Cosmos.MetadataRequestThrottleRetryPolicy; + + /// + /// Unit tests for . + /// + [TestClass] + public class MetadataRequestThrottleRetryPolicyTests + { + [TestMethod] + [Owner("dkunda")] + [DataRow(true, true, DisplayName = "Test when a response message with a valid substatus code was used.")] + [DataRow(false, true, DisplayName = "Test when an exception was thrown with a valid substatus code.")] + [DataRow(true, false, DisplayName = "Test when a response message with an invalid substatus code was used.")] + [DataRow(false, false, DisplayName = "Test when an exception was thrown with an invalid substatus code.")] + public async Task ShouldRetryAsync_WithValidAndInvalidSubStatusCodes_ShouldIncrementLocationIndexOrSkip( + bool useResponseMessage, + bool isValidSubStatusCode) + { + // Arrange. + ShouldRetryResult retryResult; + string collectionRid = "test-collection"; + Uri primaryServiceEndpoint = new ("https://default-endpoint-region1.net/"); + Uri routedServiceEndpoint = new("https://default-endpoint-region2.net/"); + + Documents.Collections.INameValueCollection headers = new Documents.Collections.RequestNameValueCollection(); + + headers.Set(HttpConstants.HttpHeaders.PageSize, "10"); + headers.Set(HttpConstants.HttpHeaders.A_IM, HttpConstants.A_IMHeaderValues.IncrementalFeed); + + DocumentServiceRequest request = DocumentServiceRequest.Create( + OperationType.ReadFeed, + collectionRid, + Documents.ResourceType.PartitionKeyRange, + AuthorizationTokenType.PrimaryMasterKey, + headers); + + Mock mockedGlobalEndpointManager = new (); + mockedGlobalEndpointManager + .SetupSequence(gem => gem.ResolveServiceEndpoint(It.IsAny())) + .Returns(primaryServiceEndpoint) + .Returns(isValidSubStatusCode ? routedServiceEndpoint : primaryServiceEndpoint); + + MetadataRequestThrottleRetryPolicy policy = new (mockedGlobalEndpointManager.Object, 0); + policy.OnBeforeSendRequest(request); + + Assert.AreEqual(primaryServiceEndpoint, request.RequestContext.LocationEndpointToRoute); + + // Act. + if (useResponseMessage) + { + Headers responseHeaders = new() + { + SubStatusCode = isValidSubStatusCode + ? SubStatusCodes.TransportGenerated503 + : SubStatusCodes.BWTermCountLimitExceeded + }; + + ResponseMessage responseMessage = new( + statusCode: HttpStatusCode.ServiceUnavailable, + requestMessage: null, + headers: responseHeaders, + cosmosException: null, + trace: NoOpTrace.Singleton); + + retryResult = await policy.ShouldRetryAsync(responseMessage, default); + } + else + { + CosmosException exception = CosmosExceptionFactory.CreateServiceUnavailableException( + message: "Service Unavailable at the moment.", + headers: new Headers() + { + ActivityId = System.Diagnostics.Trace.CorrelationManager.ActivityId.ToString(), + SubStatusCode = isValidSubStatusCode + ? SubStatusCodes.TransportGenerated503 + : SubStatusCodes.BWTermCountLimitExceeded + }, + trace: NoOpTrace.Singleton, + innerException: null); + + + retryResult = await policy.ShouldRetryAsync(exception, default); + } + + policy.OnBeforeSendRequest(request); + + // Assert. + FieldInfo fieldInfo = policy + .GetType() + .GetField( + name: "retryContext", + bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic); + + MetadataRetryContext retryContext = (MetadataRetryContext)fieldInfo + .GetValue( + obj: policy); + + Assert.IsNotNull(retryResult); + if (isValidSubStatusCode) + { + Assert.AreEqual(true, retryResult.ShouldRetry, "MetadataRequestThrottleRetryPolicy should return true since the sub status code indicates to retry the request in the next preferred read region."); + Assert.AreEqual(1, retryContext.RetryLocationIndex, "Indicates that the retry location index was incremented."); + Assert.AreEqual(routedServiceEndpoint, request.RequestContext.LocationEndpointToRoute); + } + else + { + Assert.AreEqual(false, retryResult.ShouldRetry, "ResourceThrottleRetryPolicy should return false since the status code does not indicate the request was throttled."); + Assert.AreEqual(0, retryContext.RetryLocationIndex, "Indicates that the retry location index remain unchanged."); + Assert.AreEqual(primaryServiceEndpoint, request.RequestContext.LocationEndpointToRoute); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeCacheTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeCacheTest.cs index 8d7b32a7b7..44eb2fc565 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeCacheTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeCacheTest.cs @@ -21,6 +21,8 @@ namespace Microsoft.Azure.Cosmos.Tests using TraceLevel = Cosmos.Tracing.TraceLevel; using Newtonsoft.Json.Linq; using Newtonsoft.Json; + using System; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; /// /// Unit Tests for . @@ -77,6 +79,11 @@ public async Task TryGetOverlappingRangesAsync_WithFreshContainer_ShouldNotAddSa [HttpConstants.HttpHeaders.ETag] = eTag, }; + Mock mockDocumentClient = new Mock(); + mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); + + using GlobalEndpointManager endpointManager = new (mockDocumentClient.Object, new ConnectionPolicy()); + mockStoreModel.SetupSequence(x => x.ProcessMessageAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new DocumentServiceResponse(new MemoryStream(singlePkCollectionCacheByte), new StoreResponseNameValueCollection() @@ -91,7 +98,7 @@ public async Task TryGetOverlappingRangesAsync_WithFreshContainer_ShouldNotAddSa .Returns(new ValueTask(authToken)); // Act. - PartitionKeyRangeCache partitionKeyRangeCache = new(mockTokenProvider.Object, mockStoreModel.Object, mockCollectioNCache.Object); + PartitionKeyRangeCache partitionKeyRangeCache = new(mockTokenProvider.Object, mockStoreModel.Object, mockCollectioNCache.Object, endpointManager); IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( containerRId, FeedRangeEpk.FullRange.Range, @@ -119,5 +126,137 @@ public async Task TryGetOverlappingRangesAsync_WithFreshContainer_ShouldNotAddSa Assert.AreEqual(eTag, secondPkRangeValue); } } + + /// + /// Test to validate that when the gateway service is unavailable, the partition key range cache is able to retry + /// the Get PK Range calls to different regions, based on the preferred locations count. Therefore, the cache keep + /// retrying until the max attempt is exausted. + /// + [TestMethod] + [DataRow(1, false, DisplayName = "Validates when the preferred location count is just one, the cache retries only once and fails.")] + [DataRow(2, false, DisplayName = "Validates when the preferred location count is two, the cache retries twice and fails.")] + [DataRow(3, true, DisplayName = "Validates when the preferred location count is three, the cache retries thrice and succeeds on the last attempt.")] + public async Task TryGetOverlappingRangesAsync_WhenGatewayThrowsServiceUnavailable_ShouldMarkReadEndpointAsUnavailable2(int preferredLocationsCount, bool shouldSucceed) + { + // Arrange. + string eTag = "483"; + string authToken = "token!"; + string containerRId = "kjhsAA=="; + string singlePkCollectionCache = "{\"_rid\":\"3FIlAOzjvyg=\",\"PartitionKeyRanges\":[{\"_rid\":\"3FIlAOzjvygCAAAAAAAAUA==\",\"id\":\"0\",\"_etag\":\"\\\"00005565-0000-0800-0000-621fd98a0000\\\"\",\"minInclusive\":\"\",\"maxExclusive\":\"FF\",\"ridPrefix\":0,\"_self\":\"dbs/3FIlAA==/colls/3FIlAOzjvyg=/pkranges/3FIlAOzjvygCAAAAAAAAUA==/\",\"throughputFraction\":1,\"status\":\"splitting\",\"parents\":[],\"_ts\":1646254474,\"_lsn\":44}],\"_count\":1}"; + byte[] singlePkCollectionCacheByte = Encoding.UTF8.GetBytes(singlePkCollectionCache); + using (ITrace trace = Trace.GetRootTrace(this.TestContext.TestName, TraceComponent.Unknown, TraceLevel.Info)) + { + Mock mockStoreModel = new(); + Mock mockCollectioNCache = new(); + Mock mockTokenProvider = new(); + NameValueCollectionWrapper headers = new() + { + [HttpConstants.HttpHeaders.ETag] = eTag, + }; + + Uri serviceUri = new("https://foo"); + Mock mockDocumentClient = new Mock(); + mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(serviceUri); + + Mock mockedEndpointManager = new Mock(); + mockedEndpointManager + .Setup(gem => gem.ResolveServiceEndpoint(It.IsAny())) + .Returns(serviceUri); + + mockedEndpointManager + .Setup(gem => gem.PreferredLocationCount) + .Returns(preferredLocationsCount); + + mockStoreModel.SetupSequence(x => x.ProcessMessageAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(CosmosExceptionFactory.CreateServiceUnavailableException( + message: "Service is Unavailable.", + headers: new Headers() + { + ActivityId = System.Diagnostics.Trace.CorrelationManager.ActivityId.ToString(), + SubStatusCode = SubStatusCodes.TransportGenerated503 + }, + trace: trace, + innerException: null)) + .ThrowsAsync(CosmosExceptionFactory.CreateServiceUnavailableException( + message: "Service is Unavailable.", + headers: new Headers() + { + ActivityId = System.Diagnostics.Trace.CorrelationManager.ActivityId.ToString(), + SubStatusCode = SubStatusCodes.TransportGenerated503 + }, + trace: trace, + innerException: null)) + .ThrowsAsync(CosmosExceptionFactory.CreateServiceUnavailableException( + message: "Service is Unavailable.", + headers: new Headers() + { + ActivityId = System.Diagnostics.Trace.CorrelationManager.ActivityId.ToString(), + SubStatusCode = SubStatusCodes.TransportGenerated503 + }, + trace: trace, + innerException: null)) + .ReturnsAsync(new DocumentServiceResponse(new MemoryStream(singlePkCollectionCacheByte), + new StoreResponseNameValueCollection() + { + ETag = eTag, + }, + HttpStatusCode.OK)) + .ReturnsAsync(new DocumentServiceResponse(null, headers, HttpStatusCode.NotModified, null)) + .ReturnsAsync(new DocumentServiceResponse(null, headers, HttpStatusCode.NotModified, null)); + + mockTokenProvider.Setup(x => x.GetUserAuthorizationTokenAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new ValueTask(authToken)); + + PartitionKeyRangeCache partitionKeyRangeCache = new(mockTokenProvider.Object, mockStoreModel.Object, mockCollectioNCache.Object, mockedEndpointManager.Object); + + if (shouldSucceed) + { + // Act. + IReadOnlyList partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync( + containerRId, + FeedRangeEpk.FullRange.Range, + trace, + forceRefresh: true); + + // Assert. + string dataPath = "children[0].data", previousContinuationTokenKey = "['Previous Continuation Token']", diagnostics = new CosmosTraceDiagnostics(trace).ToString(); + JObject traceData = JsonConvert.DeserializeObject(diagnostics); + + Assert.IsNotNull(traceData); + + string firstPkRangeCacheKeyName = ((JProperty)traceData.SelectToken(dataPath)?.First).Name; + string secondPkRangeCacheKeyName = ((JProperty)traceData.SelectToken(dataPath)?.Last).Name; + + Assert.IsTrue(!string.IsNullOrWhiteSpace(firstPkRangeCacheKeyName)); + Assert.IsTrue(!string.IsNullOrWhiteSpace(secondPkRangeCacheKeyName)); + Assert.AreNotEqual(firstPkRangeCacheKeyName, secondPkRangeCacheKeyName); + + string firstPkRangeValue = ((JProperty)traceData.SelectToken(dataPath).First)?.Value?.SelectToken(previousContinuationTokenKey)?.ToString(); + string secondPkRangeValue = ((JProperty)traceData.SelectToken(dataPath).Last)?.Value?.SelectToken(previousContinuationTokenKey)?.ToString(); + + Assert.IsTrue(!string.IsNullOrWhiteSpace(secondPkRangeValue)); + Assert.IsTrue(string.IsNullOrEmpty(firstPkRangeValue)); + Assert.AreEqual(eTag, secondPkRangeValue); + } + else + { + // Act. + CosmosException cosmosException = await Assert.ThrowsExceptionAsync(() => partitionKeyRangeCache.TryGetOverlappingRangesAsync( + containerRId, + FeedRangeEpk.FullRange.Range, + trace, + forceRefresh: true)); + + // Assert. + string diagnostics = new CosmosTraceDiagnostics(trace).ToString(); + JObject traceData = JsonConvert.DeserializeObject(diagnostics); + + Assert.IsNotNull(cosmosException); + Assert.IsNotNull(traceData); + Assert.AreEqual(HttpStatusCode.ServiceUnavailable, cosmosException.StatusCode); + Assert.AreEqual(SubStatusCodes.TransportGenerated503, cosmosException.Headers.SubStatusCode); + } + } + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeHandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeHandlerTests.cs index 66eed8a723..2db6f9e9d7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeHandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeHandlerTests.cs @@ -651,6 +651,10 @@ public async Task PartitionKeyRangeGoneTracePlumbingTest() containerProperties.Id = "TestContainer"; containerProperties.PartitionKey = partitionKeyDefinition; + Mock mockDocumentClient = new Mock(); + mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); + + using GlobalEndpointManager endpointManager = new(mockDocumentClient.Object, new ConnectionPolicy()); Mock collectionCache = new Mock(MockBehavior.Strict); collectionCache.Setup(c => c.ResolveCollectionAsync(It.IsAny(), default, trace)) @@ -661,7 +665,8 @@ public async Task PartitionKeyRangeGoneTracePlumbingTest() MockBehavior.Strict, new Mock().Object, new Mock().Object, - collectionCache.Object); + collectionCache.Object, + endpointManager); partitionKeyRangeCache.Setup(c => c.TryLookupAsync(collectionRid, null, It.IsAny(), trace)) .ReturnsAsync(collectionRoutingMap); 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 b0c29eb016..b6498dec6f 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 @@ -227,7 +227,14 @@ private void Init() return Task.FromResult(cosmosContainerSetting); }); - this.partitionKeyRangeCache = new Mock(null, null, null); + Mock mockDocumentClient = new(); + mockDocumentClient + .Setup(client => client.ServiceEndpoint) + .Returns(new Uri("https://foo")); + + using GlobalEndpointManager endpointManager = new(mockDocumentClient.Object, new ConnectionPolicy()); + + this.partitionKeyRangeCache = new Mock(null, null, null, endpointManager); this.partitionKeyRangeCache.Setup( m => m.TryLookupAsync( It.IsAny(), From 02e4f3475e4842c5dbe551dc706464a52b4172d4 Mon Sep 17 00:00:00 2001 From: Nalu Tripician <27316859+NaluTripician@users.noreply.github.com> Date: Sun, 31 Dec 2023 15:16:56 -0500 Subject: [PATCH 3/5] [Internal] GlobalEndpointManager: Fixes Memory Leak (#4226) * memory leak fix * removed commented out code * changed to async so test would pass, with just static GetPropetiesHelper will get disposed before requests go through * added thread saftey * fixed to interlock pattern --------- Co-authored-by: Debdatta Kunda <87335885+kundadebdatta@users.noreply.github.com> --- .../src/Routing/GlobalEndpointManager.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs b/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs index e12d99255d..64156f729e 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs @@ -114,25 +114,26 @@ public Uri GetHubUri() /// The 2 additional tasks will go through all the preferred regions in parallel /// It will return the first success and stop the parallel tasks. /// - public static Task GetDatabaseAccountFromAnyLocationsAsync( + public static async Task GetDatabaseAccountFromAnyLocationsAsync( Uri defaultEndpoint, IList? locations, Func> getDatabaseAccountFn, CancellationToken cancellationToken) - { - GetAccountPropertiesHelper threadSafeGetAccountHelper = new GetAccountPropertiesHelper( + { + using (GetAccountPropertiesHelper threadSafeGetAccountHelper = new GetAccountPropertiesHelper( defaultEndpoint, locations?.GetEnumerator(), getDatabaseAccountFn, - cancellationToken); - - return threadSafeGetAccountHelper.GetAccountPropertiesAsync(); + cancellationToken)) + { + return await threadSafeGetAccountHelper.GetAccountPropertiesAsync(); + } } /// /// This is a helper class to /// - private class GetAccountPropertiesHelper + private class GetAccountPropertiesHelper : IDisposable { private readonly CancellationTokenSource CancellationTokenSource; private readonly Uri DefaultEndpoint; @@ -140,7 +141,8 @@ private class GetAccountPropertiesHelper private readonly Func> GetDatabaseAccountFn; private readonly List TransientExceptions = new List(); private AccountProperties? AccountProperties = null; - private Exception? NonRetriableException = null; + private Exception? NonRetriableException = null; + private int disposeCounter = 0; public GetAccountPropertiesHelper( Uri defaultEndpoint, @@ -345,7 +347,16 @@ private static bool IsNonRetriableException(Exception exception) } return false; - } + } + + public void Dispose() + { + if (Interlocked.Increment(ref this.disposeCounter) == 1) + { + this.CancellationTokenSource?.Cancel(); + this.CancellationTokenSource?.Dispose(); + } + } } public virtual Uri ResolveServiceEndpoint(DocumentServiceRequest request) From 4195ec0efa5097a36e36534e140dea5afab8832e Mon Sep 17 00:00:00 2001 From: Nalu Tripician <27316859+NaluTripician@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:40:52 -0500 Subject: [PATCH 4/5] SDK 3.37.1 : Adds version bump and changelog (#4231) * [Internal] GlobalEndpointManager: Fixes Memory Leak (#4226) * memory leak fix * removed commented out code * changed to async so test would pass, with just static GetPropetiesHelper will get disposed before requests go through * added thread saftey * fixed to interlock pattern --------- Co-authored-by: Debdatta Kunda <87335885+kundadebdatta@users.noreply.github.com> * changelog + version bump * genAPI * remove code changes * removed nit --------- Co-authored-by: Debdatta Kunda <87335885+kundadebdatta@users.noreply.github.com> --- Directory.Build.props | 4 +- .../contracts/API_3.37.1-preview.txt | 1611 +++++++++++++++++ .../contracts/API_3.37.1.txt | 1552 ++++++++++++++++ changelog.md | 6 + 4 files changed, 3171 insertions(+), 2 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/contracts/API_3.37.1-preview.txt create mode 100644 Microsoft.Azure.Cosmos/contracts/API_3.37.1.txt diff --git a/Directory.Build.props b/Directory.Build.props index 05ef3a1ad8..d77c3f59e3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 3.37.0 - 3.37.0 + 3.37.1 + 3.37.1 preview 3.31.5 2.0.4 diff --git a/Microsoft.Azure.Cosmos/contracts/API_3.37.1-preview.txt b/Microsoft.Azure.Cosmos/contracts/API_3.37.1-preview.txt new file mode 100644 index 0000000000..672794b621 --- /dev/null +++ b/Microsoft.Azure.Cosmos/contracts/API_3.37.1-preview.txt @@ -0,0 +1,1611 @@ +namespace Microsoft.Azure.Cosmos +{ + public class AccountConsistency + { + public AccountConsistency(); + public ConsistencyLevel DefaultConsistencyLevel { get; } + public int MaxStalenessIntervalInSeconds { get; } + public int MaxStalenessPrefix { get; } + } + public class AccountProperties + { + public AccountConsistency Consistency { get; } + public string ETag { get; } + public string Id { get; } + public IEnumerable ReadableRegions { get; } + public IEnumerable WritableRegions { get; } + } + public class AccountRegion + { + public AccountRegion(); + public string Endpoint { get; } + public string Name { get; } + } + public sealed class BoundingBoxProperties + { + public BoundingBoxProperties(); + public double Xmax { get; set; } + public double Xmin { get; set; } + public double Ymax { get; set; } + public double Ymin { get; set; } + } + public abstract class ChangeFeedEstimator + { + protected ChangeFeedEstimator(); + public abstract FeedIterator GetCurrentStateIterator(ChangeFeedEstimatorRequestOptions changeFeedEstimatorRequestOptions=null); + } + public sealed class ChangeFeedEstimatorRequestOptions + { + public ChangeFeedEstimatorRequestOptions(); + public Nullable MaxItemCount { get; set; } + } + public class ChangeFeedItemChange + { + public ChangeFeedItemChange(); + public T Current { get; set; } + public ChangeFeedMetadata Metadata { get; set; } + public T Previous { get; set; } + } + public class ChangeFeedMetadata + { + public ChangeFeedMetadata(DateTime conflictResolutionTimestamp, long lsn, ChangeFeedOperationType operationType, long previousLsn); + public DateTime ConflictResolutionTimestamp { get; } + public bool IsTimeToLiveExpired { get; } + public long Lsn { get; } + public ChangeFeedOperationType OperationType { get; } + public long PreviousLsn { get; } + } + public abstract class ChangeFeedMode + { + public static ChangeFeedMode AllVersionsAndDeletes { get; } + public static ChangeFeedMode Incremental { get; } + public static ChangeFeedMode LatestVersion { get; } + } + public enum ChangeFeedOperationType + { + Create = 0, + Delete = 2, + Replace = 1, + } + public sealed class ChangeFeedPolicy + { + public ChangeFeedPolicy(); + public static TimeSpan FullFidelityNoRetention { get; } + public TimeSpan FullFidelityRetention { get; set; } + } + public abstract class ChangeFeedProcessor + { + protected ChangeFeedProcessor(); + public abstract Task StartAsync(); + public abstract Task StopAsync(); + } + public class ChangeFeedProcessorBuilder + { + public ChangeFeedProcessor Build(); + public ChangeFeedProcessorBuilder WithErrorNotification(Container.ChangeFeedMonitorErrorDelegate errorDelegate); + public ChangeFeedProcessorBuilder WithInstanceName(string instanceName); + public ChangeFeedProcessorBuilder WithLeaseAcquireNotification(Container.ChangeFeedMonitorLeaseAcquireDelegate acquireDelegate); + public ChangeFeedProcessorBuilder WithLeaseConfiguration(Nullable acquireInterval=default(Nullable), Nullable expirationInterval=default(Nullable), Nullable renewInterval=default(Nullable)); + public ChangeFeedProcessorBuilder WithLeaseContainer(Container leaseContainer); + public ChangeFeedProcessorBuilder WithLeaseReleaseNotification(Container.ChangeFeedMonitorLeaseReleaseDelegate releaseDelegate); + public ChangeFeedProcessorBuilder WithMaxItems(int maxItemCount); + public ChangeFeedProcessorBuilder WithPollInterval(TimeSpan pollInterval); + public ChangeFeedProcessorBuilder WithStartTime(DateTime startTime); + } + public abstract class ChangeFeedProcessorContext + { + protected ChangeFeedProcessorContext(); + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract Headers Headers { get; } + public abstract string LeaseToken { get; } + } + public sealed class ChangeFeedProcessorState + { + public ChangeFeedProcessorState(string leaseToken, long estimatedLag, string instanceName); + public long EstimatedLag { get; } + public string InstanceName { get; } + public string LeaseToken { get; } + } + public class ChangeFeedProcessorUserException : Exception + { + public ChangeFeedProcessorUserException(Exception originalException, ChangeFeedProcessorContext context); + protected ChangeFeedProcessorUserException(SerializationInfo info, StreamingContext context); + public ChangeFeedProcessorContext ChangeFeedProcessorContext { get; } + public override void GetObjectData(SerializationInfo info, StreamingContext context); + } + public sealed class ChangeFeedRequestOptions : RequestOptions + { + public ChangeFeedRequestOptions(); + public new string IfMatchEtag { get; set; } + public new string IfNoneMatchEtag { get; set; } + public Nullable PageSizeHint { get; set; } + } + public abstract class ChangeFeedStartFrom + { + public static ChangeFeedStartFrom Beginning(); + public static ChangeFeedStartFrom Beginning(FeedRange feedRange); + public static ChangeFeedStartFrom ContinuationToken(string continuationToken); + public static ChangeFeedStartFrom Now(); + public static ChangeFeedStartFrom Now(FeedRange feedRange); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc, FeedRange feedRange); + } + public sealed class ClientEncryptionIncludedPath + { + public ClientEncryptionIncludedPath(); + public string ClientEncryptionKeyId { get; set; } + public string EncryptionAlgorithm { get; set; } + public string EncryptionType { get; set; } + public string Path { get; set; } + } + public abstract class ClientEncryptionKey + { + protected ClientEncryptionKey(); + public abstract string Id { get; } + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(ClientEncryptionKeyProperties clientEncryptionKeyProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class ClientEncryptionKeyProperties : IEquatable + { + protected ClientEncryptionKeyProperties(); + public ClientEncryptionKeyProperties(string id, string encryptionAlgorithm, byte[] wrappedDataEncryptionKey, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata); + public Nullable CreatedTime { get; } + public string EncryptionAlgorithm { get; } + public EncryptionKeyWrapMetadata EncryptionKeyWrapMetadata { get; } + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public virtual string SelfLink { get; } + public byte[] WrappedDataEncryptionKey { get; } + public bool Equals(ClientEncryptionKeyProperties other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public class ClientEncryptionKeyResponse : Response + { + protected ClientEncryptionKeyResponse(); + public override string ActivityId { get; } + public virtual ClientEncryptionKey ClientEncryptionKey { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ClientEncryptionKeyProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ClientEncryptionKey (ClientEncryptionKeyResponse response); + } + public sealed class ClientEncryptionPolicy + { + public ClientEncryptionPolicy(IEnumerable includedPaths); + public ClientEncryptionPolicy(IEnumerable includedPaths, int policyFormatVersion); + public IEnumerable IncludedPaths { get; } + public int PolicyFormatVersion { get; } + } + public sealed class CompositePath + { + public CompositePath(); + public CompositePathSortOrder Order { get; set; } + public string Path { get; set; } + } + public enum CompositePathSortOrder + { + Ascending = 0, + Descending = 1, + } + public sealed class ComputedProperty + { + public ComputedProperty(); + public string Name { get; set; } + public string Query { get; set; } + } + public class ConflictProperties + { + public ConflictProperties(); + public string Id { get; } + public OperationKind OperationKind { get; } + public string SelfLink { get; } + } + public enum ConflictResolutionMode + { + Custom = 1, + LastWriterWins = 0, + } + public class ConflictResolutionPolicy + { + public ConflictResolutionPolicy(); + public ConflictResolutionMode Mode { get; set; } + public string ResolutionPath { get; set; } + public string ResolutionProcedure { get; set; } + } + public abstract class Conflicts + { + protected Conflicts(); + public abstract Task DeleteAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetConflictQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract T ReadConflictContent(ConflictProperties conflict); + public abstract Task> ReadCurrentAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum ConnectionMode + { + Direct = 1, + Gateway = 0, + } + public enum ConsistencyLevel + { + BoundedStaleness = 1, + ConsistentPrefix = 4, + Eventual = 3, + Session = 2, + Strong = 0, + } + public abstract class Container + { + protected Container(); + public abstract Conflicts Conflicts { get; } + public abstract Database Database { get; } + public abstract string Id { get; } + public abstract Scripts Scripts { get; } + public abstract Task> CreateItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey); + public abstract Task DeleteAllItemsByPartitionKeyStreamAsync(PartitionKey partitionKey, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> DeleteItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ChangeFeedEstimator GetChangeFeedEstimator(string processorName, Container leaseContainer); + public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder(string processorName, Container.ChangesEstimationHandler estimationDelegate, Nullable estimationPeriod=default(Nullable)); + public abstract FeedIterator GetChangeFeedIterator(ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions=null); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangeFeedStreamHandler onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint(string processorName, Container.ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint(string processorName, Container.ChangeFeedHandlerWithManualCheckpoint onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangeFeedHandler onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangesHandler onChangesDelegate); + public abstract FeedIterator GetChangeFeedStreamIterator(ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions=null); + public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract IOrderedQueryable GetItemLinqQueryable(bool allowSynchronousQueryExecution=false, string continuationToken=null, QueryRequestOptions requestOptions=null, CosmosLinqSerializerOptions linqSerializerOptions=null); + public abstract FeedIterator GetItemQueryIterator(FeedRange feedRange, QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(FeedRange feedRange, QueryDefinition queryDefinition, string continuationToken, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task> GetPartitionKeyRangesAsync(FeedRange feedRange, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> PatchItemAsync(string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task PatchItemStreamAsync(string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadManyItemsAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadManyItemsStreamAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerStreamAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReplaceItemAsync(T item, string id, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceItemStreamAsync(Stream streamPayload, string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> UpsertItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public delegate Task ChangeFeedHandlerWithManualCheckpoint(ChangeFeedProcessorContext context, IReadOnlyCollection changes, Func checkpointAsync, CancellationToken cancellationToken); + public delegate Task ChangeFeedHandler(ChangeFeedProcessorContext context, IReadOnlyCollection changes, CancellationToken cancellationToken); + public delegate Task ChangeFeedMonitorErrorDelegate(string leaseToken, Exception exception); + public delegate Task ChangeFeedMonitorLeaseAcquireDelegate(string leaseToken); + public delegate Task ChangeFeedMonitorLeaseReleaseDelegate(string leaseToken); + public delegate Task ChangeFeedStreamHandler(ChangeFeedProcessorContext context, Stream changes, CancellationToken cancellationToken); + public delegate Task ChangeFeedStreamHandlerWithManualCheckpoint(ChangeFeedProcessorContext context, Stream changes, Func checkpointAsync, CancellationToken cancellationToken); + public delegate Task ChangesEstimationHandler(long estimatedPendingChanges, CancellationToken cancellationToken); + public delegate Task ChangesHandler(IReadOnlyCollection changes, CancellationToken cancellationToken); + } + public class ContainerProperties + { + public ContainerProperties(); + public ContainerProperties(string id, IReadOnlyList partitionKeyPaths); + public ContainerProperties(string id, string partitionKeyPath); + public Nullable AnalyticalStoreTimeToLiveInSeconds { get; set; } + public ChangeFeedPolicy ChangeFeedPolicy { get; set; } + public ClientEncryptionPolicy ClientEncryptionPolicy { get; set; } + public Collection ComputedProperties { get; set; } + public ConflictResolutionPolicy ConflictResolutionPolicy { get; set; } + public Nullable DefaultTimeToLive { get; set; } + public string ETag { get; } + public GeospatialConfig GeospatialConfig { get; set; } + public string Id { get; set; } + public IndexingPolicy IndexingPolicy { get; set; } + public Nullable LastModified { get; } + public Nullable PartitionKeyDefinitionVersion { get; set; } + public string PartitionKeyPath { get; set; } + public IReadOnlyList PartitionKeyPaths { get; set; } + public string SelfLink { get; } + public string TimeToLivePropertyPath { get; set; } + public UniqueKeyPolicy UniqueKeyPolicy { get; set; } + } + public class ContainerRequestOptions : RequestOptions + { + public ContainerRequestOptions(); + public bool PopulateQuotaInfo { get; set; } + } + public class ContainerResponse : Response + { + protected ContainerResponse(); + public override string ActivityId { get; } + public virtual Container Container { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ContainerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Container (ContainerResponse response); + } + public class CosmosClient : IDisposable + { + protected CosmosClient(); + public CosmosClient(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential, CosmosClientOptions clientOptions=null); + public CosmosClient(string accountEndpoint, TokenCredential tokenCredential, CosmosClientOptions clientOptions=null); + public CosmosClient(string connectionString, CosmosClientOptions clientOptions=null); + public CosmosClient(string accountEndpoint, string authKeyOrResourceToken, CosmosClientOptions clientOptions=null); + public virtual CosmosClientOptions ClientOptions { get; } + public virtual Uri Endpoint { get; } + public virtual CosmosResponseFactory ResponseFactory { get; } + public static Task CreateAndInitializeAsync(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string accountEndpoint, TokenCredential tokenCredential, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string connectionString, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string accountEndpoint, string authKeyOrResourceToken, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseStreamAsync(DatabaseProperties databaseProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual Container GetContainer(string databaseId, string containerId); + public virtual Database GetDatabase(string id); + public virtual FeedIterator GetDatabaseQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual Task ReadAccountAsync(); + } + public class CosmosClientOptions + { + public CosmosClientOptions(); + public bool AllowBulkExecution { get; set; } + public string ApplicationName { get; set; } + public IReadOnlyList ApplicationPreferredRegions { get; set; } + public string ApplicationRegion { get; set; } + public ConnectionMode ConnectionMode { get; set; } + public Nullable ConsistencyLevel { get; set; } + public CosmosClientTelemetryOptions CosmosClientTelemetryOptions { get; set; } + public Collection CustomHandlers { get; } + public Nullable EnableContentResponseOnWrite { get; set; } + public bool EnableTcpConnectionEndpointRediscovery { get; set; } + public int GatewayModeMaxConnectionLimit { get; set; } + public Func HttpClientFactory { get; set; } + public Nullable IdleTcpConnectionTimeout { get; set; } + public bool LimitToEndpoint { get; set; } + public Nullable MaxRequestsPerTcpConnection { get; set; } + public Nullable MaxRetryAttemptsOnRateLimitedRequests { get; set; } + public Nullable MaxRetryWaitTimeOnRateLimitedRequests { get; set; } + public Nullable MaxTcpConnectionsPerEndpoint { get; set; } + public Nullable OpenTcpConnectionTimeout { get; set; } + public Nullable PortReuseMode { get; set; } + public TimeSpan RequestTimeout { get; set; } + public CosmosSerializer Serializer { get; set; } + public CosmosSerializationOptions SerializerOptions { get; set; } + public Func ServerCertificateCustomValidationCallback { get; set; } + public Nullable TokenCredentialBackgroundRefreshInterval { get; set; } + public IWebProxy WebProxy { get; set; } + } + public class CosmosClientTelemetryOptions + { + public CosmosClientTelemetryOptions(); + public CosmosThresholdOptions CosmosThresholdOptions { get; set; } + public bool DisableDistributedTracing { get; set; } + public bool DisableSendingMetricsToService { get; set; } + } + public abstract class CosmosDiagnostics + { + protected CosmosDiagnostics(); + public virtual TimeSpan GetClientElapsedTime(); + public abstract IReadOnlyList> GetContactedRegions(); + public virtual int GetFailedRequestCount(); + public virtual ServerSideCumulativeMetrics GetQueryMetrics(); + public virtual Nullable GetStartTimeUtc(); + public abstract override string ToString(); + } + public class CosmosException : Exception + { + public CosmosException(string message, HttpStatusCode statusCode, int subStatusCode, string activityId, double requestCharge); + public virtual string ActivityId { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual Headers Headers { get; } + public override string Message { get; } + public virtual double RequestCharge { get; } + public virtual string ResponseBody { get; } + public virtual Nullable RetryAfter { get; } + public override string StackTrace { get; } + public virtual HttpStatusCode StatusCode { get; } + public virtual int SubStatusCode { get; } + public override string ToString(); + public virtual bool TryGetHeader(string headerName, out string value); + } + public sealed class CosmosLinqSerializerOptions + { + public CosmosLinqSerializerOptions(); + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public class CosmosOperationCanceledException : OperationCanceledException + { + public CosmosOperationCanceledException(OperationCanceledException originalException, CosmosDiagnostics diagnostics); + protected CosmosOperationCanceledException(SerializationInfo info, StreamingContext context); + public override IDictionary Data { get; } + public CosmosDiagnostics Diagnostics { get; } + public override string HelpLink { get; set; } + public override string Message { get; } + public override string Source { get; set; } + public override string StackTrace { get; } + public override Exception GetBaseException(); + public override void GetObjectData(SerializationInfo info, StreamingContext context); + public override string ToString(); + } + public enum CosmosPropertyNamingPolicy + { + CamelCase = 1, + Default = 0, + } + public abstract class CosmosResponseFactory + { + protected CosmosResponseFactory(); + public abstract FeedResponse CreateItemFeedResponse(ResponseMessage responseMessage); + public abstract ItemResponse CreateItemResponse(ResponseMessage responseMessage); + public abstract StoredProcedureExecuteResponse CreateStoredProcedureExecuteResponse(ResponseMessage responseMessage); + } + public sealed class CosmosSerializationOptions + { + public CosmosSerializationOptions(); + public bool IgnoreNullValues { get; set; } + public bool Indented { get; set; } + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public abstract class CosmosSerializer + { + protected CosmosSerializer(); + public abstract T FromStream(Stream stream); + public abstract Stream ToStream(T input); + } + public class CosmosThresholdOptions + { + public CosmosThresholdOptions(); + public TimeSpan NonPointOperationLatencyThreshold { get; set; } + public TimeSpan PointOperationLatencyThreshold { get; set; } + } + public abstract class Database + { + protected Database(); + public abstract CosmosClient Client { get; } + public abstract string Id { get; } + public abstract Task CreateClientEncryptionKeyAsync(ClientEncryptionKeyProperties clientEncryptionKeyProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ContainerBuilder DefineContainer(string name, string partitionKeyPath); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ClientEncryptionKey GetClientEncryptionKey(string id); + public abstract FeedIterator GetClientEncryptionKeyQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Container GetContainer(string id); + public abstract FeedIterator GetContainerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract User GetUser(string id); + public abstract FeedIterator GetUserQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class DatabaseProperties + { + public DatabaseProperties(); + public DatabaseProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class DatabaseResponse : Response + { + protected DatabaseResponse(); + public override string ActivityId { get; } + public virtual Database Database { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override DatabaseProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Database (DatabaseResponse response); + } + public enum DataType + { + LineString = 3, + MultiPolygon = 5, + Number = 0, + Point = 2, + Polygon = 4, + String = 1, + } + public class DedicatedGatewayRequestOptions + { + public DedicatedGatewayRequestOptions(); + public Nullable BypassIntegratedCache { get; set; } + public Nullable MaxIntegratedCacheStaleness { get; set; } + } + public class EncryptionKeyWrapMetadata : IEquatable + { + public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source); + public EncryptionKeyWrapMetadata(string type, string name, string value, string algorithm); + public string Algorithm { get; } + public string Name { get; } + public string Type { get; } + public string Value { get; } + public bool Equals(EncryptionKeyWrapMetadata other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class ExcludedPath + { + public ExcludedPath(); + public string Path { get; set; } + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task> ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedRange + { + protected FeedRange(); + public static FeedRange FromJsonString(string toStringValue); + public static FeedRange FromPartitionKey(PartitionKey partitionKey); + public abstract string ToJsonString(); + } + public abstract class FeedResponse : IEnumerable, IEnumerable + { + protected FeedResponse(); + public override string ActivityId { get; } + public abstract string ContinuationToken { get; } + public abstract int Count { get; } + public override string ETag { get; } + public abstract string IndexMetrics { get; } + public override double RequestCharge { get; } + public abstract IEnumerator GetEnumerator(); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public sealed class GeospatialConfig + { + public GeospatialConfig(); + public GeospatialConfig(GeospatialType geospatialType); + public GeospatialType GeospatialType { get; set; } + } + public enum GeospatialType + { + Geography = 0, + Geometry = 1, + } + public class Headers : IEnumerable + { + public Headers(); + public virtual string ActivityId { get; } + public virtual string ContentLength { get; set; } + public virtual string ContentType { get; } + public virtual string ContinuationToken { get; } + public virtual string ETag { get; } + public virtual string this[string headerName] { get; set; } + public virtual string Location { get; } + public virtual double RequestCharge { get; } + public virtual string Session { get; } + public virtual void Add(string headerName, IEnumerable values); + public virtual void Add(string headerName, string value); + public virtual string[] AllKeys(); + public virtual string Get(string headerName); + public virtual IEnumerator GetEnumerator(); + public virtual T GetHeaderValue(string headerName); + public virtual string GetValueOrDefault(string headerName); + public virtual void Remove(string headerName); + public virtual void Set(string headerName, string value); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + public virtual bool TryGetValue(string headerName, out string value); + } + public sealed class IncludedPath + { + public IncludedPath(); + public string Path { get; set; } + } + public enum IndexingDirective + { + Default = 0, + Exclude = 2, + Include = 1, + } + public enum IndexingMode + { + Consistent = 0, + Lazy = 1, + None = 2, + } + public sealed class IndexingPolicy + { + public IndexingPolicy(); + public bool Automatic { get; set; } + public Collection> CompositeIndexes { get; } + public Collection ExcludedPaths { get; } + public Collection IncludedPaths { get; } + public IndexingMode IndexingMode { get; set; } + public Collection SpatialIndexes { get; } + } + public enum IndexKind + { + Hash = 0, + Range = 1, + Spatial = 2, + } + public class ItemRequestOptions : RequestOptions + { + public ItemRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public DedicatedGatewayRequestOptions DedicatedGatewayRequestOptions { get; set; } + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + public IEnumerable PostTriggers { get; set; } + public IEnumerable PreTriggers { get; set; } + public string SessionToken { get; set; } + } + public class ItemResponse : Response + { + protected ItemResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public override HttpStatusCode StatusCode { get; } + } + public enum OperationKind + { + Create = 1, + Delete = 3, + Invalid = 0, + Read = 4, + Replace = 2, + } + public struct PartitionKey : IEquatable + { + public static readonly PartitionKey None; + public static readonly PartitionKey Null; + public static readonly string SystemKeyName; + public static readonly string SystemKeyPath; + public PartitionKey(bool partitionKeyValue); + public PartitionKey(double partitionKeyValue); + public PartitionKey(string partitionKeyValue); + public bool Equals(PartitionKey other); + public override bool Equals(object obj); + public override int GetHashCode(); + public static bool operator ==(PartitionKey left, PartitionKey right); + public static bool operator !=(PartitionKey left, PartitionKey right); + public override string ToString(); + } + public sealed class PartitionKeyBuilder + { + public PartitionKeyBuilder(); + public PartitionKeyBuilder Add(bool val); + public PartitionKeyBuilder Add(double val); + public PartitionKeyBuilder Add(string val); + public PartitionKeyBuilder AddNoneType(); + public PartitionKeyBuilder AddNullValue(); + public PartitionKey Build(); + } + public enum PartitionKeyDefinitionVersion + { + V1 = 1, + V2 = 2, + } + public sealed class PatchItemRequestOptions : ItemRequestOptions + { + public PatchItemRequestOptions(); + public string FilterPredicate { get; set; } + } + public abstract class PatchOperation + { + protected PatchOperation(); + public virtual string From { get; set; } + public abstract PatchOperationType OperationType { get; } + public abstract string Path { get; } + public static PatchOperation Add(string path, T value); + public static PatchOperation Increment(string path, double value); + public static PatchOperation Increment(string path, long value); + public static PatchOperation Move(string from, string path); + public static PatchOperation Remove(string path); + public static PatchOperation Replace(string path, T value); + public static PatchOperation Set(string path, T value); + public virtual bool TrySerializeValueParameter(CosmosSerializer cosmosSerializer, out Stream valueParam); + } + public enum PatchOperationType + { + Add = 0, + Increment = 4, + Move = 5, + Remove = 1, + Replace = 2, + Set = 3, + } + public abstract class PatchOperation : PatchOperation + { + protected PatchOperation(); + public abstract T Value { get; } + } + public abstract class Permission + { + protected Permission(); + public abstract string Id { get; } + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadAsync(Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum PermissionMode : byte + { + All = (byte)2, + Read = (byte)1, + } + public class PermissionProperties + { + public PermissionProperties(string id, PermissionMode permissionMode, Container container, PartitionKey resourcePartitionKey, string itemId); + public PermissionProperties(string id, PermissionMode permissionMode, Container container, Nullable resourcePartitionKey=default(Nullable)); + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public PermissionMode PermissionMode { get; } + public Nullable ResourcePartitionKey { get; set; } + public string ResourceUri { get; } + public string SelfLink { get; } + public string Token { get; } + } + public class PermissionResponse : Response + { + protected PermissionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public virtual Permission Permission { get; } + public override double RequestCharge { get; } + public override PermissionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Permission (PermissionResponse response); + } + public enum PortReuseMode + { + PrivatePortPool = 1, + ReuseUnicastPort = 0, + } + public enum PriorityLevel + { + High = 1, + Low = 2, + } + public class QueryDefinition + { + public QueryDefinition(string query); + public string QueryText { get; } + public IReadOnlyList> GetQueryParameters(); + public QueryDefinition WithParameter(string name, object value); + public QueryDefinition WithParameterStream(string name, Stream valueStream); + } + public class QueryRequestOptions : RequestOptions + { + public QueryRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public DedicatedGatewayRequestOptions DedicatedGatewayRequestOptions { get; set; } + public Nullable EnableLowPrecisionOrderBy { get; set; } + public bool EnableOptimisticDirectExecution { get; set; } + public Nullable EnableScanInQuery { get; set; } + public Nullable MaxBufferedItemCount { get; set; } + public Nullable MaxConcurrency { get; set; } + public Nullable MaxItemCount { get; set; } + public Nullable PartitionKey { get; set; } + public Nullable PopulateIndexMetrics { get; set; } + public Nullable ResponseContinuationTokenLimitInKb { get; set; } + public string SessionToken { get; set; } + } + public class ReadManyRequestOptions : RequestOptions + { + public ReadManyRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public static class Regions + { + public const string AustraliaCentral = "Australia Central"; + public const string AustraliaCentral2 = "Australia Central 2"; + public const string AustraliaEast = "Australia East"; + public const string AustraliaSoutheast = "Australia Southeast"; + public const string BrazilSouth = "Brazil South"; + public const string BrazilSoutheast = "Brazil Southeast"; + public const string CanadaCentral = "Canada Central"; + public const string CanadaEast = "Canada East"; + public const string CentralIndia = "Central India"; + public const string CentralUS = "Central US"; + public const string CentralUSEUAP = "Central US EUAP"; + public const string ChinaEast = "China East"; + public const string ChinaEast2 = "China East 2"; + public const string ChinaEast3 = "China East 3"; + public const string ChinaNorth = "China North"; + public const string ChinaNorth2 = "China North 2"; + public const string ChinaNorth3 = "China North 3"; + public const string EastAsia = "East Asia"; + public const string EastUS = "East US"; + public const string EastUS2 = "East US 2"; + public const string EastUS2EUAP = "East US 2 EUAP"; + public const string EastUSSLV = "East US SLV"; + public const string FranceCentral = "France Central"; + public const string FranceSouth = "France South"; + public const string GermanyCentral = "Germany Central"; + public const string GermanyNorth = "Germany North"; + public const string GermanyNortheast = "Germany Northeast"; + public const string GermanyWestCentral = "Germany West Central"; + public const string IsraelCentral = "Israel Central"; + public const string ItalyNorth = "Italy North"; + public const string JapanEast = "Japan East"; + public const string JapanWest = "Japan West"; + public const string JioIndiaCentral = "Jio India Central"; + public const string JioIndiaWest = "Jio India West"; + public const string KoreaCentral = "Korea Central"; + public const string KoreaSouth = "Korea South"; + public const string MalaysiaSouth = "Malaysia South"; + public const string NorthCentralUS = "North Central US"; + public const string NorthEurope = "North Europe"; + public const string NorwayEast = "Norway East"; + public const string NorwayWest = "Norway West"; + public const string PolandCentral = "Poland Central"; + public const string QatarCentral = "Qatar Central"; + public const string SouthAfricaNorth = "South Africa North"; + public const string SouthAfricaWest = "South Africa West"; + public const string SouthCentralUS = "South Central US"; + public const string SoutheastAsia = "Southeast Asia"; + public const string SouthIndia = "South India"; + public const string SwedenCentral = "Sweden Central"; + public const string SwedenSouth = "Sweden South"; + public const string SwitzerlandNorth = "Switzerland North"; + public const string SwitzerlandWest = "Switzerland West"; + public const string UAECentral = "UAE Central"; + public const string UAENorth = "UAE North"; + public const string UKSouth = "UK South"; + public const string UKWest = "UK West"; + public const string USDoDCentral = "USDoD Central"; + public const string USDoDEast = "USDoD East"; + public const string USGovArizona = "USGov Arizona"; + public const string USGovTexas = "USGov Texas"; + public const string USGovVirginia = "USGov Virginia"; + public const string USNatEast = "USNat East"; + public const string USNatWest = "USNat West"; + public const string USSecEast = "USSec East"; + public const string USSecWest = "USSec West"; + public const string WestCentralUS = "West Central US"; + public const string WestEurope = "West Europe"; + public const string WestIndia = "West India"; + public const string WestUS = "West US"; + public const string WestUS2 = "West US 2"; + public const string WestUS3 = "West US 3"; + } + public abstract class RequestHandler + { + protected RequestHandler(); + public RequestHandler InnerHandler { get; set; } + public virtual Task SendAsync(RequestMessage request, CancellationToken cancellationToken); + } + public class RequestMessage : IDisposable + { + public RequestMessage(); + public RequestMessage(HttpMethod method, Uri requestUri); + public virtual Stream Content { get; set; } + public virtual Headers Headers { get; } + public virtual HttpMethod Method { get; } + public virtual Dictionary Properties { get; } + public virtual Uri RequestUri { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + } + public class RequestOptions + { + public RequestOptions(); + public Action AddRequestHeaders { get; set; } + public CosmosThresholdOptions CosmosThresholdOptions { get; set; } + public List ExcludeRegions { get; set; } + public string IfMatchEtag { get; set; } + public string IfNoneMatchEtag { get; set; } + public Nullable PriorityLevel { get; set; } + public IReadOnlyDictionary Properties { get; set; } + public RequestOptions ShallowCopy(); + } + public class ResponseMessage : IDisposable + { + public ResponseMessage(); + public ResponseMessage(HttpStatusCode statusCode, RequestMessage requestMessage=null, string errorMessage=null); + public virtual Stream Content { get; set; } + public virtual string ContinuationToken { get; } + public virtual CosmosDiagnostics Diagnostics { get; set; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public string IndexMetrics { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual RequestMessage RequestMessage { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual ResponseMessage EnsureSuccessStatusCode(); + } + public abstract class Response + { + protected Response(); + public abstract string ActivityId { get; } + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract string ETag { get; } + public abstract Headers Headers { get; } + public abstract double RequestCharge { get; } + public abstract T Resource { get; } + public abstract HttpStatusCode StatusCode { get; } + public static implicit operator T (Response response); + } + public abstract class ServerSideCumulativeMetrics + { + protected ServerSideCumulativeMetrics(); + public abstract ServerSideMetrics CumulativeMetrics { get; } + public abstract IReadOnlyList PartitionedMetrics { get; } + } + public abstract class ServerSideMetrics + { + protected ServerSideMetrics(); + public abstract TimeSpan DocumentLoadTime { get; } + public abstract TimeSpan DocumentWriteTime { get; } + public abstract double IndexHitRatio { get; } + public abstract TimeSpan IndexLookupTime { get; } + public abstract long OutputDocumentCount { get; } + public abstract long OutputDocumentSize { get; } + public abstract TimeSpan QueryPreparationTime { get; } + public abstract long RetrievedDocumentCount { get; } + public abstract long RetrievedDocumentSize { get; } + public abstract TimeSpan RuntimeExecutionTime { get; } + public abstract TimeSpan TotalTime { get; } + public abstract TimeSpan VMExecutionTime { get; } + } + public abstract class ServerSidePartitionedMetrics + { + protected ServerSidePartitionedMetrics(); + public abstract string FeedRange { get; } + public abstract Nullable PartitionKeyRangeId { get; } + public abstract ServerSideMetrics ServerSideMetrics { get; } + } + public sealed class SpatialPath + { + public SpatialPath(); + public BoundingBoxProperties BoundingBox { get; set; } + public string Path { get; set; } + public Collection SpatialTypes { get; } + } + public enum SpatialType + { + LineString = 1, + MultiPolygon = 3, + Point = 0, + Polygon = 2, + } + public class ThroughputProperties + { + public Nullable AutoscaleMaxThroughput { get; } + public string ETag { get; } + public Nullable LastModified { get; } + public string SelfLink { get; } + public Nullable Throughput { get; } + public static ThroughputProperties CreateAutoscaleThroughput(int autoscaleMaxThroughput); + public static ThroughputProperties CreateManualThroughput(int throughput); + } + public class ThroughputResponse : Response + { + protected ThroughputResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public Nullable IsReplacePending { get; } + public Nullable MinThroughput { get; } + public override double RequestCharge { get; } + public override ThroughputProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ThroughputProperties (ThroughputResponse response); + } + public abstract class TransactionalBatch + { + protected TransactionalBatch(); + public abstract TransactionalBatch CreateItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch CreateItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch DeleteItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract Task ExecuteAsync(TransactionalBatchRequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch PatchItem(string id, IReadOnlyList patchOperations, TransactionalBatchPatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReadItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItemStream(string id, Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItem(string id, T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + } + public class TransactionalBatchItemRequestOptions : RequestOptions + { + public TransactionalBatchItemRequestOptions(); + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + } + public class TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual string ETag { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual Stream ResourceStream { get; } + public virtual TimeSpan RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + } + public class TransactionalBatchOperationResult : TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual T Resource { get; set; } + } + public class TransactionalBatchPatchItemRequestOptions : TransactionalBatchItemRequestOptions + { + public TransactionalBatchPatchItemRequestOptions(); + public string FilterPredicate { get; set; } + } + public class TransactionalBatchRequestOptions : RequestOptions + { + public TransactionalBatchRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public class TransactionalBatchResponse : IDisposable, IEnumerable, IEnumerable, IReadOnlyCollection, IReadOnlyList + { + protected TransactionalBatchResponse(); + public virtual string ActivityId { get; } + public virtual int Count { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual TransactionalBatchOperationResult this[int index] { get; } + public virtual double RequestCharge { get; } + public virtual Nullable RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual IEnumerator GetEnumerator(); + public virtual TransactionalBatchOperationResult GetOperationResultAtIndex(int index); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public class UniqueKey + { + public UniqueKey(); + public Collection Paths { get; } + } + public sealed class UniqueKeyPolicy + { + public UniqueKeyPolicy(); + public Collection UniqueKeys { get; } + } + public abstract class User + { + protected User(); + public abstract string Id { get; } + public abstract Task CreatePermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Permission GetPermission(string id); + public abstract FeedIterator GetPermissionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetPermissionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(UserProperties userProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertPermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class UserProperties + { + protected UserProperties(); + public UserProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class UserResponse : Response + { + protected UserResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public virtual User User { get; } + public static implicit operator User (UserResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Fluent +{ + public class ChangeFeedPolicyDefinition + { + public ContainerBuilder Attach(); + } + public sealed class ClientEncryptionPolicyDefinition + { + public ContainerBuilder Attach(); + public ClientEncryptionPolicyDefinition WithIncludedPath(ClientEncryptionIncludedPath path); + } + public class CompositeIndexDefinition + { + public T Attach(); + public CompositeIndexDefinition Path(string path); + public CompositeIndexDefinition Path(string path, CompositePathSortOrder sortOrder); + } + public class ComputedPropertiesDefinition + { + public T Attach(); + public ComputedPropertiesDefinition WithComputedProperty(string name, string query); + } + public class ConflictResolutionDefinition + { + public ContainerBuilder Attach(); + public ConflictResolutionDefinition WithCustomStoredProcedureResolution(string conflictResolutionProcedure); + public ConflictResolutionDefinition WithLastWriterWinsResolution(string conflictResolutionPath); + } + public class ContainerBuilder : ContainerDefinition + { + protected ContainerBuilder(); + public ContainerBuilder(Database database, string name, string partitionKeyPath); + public new ContainerProperties Build(); + public Task CreateAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public ChangeFeedPolicyDefinition WithChangeFeedPolicy(TimeSpan retention); + public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(); + public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(int policyFormatVersion); + public ConflictResolutionDefinition WithConflictResolution(); + public UniqueKeyDefinition WithUniqueKey(); + } + public abstract class ContainerDefinition where T : ContainerDefinition + { + public ContainerDefinition(); + public ContainerProperties Build(); + public ComputedPropertiesDefinition WithComputedProperties(); + public T WithDefaultTimeToLive(int defaultTtlInSeconds); + public T WithDefaultTimeToLive(TimeSpan defaultTtlTimeSpan); + public IndexingPolicyDefinition WithIndexingPolicy(); + public T WithPartitionKeyDefinitionVersion(PartitionKeyDefinitionVersion partitionKeyDefinitionVersion); + public T WithTimeToLivePropertyPath(string propertyPath); + } + public class CosmosClientBuilder + { + public CosmosClientBuilder(string connectionString); + public CosmosClientBuilder(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential); + public CosmosClientBuilder(string accountEndpoint, TokenCredential tokenCredential); + public CosmosClientBuilder(string accountEndpoint, string authKeyOrResourceToken); + public CosmosClientBuilder AddCustomHandlers(params RequestHandler[] customHandlers); + public CosmosClient Build(); + public Task BuildAndInitializeAsync(IReadOnlyList> containers, CancellationToken cancellationToken=default(CancellationToken)); + public CosmosClientBuilder WithApplicationName(string applicationName); + public CosmosClientBuilder WithApplicationPreferredRegions(IReadOnlyList applicationPreferredRegions); + public CosmosClientBuilder WithApplicationRegion(string applicationRegion); + public CosmosClientBuilder WithBulkExecution(bool enabled); + public CosmosClientBuilder WithClientTelemetryOptions(CosmosClientTelemetryOptions options); + public CosmosClientBuilder WithConnectionModeDirect(); + public CosmosClientBuilder WithConnectionModeDirect(Nullable idleTcpConnectionTimeout=default(Nullable), Nullable openTcpConnectionTimeout=default(Nullable), Nullable maxRequestsPerTcpConnection=default(Nullable), Nullable maxTcpConnectionsPerEndpoint=default(Nullable), Nullable portReuseMode=default(Nullable), Nullable enableTcpConnectionEndpointRediscovery=default(Nullable)); + public CosmosClientBuilder WithConnectionModeGateway(Nullable maxConnectionLimit=default(Nullable), IWebProxy webProxy=null); + public CosmosClientBuilder WithConsistencyLevel(ConsistencyLevel consistencyLevel); + public CosmosClientBuilder WithContentResponseOnWrite(bool contentResponseOnWrite); + public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSerializer); + public CosmosClientBuilder WithHttpClientFactory(Func httpClientFactory); + public CosmosClientBuilder WithLimitToEndpoint(bool limitToEndpoint); + public CosmosClientBuilder WithRequestTimeout(TimeSpan requestTimeout); + public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions); + public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeOnThrottledRequests, int maxRetryAttemptsOnThrottledRequests); + } + public class IndexingPolicyDefinition + { + public IndexingPolicyDefinition(); + public T Attach(); + public IndexingPolicyDefinition WithAutomaticIndexing(bool enabled); + public CompositeIndexDefinition> WithCompositeIndex(); + public PathsDefinition> WithExcludedPaths(); + public PathsDefinition> WithIncludedPaths(); + public IndexingPolicyDefinition WithIndexingMode(IndexingMode indexingMode); + public SpatialIndexDefinition> WithSpatialIndex(); + } + public class PathsDefinition + { + public T Attach(); + public PathsDefinition Path(string path); + } + public class SpatialIndexDefinition + { + public T Attach(); + public SpatialIndexDefinition Path(string path); + public SpatialIndexDefinition Path(string path, params SpatialType[] spatialTypes); + } + public class UniqueKeyDefinition + { + public ContainerBuilder Attach(); + public UniqueKeyDefinition Path(string path); + } +} +namespace Microsoft.Azure.Cosmos.Linq +{ + public static class CosmosLinq + { + public static object InvokeUserDefinedFunction(string udfName, params object[] arguments); + } + public static class CosmosLinqExtensions + { + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> CountAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static bool IsArray(this object obj); + public static bool IsBool(this object obj); + public static bool IsDefined(this object obj); + public static bool IsNull(this object obj); + public static bool IsNumber(this object obj); + public static bool IsObject(this object obj); + public static bool IsPrimitive(this object obj); + public static bool IsString(this object obj); + public static Task> MaxAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> MinAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static bool RegexMatch(this object obj, string regularExpression); + public static bool RegexMatch(this object obj, string regularExpression, string searchModifier); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static FeedIterator ToFeedIterator(this IQueryable query); + public static QueryDefinition ToQueryDefinition(this IQueryable query); + public static QueryDefinition ToQueryDefinition(this IQueryable query, IDictionary namedParameters); + public static FeedIterator ToStreamIterator(this IQueryable query); + } +} +namespace Microsoft.Azure.Cosmos.Scripts +{ + public abstract class Scripts + { + protected Scripts(); + public abstract Task CreateStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ExecuteStoredProcedureAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, Stream streamPayload, PartitionKey partitionKey, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetStoredProcedureQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class StoredProcedureExecuteResponse : Response + { + protected StoredProcedureExecuteResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public virtual string ScriptLog { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + } + public class StoredProcedureProperties + { + public StoredProcedureProperties(); + public StoredProcedureProperties(string id, string body); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class StoredProcedureRequestOptions : RequestOptions + { + public StoredProcedureRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public bool EnableScriptLogging { get; set; } + public string SessionToken { get; set; } + } + public class StoredProcedureResponse : Response + { + protected StoredProcedureResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override StoredProcedureProperties Resource { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator StoredProcedureProperties (StoredProcedureResponse response); + } + public enum TriggerOperation : short + { + All = (short)0, + Create = (short)1, + Delete = (short)3, + Replace = (short)4, + Update = (short)2, + Upsert = (short)5, + } + public class TriggerProperties + { + public TriggerProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + public TriggerOperation TriggerOperation { get; set; } + public TriggerType TriggerType { get; set; } + } + public class TriggerResponse : Response + { + protected TriggerResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override TriggerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator TriggerProperties (TriggerResponse response); + } + public enum TriggerType : byte + { + Post = (byte)1, + Pre = (byte)0, + } + public class UserDefinedFunctionProperties + { + public UserDefinedFunctionProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + } + public class UserDefinedFunctionResponse : Response + { + protected UserDefinedFunctionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserDefinedFunctionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator UserDefinedFunctionProperties (UserDefinedFunctionResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Spatial +{ + public sealed class BoundingBox : IEquatable + { + public BoundingBox(Position min, Position max); + public Position Max { get; } + public Position Min { get; } + public bool Equals(BoundingBox other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public abstract class Crs + { + protected Crs(CrsType type); + public static Crs Default { get; } + public CrsType Type { get; } + public static Crs Unspecified { get; } + public static LinkedCrs Linked(string href); + public static LinkedCrs Linked(string href, string type); + public static NamedCrs Named(string name); + } + public enum CrsType + { + Linked = 1, + Named = 0, + Unspecified = 2, + } + public abstract class Geometry + { + protected Geometry(GeometryType type, GeometryParams geometryParams); + public IDictionary AdditionalProperties { get; } + public BoundingBox BoundingBox { get; } + public Crs Crs { get; } + public GeometryType Type { get; } + public double Distance(Geometry to); + public override bool Equals(object obj); + public override int GetHashCode(); + public bool Intersects(Geometry geometry2); + public bool IsValid(); + public GeometryValidationResult IsValidDetailed(); + public bool Within(Geometry outer); + } + public class GeometryParams + { + public GeometryParams(); + public IDictionary AdditionalProperties { get; set; } + public BoundingBox BoundingBox { get; set; } + public Crs Crs { get; set; } + } + public enum GeometryShape + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public enum GeometryType + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public class GeometryValidationResult + { + public GeometryValidationResult(); + public bool IsValid { get; } + public string Reason { get; } + } + public sealed class LinearRing : IEquatable + { + public LinearRing(IList coordinates); + public ReadOnlyCollection Positions { get; } + public bool Equals(LinearRing other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LineString : Geometry, IEquatable + { + public LineString(IList coordinates); + public LineString(IList coordinates, GeometryParams geometryParams); + public ReadOnlyCollection Positions { get; } + public bool Equals(LineString other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LinkedCrs : Crs, IEquatable + { + public string Href { get; } + public string HrefType { get; } + public bool Equals(LinkedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class MultiPolygon : Geometry, IEquatable + { + public MultiPolygon(IList polygons); + public MultiPolygon(IList polygons, GeometryParams geometryParams); + public ReadOnlyCollection Polygons { get; } + public bool Equals(MultiPolygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class NamedCrs : Crs, IEquatable + { + public string Name { get; } + public bool Equals(NamedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Point : Geometry, IEquatable + { + public Point(Position position); + public Point(Position position, GeometryParams geometryParams); + public Point(double longitude, double latitude); + public Position Position { get; } + public bool Equals(Point other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Polygon : Geometry, IEquatable + { + public Polygon(IList rings); + public Polygon(IList rings, GeometryParams geometryParams); + public Polygon(IList externalRingPositions); + public ReadOnlyCollection Rings { get; } + public bool Equals(Polygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class PolygonCoordinates : IEquatable + { + public PolygonCoordinates(IList rings); + public ReadOnlyCollection Rings { get; } + public bool Equals(PolygonCoordinates other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Position : IEquatable + { + public Position(IList coordinates); + public Position(double longitude, double latitude); + public Position(double longitude, double latitude, Nullable altitude); + public Nullable Altitude { get; } + public ReadOnlyCollection Coordinates { get; } + public double Latitude { get; } + public double Longitude { get; } + public bool Equals(Position other); + public override bool Equals(object obj); + public override int GetHashCode(); + } +} diff --git a/Microsoft.Azure.Cosmos/contracts/API_3.37.1.txt b/Microsoft.Azure.Cosmos/contracts/API_3.37.1.txt new file mode 100644 index 0000000000..1c02b4efb2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/contracts/API_3.37.1.txt @@ -0,0 +1,1552 @@ +namespace Microsoft.Azure.Cosmos +{ + public class AccountConsistency + { + public AccountConsistency(); + public ConsistencyLevel DefaultConsistencyLevel { get; } + public int MaxStalenessIntervalInSeconds { get; } + public int MaxStalenessPrefix { get; } + } + public class AccountProperties + { + public AccountConsistency Consistency { get; } + public string ETag { get; } + public string Id { get; } + public IEnumerable ReadableRegions { get; } + public IEnumerable WritableRegions { get; } + } + public class AccountRegion + { + public AccountRegion(); + public string Endpoint { get; } + public string Name { get; } + } + public sealed class BoundingBoxProperties + { + public BoundingBoxProperties(); + public double Xmax { get; set; } + public double Xmin { get; set; } + public double Ymax { get; set; } + public double Ymin { get; set; } + } + public abstract class ChangeFeedEstimator + { + protected ChangeFeedEstimator(); + public abstract FeedIterator GetCurrentStateIterator(ChangeFeedEstimatorRequestOptions changeFeedEstimatorRequestOptions=null); + } + public sealed class ChangeFeedEstimatorRequestOptions + { + public ChangeFeedEstimatorRequestOptions(); + public Nullable MaxItemCount { get; set; } + } + public abstract class ChangeFeedMode + { + public static ChangeFeedMode Incremental { get; } + public static ChangeFeedMode LatestVersion { get; } + } + public abstract class ChangeFeedProcessor + { + protected ChangeFeedProcessor(); + public abstract Task StartAsync(); + public abstract Task StopAsync(); + } + public class ChangeFeedProcessorBuilder + { + public ChangeFeedProcessor Build(); + public ChangeFeedProcessorBuilder WithErrorNotification(Container.ChangeFeedMonitorErrorDelegate errorDelegate); + public ChangeFeedProcessorBuilder WithInstanceName(string instanceName); + public ChangeFeedProcessorBuilder WithLeaseAcquireNotification(Container.ChangeFeedMonitorLeaseAcquireDelegate acquireDelegate); + public ChangeFeedProcessorBuilder WithLeaseConfiguration(Nullable acquireInterval=default(Nullable), Nullable expirationInterval=default(Nullable), Nullable renewInterval=default(Nullable)); + public ChangeFeedProcessorBuilder WithLeaseContainer(Container leaseContainer); + public ChangeFeedProcessorBuilder WithLeaseReleaseNotification(Container.ChangeFeedMonitorLeaseReleaseDelegate releaseDelegate); + public ChangeFeedProcessorBuilder WithMaxItems(int maxItemCount); + public ChangeFeedProcessorBuilder WithPollInterval(TimeSpan pollInterval); + public ChangeFeedProcessorBuilder WithStartTime(DateTime startTime); + } + public abstract class ChangeFeedProcessorContext + { + protected ChangeFeedProcessorContext(); + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract Headers Headers { get; } + public abstract string LeaseToken { get; } + } + public sealed class ChangeFeedProcessorState + { + public ChangeFeedProcessorState(string leaseToken, long estimatedLag, string instanceName); + public long EstimatedLag { get; } + public string InstanceName { get; } + public string LeaseToken { get; } + } + public class ChangeFeedProcessorUserException : Exception + { + public ChangeFeedProcessorUserException(Exception originalException, ChangeFeedProcessorContext context); + protected ChangeFeedProcessorUserException(SerializationInfo info, StreamingContext context); + public ChangeFeedProcessorContext ChangeFeedProcessorContext { get; } + public override void GetObjectData(SerializationInfo info, StreamingContext context); + } + public sealed class ChangeFeedRequestOptions : RequestOptions + { + public ChangeFeedRequestOptions(); + public new string IfMatchEtag { get; set; } + public new string IfNoneMatchEtag { get; set; } + public Nullable PageSizeHint { get; set; } + } + public abstract class ChangeFeedStartFrom + { + public static ChangeFeedStartFrom Beginning(); + public static ChangeFeedStartFrom Beginning(FeedRange feedRange); + public static ChangeFeedStartFrom ContinuationToken(string continuationToken); + public static ChangeFeedStartFrom Now(); + public static ChangeFeedStartFrom Now(FeedRange feedRange); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc, FeedRange feedRange); + } + public sealed class ClientEncryptionIncludedPath + { + public ClientEncryptionIncludedPath(); + public string ClientEncryptionKeyId { get; set; } + public string EncryptionAlgorithm { get; set; } + public string EncryptionType { get; set; } + public string Path { get; set; } + } + public abstract class ClientEncryptionKey + { + protected ClientEncryptionKey(); + public abstract string Id { get; } + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(ClientEncryptionKeyProperties clientEncryptionKeyProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class ClientEncryptionKeyProperties : IEquatable + { + protected ClientEncryptionKeyProperties(); + public ClientEncryptionKeyProperties(string id, string encryptionAlgorithm, byte[] wrappedDataEncryptionKey, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata); + public Nullable CreatedTime { get; } + public string EncryptionAlgorithm { get; } + public EncryptionKeyWrapMetadata EncryptionKeyWrapMetadata { get; } + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public virtual string SelfLink { get; } + public byte[] WrappedDataEncryptionKey { get; } + public bool Equals(ClientEncryptionKeyProperties other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public class ClientEncryptionKeyResponse : Response + { + protected ClientEncryptionKeyResponse(); + public override string ActivityId { get; } + public virtual ClientEncryptionKey ClientEncryptionKey { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ClientEncryptionKeyProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ClientEncryptionKey (ClientEncryptionKeyResponse response); + } + public sealed class ClientEncryptionPolicy + { + public ClientEncryptionPolicy(IEnumerable includedPaths); + public ClientEncryptionPolicy(IEnumerable includedPaths, int policyFormatVersion); + public IEnumerable IncludedPaths { get; } + public int PolicyFormatVersion { get; } + } + public sealed class CompositePath + { + public CompositePath(); + public CompositePathSortOrder Order { get; set; } + public string Path { get; set; } + } + public enum CompositePathSortOrder + { + Ascending = 0, + Descending = 1, + } + public class ConflictProperties + { + public ConflictProperties(); + public string Id { get; } + public OperationKind OperationKind { get; } + public string SelfLink { get; } + } + public enum ConflictResolutionMode + { + Custom = 1, + LastWriterWins = 0, + } + public class ConflictResolutionPolicy + { + public ConflictResolutionPolicy(); + public ConflictResolutionMode Mode { get; set; } + public string ResolutionPath { get; set; } + public string ResolutionProcedure { get; set; } + } + public abstract class Conflicts + { + protected Conflicts(); + public abstract Task DeleteAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetConflictQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract T ReadConflictContent(ConflictProperties conflict); + public abstract Task> ReadCurrentAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum ConnectionMode + { + Direct = 1, + Gateway = 0, + } + public enum ConsistencyLevel + { + BoundedStaleness = 1, + ConsistentPrefix = 4, + Eventual = 3, + Session = 2, + Strong = 0, + } + public abstract class Container + { + protected Container(); + public abstract Conflicts Conflicts { get; } + public abstract Database Database { get; } + public abstract string Id { get; } + public abstract Scripts Scripts { get; } + public abstract Task> CreateItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey); + public abstract Task DeleteContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> DeleteItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ChangeFeedEstimator GetChangeFeedEstimator(string processorName, Container leaseContainer); + public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder(string processorName, Container.ChangesEstimationHandler estimationDelegate, Nullable estimationPeriod=default(Nullable)); + public abstract FeedIterator GetChangeFeedIterator(ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions=null); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangeFeedStreamHandler onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint(string processorName, Container.ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint(string processorName, Container.ChangeFeedHandlerWithManualCheckpoint onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangeFeedHandler onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangesHandler onChangesDelegate); + public abstract FeedIterator GetChangeFeedStreamIterator(ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions=null); + public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract IOrderedQueryable GetItemLinqQueryable(bool allowSynchronousQueryExecution=false, string continuationToken=null, QueryRequestOptions requestOptions=null, CosmosLinqSerializerOptions linqSerializerOptions=null); + public abstract FeedIterator GetItemQueryIterator(FeedRange feedRange, QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(FeedRange feedRange, QueryDefinition queryDefinition, string continuationToken, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task> PatchItemAsync(string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task PatchItemStreamAsync(string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadManyItemsAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadManyItemsStreamAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerStreamAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReplaceItemAsync(T item, string id, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceItemStreamAsync(Stream streamPayload, string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> UpsertItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public delegate Task ChangeFeedHandlerWithManualCheckpoint(ChangeFeedProcessorContext context, IReadOnlyCollection changes, Func checkpointAsync, CancellationToken cancellationToken); + public delegate Task ChangeFeedHandler(ChangeFeedProcessorContext context, IReadOnlyCollection changes, CancellationToken cancellationToken); + public delegate Task ChangeFeedMonitorErrorDelegate(string leaseToken, Exception exception); + public delegate Task ChangeFeedMonitorLeaseAcquireDelegate(string leaseToken); + public delegate Task ChangeFeedMonitorLeaseReleaseDelegate(string leaseToken); + public delegate Task ChangeFeedStreamHandler(ChangeFeedProcessorContext context, Stream changes, CancellationToken cancellationToken); + public delegate Task ChangeFeedStreamHandlerWithManualCheckpoint(ChangeFeedProcessorContext context, Stream changes, Func checkpointAsync, CancellationToken cancellationToken); + public delegate Task ChangesEstimationHandler(long estimatedPendingChanges, CancellationToken cancellationToken); + public delegate Task ChangesHandler(IReadOnlyCollection changes, CancellationToken cancellationToken); + } + public class ContainerProperties + { + public ContainerProperties(); + public ContainerProperties(string id, IReadOnlyList partitionKeyPaths); + public ContainerProperties(string id, string partitionKeyPath); + public Nullable AnalyticalStoreTimeToLiveInSeconds { get; set; } + public ClientEncryptionPolicy ClientEncryptionPolicy { get; set; } + public ConflictResolutionPolicy ConflictResolutionPolicy { get; set; } + public Nullable DefaultTimeToLive { get; set; } + public string ETag { get; } + public GeospatialConfig GeospatialConfig { get; set; } + public string Id { get; set; } + public IndexingPolicy IndexingPolicy { get; set; } + public Nullable LastModified { get; } + public Nullable PartitionKeyDefinitionVersion { get; set; } + public string PartitionKeyPath { get; set; } + public IReadOnlyList PartitionKeyPaths { get; set; } + public string SelfLink { get; } + public string TimeToLivePropertyPath { get; set; } + public UniqueKeyPolicy UniqueKeyPolicy { get; set; } + } + public class ContainerRequestOptions : RequestOptions + { + public ContainerRequestOptions(); + public bool PopulateQuotaInfo { get; set; } + } + public class ContainerResponse : Response + { + protected ContainerResponse(); + public override string ActivityId { get; } + public virtual Container Container { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ContainerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Container (ContainerResponse response); + } + public class CosmosClient : IDisposable + { + protected CosmosClient(); + public CosmosClient(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential, CosmosClientOptions clientOptions=null); + public CosmosClient(string accountEndpoint, TokenCredential tokenCredential, CosmosClientOptions clientOptions=null); + public CosmosClient(string connectionString, CosmosClientOptions clientOptions=null); + public CosmosClient(string accountEndpoint, string authKeyOrResourceToken, CosmosClientOptions clientOptions=null); + public virtual CosmosClientOptions ClientOptions { get; } + public virtual Uri Endpoint { get; } + public virtual CosmosResponseFactory ResponseFactory { get; } + public static Task CreateAndInitializeAsync(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string accountEndpoint, TokenCredential tokenCredential, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string connectionString, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string accountEndpoint, string authKeyOrResourceToken, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseStreamAsync(DatabaseProperties databaseProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual Container GetContainer(string databaseId, string containerId); + public virtual Database GetDatabase(string id); + public virtual FeedIterator GetDatabaseQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual Task ReadAccountAsync(); + } + public class CosmosClientOptions + { + public CosmosClientOptions(); + public bool AllowBulkExecution { get; set; } + public string ApplicationName { get; set; } + public IReadOnlyList ApplicationPreferredRegions { get; set; } + public string ApplicationRegion { get; set; } + public ConnectionMode ConnectionMode { get; set; } + public Nullable ConsistencyLevel { get; set; } + public CosmosClientTelemetryOptions CosmosClientTelemetryOptions { get; set; } + public Collection CustomHandlers { get; } + public Nullable EnableContentResponseOnWrite { get; set; } + public bool EnableTcpConnectionEndpointRediscovery { get; set; } + public int GatewayModeMaxConnectionLimit { get; set; } + public Func HttpClientFactory { get; set; } + public Nullable IdleTcpConnectionTimeout { get; set; } + public bool LimitToEndpoint { get; set; } + public Nullable MaxRequestsPerTcpConnection { get; set; } + public Nullable MaxRetryAttemptsOnRateLimitedRequests { get; set; } + public Nullable MaxRetryWaitTimeOnRateLimitedRequests { get; set; } + public Nullable MaxTcpConnectionsPerEndpoint { get; set; } + public Nullable OpenTcpConnectionTimeout { get; set; } + public Nullable PortReuseMode { get; set; } + public TimeSpan RequestTimeout { get; set; } + public CosmosSerializer Serializer { get; set; } + public CosmosSerializationOptions SerializerOptions { get; set; } + public Func ServerCertificateCustomValidationCallback { get; set; } + public Nullable TokenCredentialBackgroundRefreshInterval { get; set; } + public IWebProxy WebProxy { get; set; } + } + public class CosmosClientTelemetryOptions + { + public CosmosClientTelemetryOptions(); + public CosmosThresholdOptions CosmosThresholdOptions { get; set; } + public bool DisableDistributedTracing { get; set; } + public bool DisableSendingMetricsToService { get; set; } + } + public abstract class CosmosDiagnostics + { + protected CosmosDiagnostics(); + public virtual TimeSpan GetClientElapsedTime(); + public abstract IReadOnlyList> GetContactedRegions(); + public virtual int GetFailedRequestCount(); + public virtual ServerSideCumulativeMetrics GetQueryMetrics(); + public virtual Nullable GetStartTimeUtc(); + public abstract override string ToString(); + } + public class CosmosException : Exception + { + public CosmosException(string message, HttpStatusCode statusCode, int subStatusCode, string activityId, double requestCharge); + public virtual string ActivityId { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual Headers Headers { get; } + public override string Message { get; } + public virtual double RequestCharge { get; } + public virtual string ResponseBody { get; } + public virtual Nullable RetryAfter { get; } + public override string StackTrace { get; } + public virtual HttpStatusCode StatusCode { get; } + public virtual int SubStatusCode { get; } + public override string ToString(); + public virtual bool TryGetHeader(string headerName, out string value); + } + public sealed class CosmosLinqSerializerOptions + { + public CosmosLinqSerializerOptions(); + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public class CosmosOperationCanceledException : OperationCanceledException + { + public CosmosOperationCanceledException(OperationCanceledException originalException, CosmosDiagnostics diagnostics); + protected CosmosOperationCanceledException(SerializationInfo info, StreamingContext context); + public override IDictionary Data { get; } + public CosmosDiagnostics Diagnostics { get; } + public override string HelpLink { get; set; } + public override string Message { get; } + public override string Source { get; set; } + public override string StackTrace { get; } + public override Exception GetBaseException(); + public override void GetObjectData(SerializationInfo info, StreamingContext context); + public override string ToString(); + } + public enum CosmosPropertyNamingPolicy + { + CamelCase = 1, + Default = 0, + } + public abstract class CosmosResponseFactory + { + protected CosmosResponseFactory(); + public abstract FeedResponse CreateItemFeedResponse(ResponseMessage responseMessage); + public abstract ItemResponse CreateItemResponse(ResponseMessage responseMessage); + public abstract StoredProcedureExecuteResponse CreateStoredProcedureExecuteResponse(ResponseMessage responseMessage); + } + public sealed class CosmosSerializationOptions + { + public CosmosSerializationOptions(); + public bool IgnoreNullValues { get; set; } + public bool Indented { get; set; } + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public abstract class CosmosSerializer + { + protected CosmosSerializer(); + public abstract T FromStream(Stream stream); + public abstract Stream ToStream(T input); + } + public class CosmosThresholdOptions + { + public CosmosThresholdOptions(); + public TimeSpan NonPointOperationLatencyThreshold { get; set; } + public TimeSpan PointOperationLatencyThreshold { get; set; } + } + public abstract class Database + { + protected Database(); + public abstract CosmosClient Client { get; } + public abstract string Id { get; } + public abstract Task CreateClientEncryptionKeyAsync(ClientEncryptionKeyProperties clientEncryptionKeyProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ContainerBuilder DefineContainer(string name, string partitionKeyPath); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ClientEncryptionKey GetClientEncryptionKey(string id); + public abstract FeedIterator GetClientEncryptionKeyQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Container GetContainer(string id); + public abstract FeedIterator GetContainerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract User GetUser(string id); + public abstract FeedIterator GetUserQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class DatabaseProperties + { + public DatabaseProperties(); + public DatabaseProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class DatabaseResponse : Response + { + protected DatabaseResponse(); + public override string ActivityId { get; } + public virtual Database Database { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override DatabaseProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Database (DatabaseResponse response); + } + public enum DataType + { + LineString = 3, + MultiPolygon = 5, + Number = 0, + Point = 2, + Polygon = 4, + String = 1, + } + public class DedicatedGatewayRequestOptions + { + public DedicatedGatewayRequestOptions(); + public Nullable MaxIntegratedCacheStaleness { get; set; } + } + public class EncryptionKeyWrapMetadata : IEquatable + { + public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source); + public EncryptionKeyWrapMetadata(string type, string name, string value, string algorithm); + public string Algorithm { get; } + public string Name { get; } + public string Type { get; } + public string Value { get; } + public bool Equals(EncryptionKeyWrapMetadata other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class ExcludedPath + { + public ExcludedPath(); + public string Path { get; set; } + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task> ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedRange + { + protected FeedRange(); + public static FeedRange FromJsonString(string toStringValue); + public static FeedRange FromPartitionKey(PartitionKey partitionKey); + public abstract string ToJsonString(); + } + public abstract class FeedResponse : IEnumerable, IEnumerable + { + protected FeedResponse(); + public override string ActivityId { get; } + public abstract string ContinuationToken { get; } + public abstract int Count { get; } + public override string ETag { get; } + public abstract string IndexMetrics { get; } + public override double RequestCharge { get; } + public abstract IEnumerator GetEnumerator(); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public sealed class GeospatialConfig + { + public GeospatialConfig(); + public GeospatialConfig(GeospatialType geospatialType); + public GeospatialType GeospatialType { get; set; } + } + public enum GeospatialType + { + Geography = 0, + Geometry = 1, + } + public class Headers : IEnumerable + { + public Headers(); + public virtual string ActivityId { get; } + public virtual string ContentLength { get; set; } + public virtual string ContentType { get; } + public virtual string ContinuationToken { get; } + public virtual string ETag { get; } + public virtual string this[string headerName] { get; set; } + public virtual string Location { get; } + public virtual double RequestCharge { get; } + public virtual string Session { get; } + public virtual void Add(string headerName, IEnumerable values); + public virtual void Add(string headerName, string value); + public virtual string[] AllKeys(); + public virtual string Get(string headerName); + public virtual IEnumerator GetEnumerator(); + public virtual T GetHeaderValue(string headerName); + public virtual string GetValueOrDefault(string headerName); + public virtual void Remove(string headerName); + public virtual void Set(string headerName, string value); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + public virtual bool TryGetValue(string headerName, out string value); + } + public sealed class IncludedPath + { + public IncludedPath(); + public string Path { get; set; } + } + public enum IndexingDirective + { + Default = 0, + Exclude = 2, + Include = 1, + } + public enum IndexingMode + { + Consistent = 0, + Lazy = 1, + None = 2, + } + public sealed class IndexingPolicy + { + public IndexingPolicy(); + public bool Automatic { get; set; } + public Collection> CompositeIndexes { get; } + public Collection ExcludedPaths { get; } + public Collection IncludedPaths { get; } + public IndexingMode IndexingMode { get; set; } + public Collection SpatialIndexes { get; } + } + public enum IndexKind + { + Hash = 0, + Range = 1, + Spatial = 2, + } + public class ItemRequestOptions : RequestOptions + { + public ItemRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public DedicatedGatewayRequestOptions DedicatedGatewayRequestOptions { get; set; } + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + public IEnumerable PostTriggers { get; set; } + public IEnumerable PreTriggers { get; set; } + public string SessionToken { get; set; } + } + public class ItemResponse : Response + { + protected ItemResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public override HttpStatusCode StatusCode { get; } + } + public enum OperationKind + { + Create = 1, + Delete = 3, + Invalid = 0, + Read = 4, + Replace = 2, + } + public struct PartitionKey : IEquatable + { + public static readonly PartitionKey None; + public static readonly PartitionKey Null; + public static readonly string SystemKeyName; + public static readonly string SystemKeyPath; + public PartitionKey(bool partitionKeyValue); + public PartitionKey(double partitionKeyValue); + public PartitionKey(string partitionKeyValue); + public bool Equals(PartitionKey other); + public override bool Equals(object obj); + public override int GetHashCode(); + public static bool operator ==(PartitionKey left, PartitionKey right); + public static bool operator !=(PartitionKey left, PartitionKey right); + public override string ToString(); + } + public sealed class PartitionKeyBuilder + { + public PartitionKeyBuilder(); + public PartitionKeyBuilder Add(bool val); + public PartitionKeyBuilder Add(double val); + public PartitionKeyBuilder Add(string val); + public PartitionKeyBuilder AddNoneType(); + public PartitionKeyBuilder AddNullValue(); + public PartitionKey Build(); + } + public enum PartitionKeyDefinitionVersion + { + V1 = 1, + V2 = 2, + } + public sealed class PatchItemRequestOptions : ItemRequestOptions + { + public PatchItemRequestOptions(); + public string FilterPredicate { get; set; } + } + public abstract class PatchOperation + { + protected PatchOperation(); + public virtual string From { get; set; } + public abstract PatchOperationType OperationType { get; } + public abstract string Path { get; } + public static PatchOperation Add(string path, T value); + public static PatchOperation Increment(string path, double value); + public static PatchOperation Increment(string path, long value); + public static PatchOperation Move(string from, string path); + public static PatchOperation Remove(string path); + public static PatchOperation Replace(string path, T value); + public static PatchOperation Set(string path, T value); + public virtual bool TrySerializeValueParameter(CosmosSerializer cosmosSerializer, out Stream valueParam); + } + public enum PatchOperationType + { + Add = 0, + Increment = 4, + Move = 5, + Remove = 1, + Replace = 2, + Set = 3, + } + public abstract class PatchOperation : PatchOperation + { + protected PatchOperation(); + public abstract T Value { get; } + } + public abstract class Permission + { + protected Permission(); + public abstract string Id { get; } + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadAsync(Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum PermissionMode : byte + { + All = (byte)2, + Read = (byte)1, + } + public class PermissionProperties + { + public PermissionProperties(string id, PermissionMode permissionMode, Container container, PartitionKey resourcePartitionKey, string itemId); + public PermissionProperties(string id, PermissionMode permissionMode, Container container, Nullable resourcePartitionKey=default(Nullable)); + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public PermissionMode PermissionMode { get; } + public Nullable ResourcePartitionKey { get; set; } + public string ResourceUri { get; } + public string SelfLink { get; } + public string Token { get; } + } + public class PermissionResponse : Response + { + protected PermissionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public virtual Permission Permission { get; } + public override double RequestCharge { get; } + public override PermissionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Permission (PermissionResponse response); + } + public enum PortReuseMode + { + PrivatePortPool = 1, + ReuseUnicastPort = 0, + } + public class QueryDefinition + { + public QueryDefinition(string query); + public string QueryText { get; } + public IReadOnlyList> GetQueryParameters(); + public QueryDefinition WithParameter(string name, object value); + public QueryDefinition WithParameterStream(string name, Stream valueStream); + } + public class QueryRequestOptions : RequestOptions + { + public QueryRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public DedicatedGatewayRequestOptions DedicatedGatewayRequestOptions { get; set; } + public Nullable EnableLowPrecisionOrderBy { get; set; } + public Nullable EnableScanInQuery { get; set; } + public Nullable MaxBufferedItemCount { get; set; } + public Nullable MaxConcurrency { get; set; } + public Nullable MaxItemCount { get; set; } + public Nullable PartitionKey { get; set; } + public Nullable PopulateIndexMetrics { get; set; } + public Nullable ResponseContinuationTokenLimitInKb { get; set; } + public string SessionToken { get; set; } + } + public class ReadManyRequestOptions : RequestOptions + { + public ReadManyRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public static class Regions + { + public const string AustraliaCentral = "Australia Central"; + public const string AustraliaCentral2 = "Australia Central 2"; + public const string AustraliaEast = "Australia East"; + public const string AustraliaSoutheast = "Australia Southeast"; + public const string BrazilSouth = "Brazil South"; + public const string BrazilSoutheast = "Brazil Southeast"; + public const string CanadaCentral = "Canada Central"; + public const string CanadaEast = "Canada East"; + public const string CentralIndia = "Central India"; + public const string CentralUS = "Central US"; + public const string CentralUSEUAP = "Central US EUAP"; + public const string ChinaEast = "China East"; + public const string ChinaEast2 = "China East 2"; + public const string ChinaEast3 = "China East 3"; + public const string ChinaNorth = "China North"; + public const string ChinaNorth2 = "China North 2"; + public const string ChinaNorth3 = "China North 3"; + public const string EastAsia = "East Asia"; + public const string EastUS = "East US"; + public const string EastUS2 = "East US 2"; + public const string EastUS2EUAP = "East US 2 EUAP"; + public const string EastUSSLV = "East US SLV"; + public const string FranceCentral = "France Central"; + public const string FranceSouth = "France South"; + public const string GermanyCentral = "Germany Central"; + public const string GermanyNorth = "Germany North"; + public const string GermanyNortheast = "Germany Northeast"; + public const string GermanyWestCentral = "Germany West Central"; + public const string IsraelCentral = "Israel Central"; + public const string ItalyNorth = "Italy North"; + public const string JapanEast = "Japan East"; + public const string JapanWest = "Japan West"; + public const string JioIndiaCentral = "Jio India Central"; + public const string JioIndiaWest = "Jio India West"; + public const string KoreaCentral = "Korea Central"; + public const string KoreaSouth = "Korea South"; + public const string MalaysiaSouth = "Malaysia South"; + public const string NorthCentralUS = "North Central US"; + public const string NorthEurope = "North Europe"; + public const string NorwayEast = "Norway East"; + public const string NorwayWest = "Norway West"; + public const string PolandCentral = "Poland Central"; + public const string QatarCentral = "Qatar Central"; + public const string SouthAfricaNorth = "South Africa North"; + public const string SouthAfricaWest = "South Africa West"; + public const string SouthCentralUS = "South Central US"; + public const string SoutheastAsia = "Southeast Asia"; + public const string SouthIndia = "South India"; + public const string SwedenCentral = "Sweden Central"; + public const string SwedenSouth = "Sweden South"; + public const string SwitzerlandNorth = "Switzerland North"; + public const string SwitzerlandWest = "Switzerland West"; + public const string UAECentral = "UAE Central"; + public const string UAENorth = "UAE North"; + public const string UKSouth = "UK South"; + public const string UKWest = "UK West"; + public const string USDoDCentral = "USDoD Central"; + public const string USDoDEast = "USDoD East"; + public const string USGovArizona = "USGov Arizona"; + public const string USGovTexas = "USGov Texas"; + public const string USGovVirginia = "USGov Virginia"; + public const string USNatEast = "USNat East"; + public const string USNatWest = "USNat West"; + public const string USSecEast = "USSec East"; + public const string USSecWest = "USSec West"; + public const string WestCentralUS = "West Central US"; + public const string WestEurope = "West Europe"; + public const string WestIndia = "West India"; + public const string WestUS = "West US"; + public const string WestUS2 = "West US 2"; + public const string WestUS3 = "West US 3"; + } + public abstract class RequestHandler + { + protected RequestHandler(); + public RequestHandler InnerHandler { get; set; } + public virtual Task SendAsync(RequestMessage request, CancellationToken cancellationToken); + } + public class RequestMessage : IDisposable + { + public RequestMessage(); + public RequestMessage(HttpMethod method, Uri requestUri); + public virtual Stream Content { get; set; } + public virtual Headers Headers { get; } + public virtual HttpMethod Method { get; } + public virtual Dictionary Properties { get; } + public virtual Uri RequestUri { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + } + public class RequestOptions + { + public RequestOptions(); + public Action AddRequestHeaders { get; set; } + public CosmosThresholdOptions CosmosThresholdOptions { get; set; } + public List ExcludeRegions { get; set; } + public string IfMatchEtag { get; set; } + public string IfNoneMatchEtag { get; set; } + public IReadOnlyDictionary Properties { get; set; } + public RequestOptions ShallowCopy(); + } + public class ResponseMessage : IDisposable + { + public ResponseMessage(); + public ResponseMessage(HttpStatusCode statusCode, RequestMessage requestMessage=null, string errorMessage=null); + public virtual Stream Content { get; set; } + public virtual string ContinuationToken { get; } + public virtual CosmosDiagnostics Diagnostics { get; set; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public string IndexMetrics { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual RequestMessage RequestMessage { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual ResponseMessage EnsureSuccessStatusCode(); + } + public abstract class Response + { + protected Response(); + public abstract string ActivityId { get; } + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract string ETag { get; } + public abstract Headers Headers { get; } + public abstract double RequestCharge { get; } + public abstract T Resource { get; } + public abstract HttpStatusCode StatusCode { get; } + public static implicit operator T (Response response); + } + public abstract class ServerSideCumulativeMetrics + { + protected ServerSideCumulativeMetrics(); + public abstract ServerSideMetrics CumulativeMetrics { get; } + public abstract IReadOnlyList PartitionedMetrics { get; } + } + public abstract class ServerSideMetrics + { + protected ServerSideMetrics(); + public abstract TimeSpan DocumentLoadTime { get; } + public abstract TimeSpan DocumentWriteTime { get; } + public abstract double IndexHitRatio { get; } + public abstract TimeSpan IndexLookupTime { get; } + public abstract long OutputDocumentCount { get; } + public abstract long OutputDocumentSize { get; } + public abstract TimeSpan QueryPreparationTime { get; } + public abstract long RetrievedDocumentCount { get; } + public abstract long RetrievedDocumentSize { get; } + public abstract TimeSpan RuntimeExecutionTime { get; } + public abstract TimeSpan TotalTime { get; } + public abstract TimeSpan VMExecutionTime { get; } + } + public abstract class ServerSidePartitionedMetrics + { + protected ServerSidePartitionedMetrics(); + public abstract string FeedRange { get; } + public abstract Nullable PartitionKeyRangeId { get; } + public abstract ServerSideMetrics ServerSideMetrics { get; } + } + public sealed class SpatialPath + { + public SpatialPath(); + public BoundingBoxProperties BoundingBox { get; set; } + public string Path { get; set; } + public Collection SpatialTypes { get; } + } + public enum SpatialType + { + LineString = 1, + MultiPolygon = 3, + Point = 0, + Polygon = 2, + } + public class ThroughputProperties + { + public Nullable AutoscaleMaxThroughput { get; } + public string ETag { get; } + public Nullable LastModified { get; } + public string SelfLink { get; } + public Nullable Throughput { get; } + public static ThroughputProperties CreateAutoscaleThroughput(int autoscaleMaxThroughput); + public static ThroughputProperties CreateManualThroughput(int throughput); + } + public class ThroughputResponse : Response + { + protected ThroughputResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public Nullable IsReplacePending { get; } + public Nullable MinThroughput { get; } + public override double RequestCharge { get; } + public override ThroughputProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ThroughputProperties (ThroughputResponse response); + } + public abstract class TransactionalBatch + { + protected TransactionalBatch(); + public abstract TransactionalBatch CreateItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch CreateItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch DeleteItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract Task ExecuteAsync(TransactionalBatchRequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch PatchItem(string id, IReadOnlyList patchOperations, TransactionalBatchPatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReadItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItemStream(string id, Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItem(string id, T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + } + public class TransactionalBatchItemRequestOptions : RequestOptions + { + public TransactionalBatchItemRequestOptions(); + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + } + public class TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual string ETag { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual Stream ResourceStream { get; } + public virtual TimeSpan RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + } + public class TransactionalBatchOperationResult : TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual T Resource { get; set; } + } + public class TransactionalBatchPatchItemRequestOptions : TransactionalBatchItemRequestOptions + { + public TransactionalBatchPatchItemRequestOptions(); + public string FilterPredicate { get; set; } + } + public class TransactionalBatchRequestOptions : RequestOptions + { + public TransactionalBatchRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public class TransactionalBatchResponse : IDisposable, IEnumerable, IEnumerable, IReadOnlyCollection, IReadOnlyList + { + protected TransactionalBatchResponse(); + public virtual string ActivityId { get; } + public virtual int Count { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual TransactionalBatchOperationResult this[int index] { get; } + public virtual double RequestCharge { get; } + public virtual Nullable RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual IEnumerator GetEnumerator(); + public virtual TransactionalBatchOperationResult GetOperationResultAtIndex(int index); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public class UniqueKey + { + public UniqueKey(); + public Collection Paths { get; } + } + public sealed class UniqueKeyPolicy + { + public UniqueKeyPolicy(); + public Collection UniqueKeys { get; } + } + public abstract class User + { + protected User(); + public abstract string Id { get; } + public abstract Task CreatePermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Permission GetPermission(string id); + public abstract FeedIterator GetPermissionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetPermissionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(UserProperties userProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertPermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class UserProperties + { + protected UserProperties(); + public UserProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class UserResponse : Response + { + protected UserResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public virtual User User { get; } + public static implicit operator User (UserResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Fluent +{ + public sealed class ClientEncryptionPolicyDefinition + { + public ContainerBuilder Attach(); + public ClientEncryptionPolicyDefinition WithIncludedPath(ClientEncryptionIncludedPath path); + } + public class CompositeIndexDefinition + { + public T Attach(); + public CompositeIndexDefinition Path(string path); + public CompositeIndexDefinition Path(string path, CompositePathSortOrder sortOrder); + } + public class ConflictResolutionDefinition + { + public ContainerBuilder Attach(); + public ConflictResolutionDefinition WithCustomStoredProcedureResolution(string conflictResolutionProcedure); + public ConflictResolutionDefinition WithLastWriterWinsResolution(string conflictResolutionPath); + } + public class ContainerBuilder : ContainerDefinition + { + protected ContainerBuilder(); + public ContainerBuilder(Database database, string name, string partitionKeyPath); + public new ContainerProperties Build(); + public Task CreateAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(); + public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(int policyFormatVersion); + public ConflictResolutionDefinition WithConflictResolution(); + public UniqueKeyDefinition WithUniqueKey(); + } + public abstract class ContainerDefinition where T : ContainerDefinition + { + public ContainerDefinition(); + public ContainerProperties Build(); + public T WithDefaultTimeToLive(int defaultTtlInSeconds); + public T WithDefaultTimeToLive(TimeSpan defaultTtlTimeSpan); + public IndexingPolicyDefinition WithIndexingPolicy(); + public T WithPartitionKeyDefinitionVersion(PartitionKeyDefinitionVersion partitionKeyDefinitionVersion); + public T WithTimeToLivePropertyPath(string propertyPath); + } + public class CosmosClientBuilder + { + public CosmosClientBuilder(string connectionString); + public CosmosClientBuilder(string accountEndpoint, AzureKeyCredential authKeyOrResourceTokenCredential); + public CosmosClientBuilder(string accountEndpoint, TokenCredential tokenCredential); + public CosmosClientBuilder(string accountEndpoint, string authKeyOrResourceToken); + public CosmosClientBuilder AddCustomHandlers(params RequestHandler[] customHandlers); + public CosmosClient Build(); + public Task BuildAndInitializeAsync(IReadOnlyList> containers, CancellationToken cancellationToken=default(CancellationToken)); + public CosmosClientBuilder WithApplicationName(string applicationName); + public CosmosClientBuilder WithApplicationPreferredRegions(IReadOnlyList applicationPreferredRegions); + public CosmosClientBuilder WithApplicationRegion(string applicationRegion); + public CosmosClientBuilder WithBulkExecution(bool enabled); + public CosmosClientBuilder WithClientTelemetryOptions(CosmosClientTelemetryOptions options); + public CosmosClientBuilder WithConnectionModeDirect(); + public CosmosClientBuilder WithConnectionModeDirect(Nullable idleTcpConnectionTimeout=default(Nullable), Nullable openTcpConnectionTimeout=default(Nullable), Nullable maxRequestsPerTcpConnection=default(Nullable), Nullable maxTcpConnectionsPerEndpoint=default(Nullable), Nullable portReuseMode=default(Nullable), Nullable enableTcpConnectionEndpointRediscovery=default(Nullable)); + public CosmosClientBuilder WithConnectionModeGateway(Nullable maxConnectionLimit=default(Nullable), IWebProxy webProxy=null); + public CosmosClientBuilder WithConsistencyLevel(ConsistencyLevel consistencyLevel); + public CosmosClientBuilder WithContentResponseOnWrite(bool contentResponseOnWrite); + public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSerializer); + public CosmosClientBuilder WithHttpClientFactory(Func httpClientFactory); + public CosmosClientBuilder WithLimitToEndpoint(bool limitToEndpoint); + public CosmosClientBuilder WithRequestTimeout(TimeSpan requestTimeout); + public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions); + public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeOnThrottledRequests, int maxRetryAttemptsOnThrottledRequests); + } + public class IndexingPolicyDefinition + { + public IndexingPolicyDefinition(); + public T Attach(); + public IndexingPolicyDefinition WithAutomaticIndexing(bool enabled); + public CompositeIndexDefinition> WithCompositeIndex(); + public PathsDefinition> WithExcludedPaths(); + public PathsDefinition> WithIncludedPaths(); + public IndexingPolicyDefinition WithIndexingMode(IndexingMode indexingMode); + public SpatialIndexDefinition> WithSpatialIndex(); + } + public class PathsDefinition + { + public T Attach(); + public PathsDefinition Path(string path); + } + public class SpatialIndexDefinition + { + public T Attach(); + public SpatialIndexDefinition Path(string path); + public SpatialIndexDefinition Path(string path, params SpatialType[] spatialTypes); + } + public class UniqueKeyDefinition + { + public ContainerBuilder Attach(); + public UniqueKeyDefinition Path(string path); + } +} +namespace Microsoft.Azure.Cosmos.Linq +{ + public static class CosmosLinq + { + public static object InvokeUserDefinedFunction(string udfName, params object[] arguments); + } + public static class CosmosLinqExtensions + { + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> CountAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static bool IsArray(this object obj); + public static bool IsBool(this object obj); + public static bool IsDefined(this object obj); + public static bool IsNull(this object obj); + public static bool IsNumber(this object obj); + public static bool IsObject(this object obj); + public static bool IsPrimitive(this object obj); + public static bool IsString(this object obj); + public static Task> MaxAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> MinAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static bool RegexMatch(this object obj, string regularExpression); + public static bool RegexMatch(this object obj, string regularExpression, string searchModifier); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static FeedIterator ToFeedIterator(this IQueryable query); + public static QueryDefinition ToQueryDefinition(this IQueryable query); + public static FeedIterator ToStreamIterator(this IQueryable query); + } +} +namespace Microsoft.Azure.Cosmos.Scripts +{ + public abstract class Scripts + { + protected Scripts(); + public abstract Task CreateStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ExecuteStoredProcedureAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, Stream streamPayload, PartitionKey partitionKey, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetStoredProcedureQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class StoredProcedureExecuteResponse : Response + { + protected StoredProcedureExecuteResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public virtual string ScriptLog { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + } + public class StoredProcedureProperties + { + public StoredProcedureProperties(); + public StoredProcedureProperties(string id, string body); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class StoredProcedureRequestOptions : RequestOptions + { + public StoredProcedureRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public bool EnableScriptLogging { get; set; } + public string SessionToken { get; set; } + } + public class StoredProcedureResponse : Response + { + protected StoredProcedureResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override StoredProcedureProperties Resource { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator StoredProcedureProperties (StoredProcedureResponse response); + } + public enum TriggerOperation : short + { + All = (short)0, + Create = (short)1, + Delete = (short)3, + Replace = (short)4, + Update = (short)2, + Upsert = (short)5, + } + public class TriggerProperties + { + public TriggerProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + public TriggerOperation TriggerOperation { get; set; } + public TriggerType TriggerType { get; set; } + } + public class TriggerResponse : Response + { + protected TriggerResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override TriggerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator TriggerProperties (TriggerResponse response); + } + public enum TriggerType : byte + { + Post = (byte)1, + Pre = (byte)0, + } + public class UserDefinedFunctionProperties + { + public UserDefinedFunctionProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + } + public class UserDefinedFunctionResponse : Response + { + protected UserDefinedFunctionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserDefinedFunctionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator UserDefinedFunctionProperties (UserDefinedFunctionResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Spatial +{ + public sealed class BoundingBox : IEquatable + { + public BoundingBox(Position min, Position max); + public Position Max { get; } + public Position Min { get; } + public bool Equals(BoundingBox other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public abstract class Crs + { + protected Crs(CrsType type); + public static Crs Default { get; } + public CrsType Type { get; } + public static Crs Unspecified { get; } + public static LinkedCrs Linked(string href); + public static LinkedCrs Linked(string href, string type); + public static NamedCrs Named(string name); + } + public enum CrsType + { + Linked = 1, + Named = 0, + Unspecified = 2, + } + public abstract class Geometry + { + protected Geometry(GeometryType type, GeometryParams geometryParams); + public IDictionary AdditionalProperties { get; } + public BoundingBox BoundingBox { get; } + public Crs Crs { get; } + public GeometryType Type { get; } + public double Distance(Geometry to); + public override bool Equals(object obj); + public override int GetHashCode(); + public bool Intersects(Geometry geometry2); + public bool IsValid(); + public GeometryValidationResult IsValidDetailed(); + public bool Within(Geometry outer); + } + public class GeometryParams + { + public GeometryParams(); + public IDictionary AdditionalProperties { get; set; } + public BoundingBox BoundingBox { get; set; } + public Crs Crs { get; set; } + } + public enum GeometryShape + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public enum GeometryType + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public class GeometryValidationResult + { + public GeometryValidationResult(); + public bool IsValid { get; } + public string Reason { get; } + } + public sealed class LinearRing : IEquatable + { + public LinearRing(IList coordinates); + public ReadOnlyCollection Positions { get; } + public bool Equals(LinearRing other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LineString : Geometry, IEquatable + { + public LineString(IList coordinates); + public LineString(IList coordinates, GeometryParams geometryParams); + public ReadOnlyCollection Positions { get; } + public bool Equals(LineString other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LinkedCrs : Crs, IEquatable + { + public string Href { get; } + public string HrefType { get; } + public bool Equals(LinkedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class MultiPolygon : Geometry, IEquatable + { + public MultiPolygon(IList polygons); + public MultiPolygon(IList polygons, GeometryParams geometryParams); + public ReadOnlyCollection Polygons { get; } + public bool Equals(MultiPolygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class NamedCrs : Crs, IEquatable + { + public string Name { get; } + public bool Equals(NamedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Point : Geometry, IEquatable + { + public Point(Position position); + public Point(Position position, GeometryParams geometryParams); + public Point(double longitude, double latitude); + public Position Position { get; } + public bool Equals(Point other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Polygon : Geometry, IEquatable + { + public Polygon(IList rings); + public Polygon(IList rings, GeometryParams geometryParams); + public Polygon(IList externalRingPositions); + public ReadOnlyCollection Rings { get; } + public bool Equals(Polygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class PolygonCoordinates : IEquatable + { + public PolygonCoordinates(IList rings); + public ReadOnlyCollection Rings { get; } + public bool Equals(PolygonCoordinates other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Position : IEquatable + { + public Position(IList coordinates); + public Position(double longitude, double latitude); + public Position(double longitude, double latitude, Nullable altitude); + public Nullable Altitude { get; } + public ReadOnlyCollection Coordinates { get; } + public double Latitude { get; } + public double Longitude { get; } + public bool Equals(Position other); + public override bool Equals(object obj); + public override int GetHashCode(); + } +} diff --git a/changelog.md b/changelog.md index c6f797d1ec..1fd2e31825 100644 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,12 @@ Preview features are treated as a separate branch and will not be included in th The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [3.37.1-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.37.1-preview) - 2024-1-2 +### [3.37.1](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.37.1) - 2024-1-2 + +#### Fixed +- [4226](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/4226) GlobalEndpointManager: Fixes Memory Leak (#4226) + ### [3.37.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.37.0-preview) - 2023-11-17 ### [3.37.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.37.0) - 2023-11-17 From 17bbdab53b3760664c38f95cc5edace18da4639d Mon Sep 17 00:00:00 2001 From: Maya Painter <130110800+Maya-Painter@users.noreply.github.com> Date: Fri, 5 Jan 2024 13:30:29 -0800 Subject: [PATCH 5/5] Query: Adds support for System.Text LINQ Custom Serializer (#4138) * initial commit * add more tests * cleanup * camel case fix * fix precidence and add test * cleanup * cleanup * Implement serializers * clenaup and bugfix * cleanup constructors * fix baseline * updates * cleanup * removed requirement to decorate enums * nit PR comments * bug fix and api updates * Pr comments * remove datacontract and newtonsoft serializer types * Pr comments and adding tests * updates * Aggregate tests and new serializer member * More tests and cleanup * PR comments * PR comments * PR comments - internal options class * Update Program.cs * comment fixes * PR comments * Add preview flag * API changes * new serialization interface * Update API and serializer updates * API fix * cleanup * PR comments * update interface * fix * API * Update Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs Co-authored-by: Kiran Kumar Kolli * Some PR comments * adding sample code * Enum rename and interfact to abstract class * PR comments * PR comments * Remove CosmosLinqSerializerType * last one (hopefully) * Update Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs Co-authored-by: Matias Quaranta * Update Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs Co-authored-by: Matias Quaranta --------- Co-authored-by: Kiran Kumar Kolli Co-authored-by: Matias Quaranta --- .../src/Linq/CosmosLinqQuery.cs | 8 +- .../src/Linq/CosmosLinqQueryProvider.cs | 5 +- .../src/Linq/CustomCosmosLinqSerializer.cs | 56 ++++ .../src/Linq/DefaultCosmosLinqSerializer.cs | 23 +- .../src/Linq/DocumentQueryEvaluator.cs | 4 +- .../src/Linq/ExpressionToSQL.cs | 8 +- ...er.cs => ICosmosLinqSerializerInternal.cs} | 4 +- .../src/Linq/SQLTranslator.cs | 7 +- .../src/Linq/TranslationContext.cs | 35 ++- Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs | 2 +- .../Resource/Container/ContainerCore.Items.cs | 12 +- .../src/Serializer/CosmosLinqSerializer.cs | 84 +++++ .../CosmosLinqSerializerOptionsInternal.cs | 56 ++++ .../src/Serializer/CosmosSerializationUtil.cs | 23 +- .../src/Serializer/CosmosSerializer.cs | 5 +- ...stAggregateQueriesWithCustomSerializer.xml | 126 ++++++++ ...ine.TestAggregatesWithCustomSerializer.xml | 58 ++++ ...emberInitializerDotNetCustomSerializer.xml | 143 +++++++++ ...TestMemberInitializerDotNetDataMember.xml} | 36 +-- ...seline.TestMemberInitializerNewtonsoft.xml | 120 ++++---- ...tMemberInitializerNewtonsoftDataMember.xml | 236 +++++++++++++++ ...TestMemberInitializerNewtonsoftDotNet.xml} | 16 +- ...inqAggregateCustomSerializationBaseline.cs | 198 ++++++++++++ .../LinqAttributeContractBaselineTests.cs | 2 +- .../Linq/LinqTestsCommon.cs | 105 ++++++- ...TranslationWithCustomSerializerBaseline.cs | 286 ++++++++++++------ ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 21 +- .../Contracts/DotNetPreviewSDKAPI.json | 28 ++ .../Linq/CosmosLinqJsonConverterTests.cs | 151 ++++++--- 29 files changed, 1570 insertions(+), 288 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs rename Microsoft.Azure.Cosmos/src/Linq/{ICosmosLinqSerializer.cs => ICosmosLinqSerializerInternal.cs} (86%) create mode 100644 Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs create mode 100644 Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/{LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml => LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml} (84%) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/{LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerMultiSerializer.xml => LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml} (92%) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs index f8400229b2..a4c4c5e938 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs @@ -12,8 +12,8 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Diagnostics; - using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Newtonsoft.Json; @@ -32,7 +32,7 @@ internal sealed class CosmosLinqQuery : IDocumentQuery, IOrderedQueryable< private readonly QueryRequestOptions cosmosQueryRequestOptions; private readonly bool allowSynchronousQueryExecution = false; private readonly string continuationToken; - private readonly CosmosLinqSerializerOptions linqSerializationOptions; + private readonly CosmosLinqSerializerOptionsInternal linqSerializationOptions; public CosmosLinqQuery( ContainerInternal container, @@ -42,7 +42,7 @@ public CosmosLinqQuery( QueryRequestOptions cosmosQueryRequestOptions, Expression expression, bool allowSynchronousQueryExecution, - CosmosLinqSerializerOptions linqSerializationOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializationOptions = null) { this.container = container ?? throw new ArgumentNullException(nameof(container)); this.responseFactory = responseFactory ?? throw new ArgumentNullException(nameof(responseFactory)); @@ -72,7 +72,7 @@ public CosmosLinqQuery( string continuationToken, QueryRequestOptions cosmosQueryRequestOptions, bool allowSynchronousQueryExecution, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) : this( container, responseFactory, diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs index 8ba71d7b98..2c695c6477 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; - using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; @@ -24,7 +23,7 @@ internal sealed class CosmosLinqQueryProvider : IQueryProvider private readonly bool allowSynchronousQueryExecution; private readonly Action onExecuteScalarQueryCallback; private readonly string continuationToken; - private readonly CosmosLinqSerializerOptions linqSerializerOptions; + private readonly CosmosLinqSerializerOptionsInternal linqSerializerOptions; public CosmosLinqQueryProvider( ContainerInternal container, @@ -34,7 +33,7 @@ public CosmosLinqQueryProvider( QueryRequestOptions cosmosQueryRequestOptions, bool allowSynchronousQueryExecution, Action onExecuteScalarQueryCallback = null, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { this.container = container; this.responseFactory = responseFactory; diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs new file mode 100644 index 0000000000..03f7c9cff6 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Globalization; + using System.IO; + using System.Linq.Expressions; + using System.Reflection; + + internal class CustomCosmosLinqSerializer : ICosmosLinqSerializerInternal + { + private readonly CosmosLinqSerializer CustomCosmosSerializer; + + public CustomCosmosLinqSerializer(CosmosLinqSerializer customCosmosLinqSerializer) + { + this.CustomCosmosSerializer = customCosmosLinqSerializer; + } + + public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) + { + return true; + } + + public string Serialize(object value, MemberExpression memberExpression, Type memberType) + { + return this.SerializeWithCustomSerializer(value); + } + + public string SerializeScalarExpression(ConstantExpression inputExpression) + { + return this.SerializeWithCustomSerializer(inputExpression.Value); + } + + public string SerializeMemberName(MemberInfo memberInfo) + { + return this.CustomCosmosSerializer.SerializeMemberName(memberInfo); + } + + private string SerializeWithCustomSerializer(object value) + { + StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); + + using (Stream stream = this.CustomCosmosSerializer.ToStream(value)) + { + using (StreamReader streamReader = new StreamReader(stream)) + { + string propertyValue = streamReader.ReadToEnd(); + writer.Write(propertyValue); + return writer.ToString(); + } + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs index 024c33fab0..31f20d5059 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs @@ -12,8 +12,15 @@ namespace Microsoft.Azure.Cosmos.Linq using Microsoft.Azure.Documents; using Newtonsoft.Json; - internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializer + internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializerInternal { + private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; + + public DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) + { + this.PropertyNamingPolicy = propertyNamingPolicy; + } + public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) { // There are two ways to specify a custom attribute @@ -63,9 +70,9 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) return JsonConvert.SerializeObject(inputExpression.Value); } - public string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOptions linqSerializerOptions = null) + public string SerializeMemberName(MemberInfo memberInfo) { - string memberName = null; + string memberName = memberInfo.Name; // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); @@ -94,15 +101,7 @@ public string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOpt } } - if (memberName == null) - { - memberName = memberInfo.Name; - } - - if (linqSerializerOptions != null) - { - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(linqSerializerOptions, memberName); - } + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs b/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs index c81f5a9645..da25188bc2 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs @@ -17,7 +17,7 @@ internal static class DocumentQueryEvaluator public static SqlQuerySpec Evaluate( Expression expression, - CosmosLinqSerializerOptions linqSerializerOptions = null, + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null, IDictionary parameters = null) { switch (expression.NodeType) @@ -76,7 +76,7 @@ private static SqlQuerySpec HandleEmptyQuery(ConstantExpression expression) private static SqlQuerySpec HandleMethodCallExpression( MethodCallExpression expression, IDictionary parameters, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { if (DocumentQueryEvaluator.IsTransformExpression(expression)) { diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 5153f29115..6be1e82e9c 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -14,10 +14,10 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq.Expressions; using System.Reflection; using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Spatial; using Microsoft.Azure.Cosmos.SqlObjects; using Microsoft.Azure.Documents; - using Newtonsoft.Json; using static Microsoft.Azure.Cosmos.Linq.FromParameterBindings; // ReSharper disable UnusedParameter.Local @@ -88,7 +88,7 @@ public static class LinqMethods public static SqlQuery TranslateQuery( Expression inputExpression, IDictionary parameters, - CosmosLinqSerializerOptions linqSerializerOptions) + CosmosLinqSerializerOptionsInternal linqSerializerOptions) { TranslationContext context = new TranslationContext(linqSerializerOptions, parameters); ExpressionToSql.Translate(inputExpression, context); // ignore result here @@ -503,8 +503,8 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit memberType = memberType.NullableUnderlyingType(); } - bool requiresCustomSerializatior = context.CosmosLinqSerializer.RequiresCustomSerialization(memberExpression, memberType); - if (requiresCustomSerializatior) + bool requiresCustomSerialization = context.CosmosLinqSerializer.RequiresCustomSerialization(memberExpression, memberType); + if (requiresCustomSerialization) { object value = default(object); // Enum diff --git a/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializerInternal.cs similarity index 86% rename from Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs rename to Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializerInternal.cs index f31490832d..7a3054e3b6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializerInternal.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq.Expressions; using System.Reflection; - internal interface ICosmosLinqSerializer + internal interface ICosmosLinqSerializerInternal { /// /// Returns true if there are custom attributes on a member expression. @@ -28,6 +28,6 @@ internal interface ICosmosLinqSerializer /// /// Serializes a member name with LINQ serializer options applied. /// - string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOptions linqSerializerOptions = null); + string SerializeMemberName(MemberInfo memberInfo); } } diff --git a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs index c0bd6d38a0..8491d9cf0c 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.SqlObjects; /// @@ -21,7 +22,7 @@ internal static class SqlTranslator /// A string describing the expression translation. internal static string TranslateExpression( Expression inputExpression, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { TranslationContext context = new TranslationContext(linqSerializerOptions); @@ -32,7 +33,7 @@ internal static string TranslateExpression( internal static string TranslateExpressionOld( Expression inputExpression, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { TranslationContext context = new TranslationContext(linqSerializerOptions); @@ -43,7 +44,7 @@ internal static string TranslateExpressionOld( internal static SqlQuerySpec TranslateQuery( Expression inputExpression, - CosmosLinqSerializerOptions linqSerializerOptions, + CosmosLinqSerializerOptionsInternal linqSerializerOptions, IDictionary parameters) { inputExpression = ConstantEvaluator.PartialEval(inputExpression); diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index f5d53bd1e7..3b1d9cd20b 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System; using System.Collections.Generic; using System.Linq.Expressions; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.SqlObjects; using static Microsoft.Azure.Cosmos.Linq.ExpressionToSql; using static Microsoft.Azure.Cosmos.Linq.FromParameterBindings; @@ -24,12 +25,7 @@ internal sealed class TranslationContext /// /// The LINQ serializer /// - public readonly ICosmosLinqSerializer CosmosLinqSerializer; - - /// - /// User-provided LINQ serializer options - /// - public CosmosLinqSerializerOptions LinqSerializerOptions; + public readonly ICosmosLinqSerializerInternal CosmosLinqSerializer; /// /// Set of parameters in scope at any point; used to generate fresh parameter names if necessary. @@ -72,7 +68,11 @@ internal sealed class TranslationContext /// private Stack subqueryBindingStack; - public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDictionary parameters = null) + private static readonly ICosmosLinqSerializerInternal DefaultLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosLinqSerializerOptions().PropertyNamingPolicy); + + private static readonly MemberNames DefaultMemberNames = new MemberNames(new CosmosLinqSerializerOptions()); + + public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal, IDictionary parameters = null) { this.InScope = new HashSet(); this.substitutions = new ParameterSubstitution(); @@ -81,10 +81,25 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.collectionStack = new List(); this.CurrentQuery = new QueryUnderConstruction(this.GetGenFreshParameterFunc()); this.subqueryBindingStack = new Stack(); - this.LinqSerializerOptions = linqSerializerOptions; this.Parameters = parameters; - this.MemberNames = new MemberNames(linqSerializerOptions); - this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(); + + if (linqSerializerOptionsInternal?.CustomCosmosLinqSerializer != null) + { + this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(linqSerializerOptionsInternal.CustomCosmosLinqSerializer); + this.MemberNames = new MemberNames(new CosmosLinqSerializerOptions()); + } + else if (linqSerializerOptionsInternal?.CosmosLinqSerializerOptions != null) + { + CosmosLinqSerializerOptions linqSerializerOptions = linqSerializerOptionsInternal.CosmosLinqSerializerOptions; + + this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy); + this.MemberNames = new MemberNames(linqSerializerOptions); + } + else + { + this.CosmosLinqSerializer = TranslationContext.DefaultLinqSerializer; + this.MemberNames = TranslationContext.DefaultMemberNames; + } } public Expression LookupSubstitution(ParameterExpression parameter) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs index e11c145c71..dbe6809017 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs @@ -21,7 +21,7 @@ public static Type GetElementType(Type type) public static string GetMemberName(this MemberInfo memberInfo, TranslationContext context) { - return context.CosmosLinqSerializer.SerializeMemberName(memberInfo, context.LinqSerializerOptions); + return context.CosmosLinqSerializer.SerializeMemberName(memberInfo); } private static Type GetElementType(Type type, HashSet visitedSet) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 13e5425d7d..2397ab437a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -24,9 +24,9 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; - using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -387,14 +387,16 @@ public override IOrderedQueryable GetItemLinqQueryable( { requestOptions ??= new QueryRequestOptions(); - if (linqSerializerOptions == null && this.ClientContext.ClientOptions.SerializerOptions != null) + if (this.ClientContext.ClientOptions != null) { - linqSerializerOptions = new CosmosLinqSerializerOptions + linqSerializerOptions ??= new CosmosLinqSerializerOptions { - PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions.PropertyNamingPolicy + PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default }; } + CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal = CosmosLinqSerializerOptionsInternal.Create(linqSerializerOptions, this.ClientContext.ClientOptions.Serializer); + return new CosmosLinqQuery( this, this.ClientContext.ResponseFactory, @@ -402,7 +404,7 @@ public override IOrderedQueryable GetItemLinqQueryable( continuationToken, requestOptions, allowSynchronousQueryExecution, - linqSerializerOptions); + linqSerializerOptionsInternal); } public override FeedIterator GetItemQueryIterator( diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs new file mode 100644 index 0000000000..e58abfcebd --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos +{ + using System.Reflection; + + /// + /// This abstract class can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) + /// to be used by the CosmosClient for LINQ queries. + /// + /// + /// This example implements the CosmosLinqSerializer contract. + /// This example custom serializer will honor System.Text.Json attributes. + /// + /// (Stream stream) + /// { + /// if (stream == null) + /// throw new ArgumentNullException(nameof(stream)); + /// + /// using (stream) + /// { + /// if (stream.CanSeek && stream.Length == 0) + /// { + /// return default; + /// } + /// + /// if (typeof(Stream).IsAssignableFrom(typeof(T))) + /// { + /// return (T)(object)stream; + /// } + /// + /// return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + /// } + /// } + /// + /// public override Stream ToStream(T input) + /// { + /// MemoryStream streamPayload = new MemoryStream(); + /// this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default); + /// streamPayload.Position = 0; + /// return streamPayload; + /// } + /// + /// public override string SerializeMemberName(MemberInfo memberInfo) + /// { + /// JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + /// + /// string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name) + /// ? jsonPropertyNameAttribute.Name + /// : memberInfo.Name; + /// + /// return memberName; + /// } + /// } + /// ]]> + /// + /// +#if PREVIEW + public +#else + internal +#endif + abstract class CosmosLinqSerializer : CosmosSerializer + { + /// + /// Convert a MemberInfo to a string for use in LINQ query translation. + /// This must be implemented when using a custom serializer for LINQ queries. + /// + /// Any MemberInfo used in the query. + /// A serialized representation of the member. + public abstract string SerializeMemberName(MemberInfo memberInfo); + } +} diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs new file mode 100644 index 0000000000..638c05c47b --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serializer +{ + using System; + using Microsoft.Azure.Cosmos; + + /// + /// This class stores user-provided LINQ Serialization Properties. + /// + internal sealed class CosmosLinqSerializerOptionsInternal + { + /// + /// Creates an instance of CosmosSerializationOptionsInternal. + /// + public static CosmosLinqSerializerOptionsInternal Create( + CosmosLinqSerializerOptions cosmosLinqSerializerOptions, + CosmosSerializer customCosmosSerializer) + { + if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) + { + if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) + { + throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if using custom serializer for LINQ translations. See https://aka.ms/CosmosDB/dotnetlinq for more information."); + } + + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); + } + else + { + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, null); + } + } + + private CosmosLinqSerializerOptionsInternal( + CosmosLinqSerializerOptions cosmosLinqSerializerOptions, + CosmosLinqSerializer customCosmosLinqSerializer) + { + this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; + this.CustomCosmosLinqSerializer = customCosmosLinqSerializer; + } + + /// + /// User-provided CosmosLinqSerializerOptions. + /// + public CosmosLinqSerializerOptions CosmosLinqSerializerOptions { get; } + + /// + /// User defined customer serializer, if one exists. + /// Otherwise set to null. + /// + public CosmosLinqSerializer CustomCosmosLinqSerializer { get; } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index b2e0838248..8946d9ac7a 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -4,26 +4,31 @@ namespace Microsoft.Azure.Cosmos { - using Microsoft.Azure.Cosmos.Serializer; + using System; using Newtonsoft.Json.Serialization; internal static class CosmosSerializationUtil { private static readonly CamelCaseNamingStrategy camelCaseNamingStrategy = new CamelCaseNamingStrategy(); - internal static string ToCamelCase(string name) - { - return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); - } - internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOptions options, string name) { - if (options != null && options.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + if (options == null) { - return CosmosSerializationUtil.ToCamelCase(name); + return name; } - return name; + return GetStringWithPropertyNamingPolicy(options.PropertyNamingPolicy, name); + } + + internal static string GetStringWithPropertyNamingPolicy(CosmosPropertyNamingPolicy namingPolicy, string name) + { + return namingPolicy switch + { + CosmosPropertyNamingPolicy.CamelCase => CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false), + CosmosPropertyNamingPolicy.Default => name, + _ => throw new NotImplementedException("Unsupported CosmosPropertyNamingPolicy value"), + }; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index 3632bd6740..e6bd34d1f5 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -7,8 +7,11 @@ namespace Microsoft.Azure.Cosmos using System.IO; /// - /// This is an interface to allow a custom serializer to be used by the CosmosClient + /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient. /// + /// + /// To use the custom serializer for LINQ queries, must be implemented. + /// public abstract class CosmosSerializer { /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml new file mode 100644 index 0000000000..1a7337ba81 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml @@ -0,0 +1,126 @@ + + + + + doc.NumericField), Object)]]> + + + + + + + + + doc.NumericField), Object)]]> + + + + + + + + Filter -> Select -> Average, Custom serializer: True]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Average(), Object)]]> + + + + + + + + Skip -> Count, Custom serializer: True]]> + f.NumericField).Skip(2).Count(), Object)]]> + + + + + + + + + Min w/ mapping]]> + doc.NumericField).Min(num => num), Object)]]> + + + + + + + + + doc.NumericField), Object)]]> + + + + + + + + + doc.NumericField), Object)]]> + + + + + + + + Filter -> Select -> Average, Custom serializer: False]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Average(), Object)]]> + + + + + + + + Skip -> Count, Custom serializer: False]]> + f.NumericField).Skip(2).Count(), Object)]]> + + + + + + + + + Min w/ mapping]]> + doc.NumericField).Min(num => num), Object)]]> + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml new file mode 100644 index 0000000000..752edecde8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml @@ -0,0 +1,58 @@ + + + + + doc.NumericField), Object)]]> + + + + . Actual:<0>. ]]> + + + + + + doc.NumericField), Object)]]> + + + + . Actual:<0>. ]]> + + + + + + + + + 0) +FROM ( + SELECT VALUE root + FROM root) AS v0 +]]> + + + + + + Filter -> Select -> Any]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Any(), Object)]]> + + + 0) +FROM ( + SELECT VALUE m0 + FROM root + JOIN m0 IN root["ArrayField"] + WHERE ((m0 % 3) = 0)) AS v0 +]]> + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml new file mode 100644 index 0000000000..a9c63ebf41 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml @@ -0,0 +1,143 @@ + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectDotNet() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectDotNet() {NumericField = 1, StringField = "1"}, new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) +FROM root]]> + + + + + + + + (doc == new DataObjectDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + + + + x).OrderBy(x => x.NumericField).Take(5)]]> + + + + + + + + + + + IIF((c.NumericField > 1), "true", "false"))]]> + + + 1) ? "true" : "false") +FROM root]]> + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml similarity index 84% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml index b381e2b651..df12581f67 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml @@ -8,7 +8,7 @@ +WHERE (root["numericFieldDataMember"] = 1)]]> - (doc == new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> +WHERE (root = {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"})]]> - new DataObjectDotNet() {NumericField = 1, StringField = "1"})]]> + new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectDotNet() {NumericField = 1, StringField = "1"}, new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> 1) ? {"NumericField": 1, "StringField": "1", "id": null, "Pk": null} : {"NumericField": 1, "StringField": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numericFieldDataMember"] > 1) ? {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"} : {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"}) FROM root]]> - (doc == new DataObjectDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectDotNetDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> +WHERE (root = {"numericFieldDataMember": root["numericFieldDataMember"], "stringFieldDataMember": root["stringFieldDataMember"]})]]> +WHERE (root["NumericFieldDataMember"] = 1)]]> - (doc == new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> +WHERE (root = {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"})]]> - new DataObjectDotNet() {NumericField = 1, StringField = "1"})]]> + new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectDotNet() {NumericField = 1, StringField = "1"}, new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> 1) ? {"NumericField": 1, "StringField": "1", "id": null, "Pk": null} : {"NumericField": 1, "StringField": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["NumericFieldDataMember"] > 1) ? {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"} : {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"}) FROM root]]> - (doc == new DataObjectDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectDotNetDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> +WHERE (root = {"NumericFieldDataMember": root["NumericFieldDataMember"], "StringFieldDataMember": root["StringFieldDataMember"]})]]> @@ -31,12 +31,12 @@ SELECT VALUE root FROM root WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null})]]> @@ -51,12 +51,12 @@ WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": n SELECT VALUE {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} FROM root]]> SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) FROM root]]> @@ -127,12 +127,12 @@ SELECT VALUE root FROM root WHERE (root["NumberValueNewtonsoft"] = 1)]]> @@ -148,12 +148,12 @@ SELECT VALUE root FROM root WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null})]]> @@ -168,12 +168,12 @@ WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": n SELECT VALUE {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} FROM root]]> SELECT VALUE ((root["NumberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) FROM root]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml new file mode 100644 index 0000000000..215f54921f --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml @@ -0,0 +1,236 @@ + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"}) +FROM root]]> + + + + + + + + (doc == new DataObjectNewtonsoftDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"}) +FROM root]]> + + + + + + + + (doc == new DataObjectNewtonsoftDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerMultiSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml similarity index 92% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerMultiSerializer.xml rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml index 704eb39807..be82c8dea7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerMultiSerializer.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml @@ -23,7 +23,7 @@ WHERE (root["numberValueNewtonsoft"] = 1)]]> - (doc == new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}, new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectMultiSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - (doc == new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}, new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectMultiSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + { + private static CosmosSerializer customCosmosLinqSerializer; + private static CosmosClient clientLinq; + private static Cosmos.Database testDbLinq; + private static Container testContainerLinq; + private static IQueryable lastExecutedScalarQuery; + + private static CosmosSerializer customCosmosSerializer; + private static CosmosClient client; + private static Cosmos.Database testDb; + private static Container testContainer; + + [ClassInitialize] + public async static Task Initialize(TestContext textContext) + { + customCosmosLinqSerializer = new SystemTextJsonLinqSerializer(new JsonSerializerOptions()); + clientLinq = TestCommon.CreateCosmosClient((cosmosClientBuilder) + => cosmosClientBuilder.WithCustomSerializer(customCosmosLinqSerializer)); + + // Set a callback to get the handle of the last executed query to do the verification + // This is neede because aggregate queries return type is a scalar so it can't be used + // to verify the translated LINQ directly as other queries type. + clientLinq.DocumentClient.OnExecuteScalarQueryCallback = q => lastExecutedScalarQuery = q; + + string dbName = $"{nameof(LinqAggregateCustomSerializationBaseline)}-{Guid.NewGuid().ToString("N")}"; + testDbLinq = await clientLinq.CreateDatabaseAsync(dbName); + testContainerLinq = testDbLinq.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")).Result; + + customCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + + client = TestCommon.CreateCosmosClient((cosmosClientBuilder) + => cosmosClientBuilder.WithCustomSerializer(customCosmosSerializer)); + + // Set a callback to get the handle of the last executed query to do the verification + // This is neede because aggregate queries return type is a scalar so it can't be used + // to verify the translated LINQ directly as other queries type. + client.DocumentClient.OnExecuteScalarQueryCallback = q => lastExecutedScalarQuery = q; + + dbName = $"{nameof(LinqAggregateCustomSerializationBaseline)}-{Guid.NewGuid().ToString("N")}"; + testDb = await client.CreateDatabaseAsync(dbName); + testContainer = testDb.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")).Result; + } + + [ClassCleanup] + public async static Task CleanUp() + { + if (testDbLinq != null) + { + await testDbLinq.DeleteStreamAsync(); + } + + clientLinq?.Dispose(); + + if (testDb != null) + { + await testDb.DeleteStreamAsync(); + } + + client?.Dispose(); + } + + [TestMethod] + [Owner("mayapainter")] + public void TestAggregateQueriesWithCustomSerializer() + { + static DataObjectDotNet createDataObj(int index, bool camelCase) + { + DataObjectDotNet obj = new DataObjectDotNet + { + NumericField = index, + StringField = index.ToString(), + ArrayField = new int[] { 1, 2, 3, 4, 5 }, + id = Guid.NewGuid().ToString(), + Pk = "Test" + }; + return obj; + } + + List>> getQueryList = new List>> + { + LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainerLinq, new CosmosLinqSerializerOptions()), + LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, new CosmosLinqSerializerOptions()) + }; + + List inputs = new List(); + + foreach (bool applyCustomSerializer in new List{ true, false }) + { + Func> getQuery = getQueryList[applyCustomSerializer ? 0 : 1]; + + inputs.Add(new LinqAggregateInput( + "Avg, Custom serializer: " + applyCustomSerializer, b => getQuery(b) + .Average(doc => doc.NumericField))); + + inputs.Add(new LinqAggregateInput( + "Sum, Custom serializer: " + applyCustomSerializer, b => getQuery(b) + .Sum(doc => doc.NumericField))); + + inputs.Add(new LinqAggregateInput( + "Select many -> Filter -> Select -> Average, Custom serializer: " + applyCustomSerializer, b => getQuery(b) + .SelectMany(doc => doc.ArrayField.Where(m => (m % 3) == 0).Select(m => m)).Average())); + + inputs.Add(new LinqAggregateInput( + "Select number -> Skip -> Count, Custom serializer: " + applyCustomSerializer, b => getQuery(b) + .Select(f => f.NumericField).Skip(2).Count())); + + inputs.Add(new LinqAggregateInput( + "Select number -> Min w/ mapping", b => getQuery(b) + .Select(doc => doc.NumericField).Min(num => num))); + } + + this.ExecuteTestSuite(inputs); + } + + public override LinqAggregateOutput ExecuteTest(LinqAggregateInput input) + { + lastExecutedScalarQuery = null; + Func compiledQuery = input.expression.Compile(); + + string errorMessage = null; + string query = string.Empty; + try + { + object queryResult; + try + { + queryResult = compiledQuery(true); + } + finally + { + Assert.IsNotNull(lastExecutedScalarQuery, "lastExecutedScalarQuery is not set"); + + query = JObject + .Parse(lastExecutedScalarQuery.ToString()) + .GetValue("query", StringComparison.Ordinal) + .ToString(); + } + } + catch (Exception e) + { + errorMessage = LinqTestsCommon.BuildExceptionMessageForTest(e); + } + + return new LinqAggregateOutput(query, errorMessage); + } + + private class DataObjectDotNet : LinqTestObject + { + [JsonPropertyName("NumberValueDotNet")] + public double NumericField { get; set; } + + [JsonPropertyName("StringValueDotNet")] + public string StringField { get; set; } + + [JsonPropertyName("ArrayValuesDotNet")] + public int[] ArrayField { get; set; } + + public string id { get; set; } + + public string Pk { get; set; } + + public DataObjectDotNet() { } + + public DataObjectDotNet(double numericField, string stringField, int[] arrayField, string id, string pk) + { + this.NumericField = numericField; + this.StringField = stringField; + this.ArrayField = arrayField; + this.id = id; + this.Pk = pk; + } + + public override string ToString() + { + return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs index 5ea03bf0b6..1f511a3c11 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs @@ -172,7 +172,7 @@ public Datum2(string jsonProperty, string dataMember, string defaultMember, stri [TestMethod] public void TestAttributePriority() { - ICosmosLinqSerializer cosmosLinqSerializer = new DefaultCosmosLinqSerializer(); + ICosmosLinqSerializerInternal cosmosLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosPropertyNamingPolicy()); Assert.AreEqual("jsonProperty", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("JsonProperty").First())); Assert.AreEqual("dataMember", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("DataMember").First())); Assert.AreEqual("Default", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("Default").First())); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index c8513e41b8..8cc819014a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -10,18 +10,23 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; + using System.IO; using System.Linq; using System.Linq.Expressions; + using System.Reflection; using System.Runtime.CompilerServices; using System.Text; + using System.Text.Json.Serialization; + using System.Text.Json; using System.Text.RegularExpressions; using System.Xml; + using global::Azure.Core.Serialization; using Microsoft.Azure.Cosmos.Services.Management.Tests.BaselineTest; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - + using Newtonsoft.Json.Linq; + internal class LinqTestsCommon { /// @@ -333,13 +338,13 @@ public static Func> GenerateTestCosmosData(Funcnumber of test data to be created /// the target container /// if theCosmosLinqSerializerOption of camelCaseSerialization should be applied - /// a lambda that takes a boolean which indicate where the query should run against CosmosDB or against original data, and return a query results as IQueryable. Also the serialized payload. - public static Func> GenerateSerializationTestCosmosData(Func func, int count, Container container, bool camelCaseSerialization = false) + /// a lambda that takes a boolean which indicate where the query should run against CosmosDB or against original data, and return a query results as IQueryable. + public static Func> GenerateSerializationTestCosmosData(Func func, int count, Container container, CosmosLinqSerializerOptions linqSerializerOptions) { List data = new List(); for (int i = 0; i < count; i++) { - data.Add(func(i, camelCaseSerialization)); + data.Add(func(i, linqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase)); } foreach (T obj in data) @@ -355,7 +360,6 @@ public static Func> GenerateSerializationTestCosmosData(F #endif }; - CosmosLinqSerializerOptions linqSerializerOptions = new CosmosLinqSerializerOptions { PropertyNamingPolicy = camelCaseSerialization ? CosmosPropertyNamingPolicy.CamelCase : CosmosPropertyNamingPolicy.Default }; IOrderedQueryable query = container.GetItemLinqQueryable(allowSynchronousQueryExecution: true, requestOptions: requestOptions, linqSerializerOptions: linqSerializerOptions); IQueryable getQuery(bool useQuery) => useQuery ? query : data.AsQueryable(); @@ -836,4 +840,93 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } } + + class SystemTextJsonLinqSerializer : CosmosLinqSerializer + { + private readonly JsonObjectSerializer systemTextJsonSerializer; + + public SystemTextJsonLinqSerializer(JsonSerializerOptions jsonSerializerOptions) + { + this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); + } + + public override T FromStream(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + using (stream) + { + if (stream.CanSeek && stream.Length == 0) + { + return default; + } + + if (typeof(Stream).IsAssignableFrom(typeof(T))) + { + return (T)(object)stream; + } + + return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + } + } + + public override Stream ToStream(T input) + { + MemoryStream streamPayload = new MemoryStream(); + this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default); + streamPayload.Position = 0; + return streamPayload; + } + + public override string SerializeMemberName(MemberInfo memberInfo) + { + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + + string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name) + ? jsonPropertyNameAttribute.Name + : memberInfo.Name; + + return memberName; + } + } + + class SystemTextJsonSerializer : CosmosSerializer + { + private readonly JsonObjectSerializer systemTextJsonSerializer; + + public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions) + { + this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); + } + + public override T FromStream(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + using (stream) + { + if (stream.CanSeek && stream.Length == 0) + { + return default; + } + + if (typeof(Stream).IsAssignableFrom(typeof(T))) + { + return (T)(object)stream; + } + + return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + } + } + + public override Stream ToStream(T input) + { + MemoryStream streamPayload = new MemoryStream(); + this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default); + streamPayload.Position = 0; + return streamPayload; + } + } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 3266809926..817b030a60 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using System.Text.Json.Serialization; using System.Threading.Tasks; using BaselineTest; - using global::Azure.Core.Serialization; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -24,6 +23,10 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests [SDK.EmulatorTests.TestClass] public class LinqTranslationWithCustomSerializerBaseline : BaselineTests { + private static CosmosClient CosmosLinqClient; + private static Database TestDbLinq; + private static Container TestLinqContainer; + private static CosmosClient CosmosClient; private static Database TestDb; private static Container TestContainer; @@ -36,8 +39,14 @@ public class LinqTranslationWithCustomSerializerBaseline : BaselineTests cosmosClientBuilder.WithCustomSerializer(new SystemTextJsonLinqSerializer(new JsonSerializerOptions()))); + + string dbNameLinq = $"{nameof(LinqTranslationBaselineTests)}-{Guid.NewGuid():N}"; + TestDbLinq = await CosmosLinqClient.CreateDatabaseAsync(dbNameLinq); + CosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) - => cosmosClientBuilder.WithCustomSerializer(new SystemTextJsonSerializer(new JsonSerializerOptions()))); + => cosmosClientBuilder.WithCustomSerializer(new SystemTextJsonSerializer(new JsonSerializerOptions()))); string dbName = $"{nameof(LinqTranslationBaselineTests)}-{Guid.NewGuid():N}"; TestDb = await CosmosClient.CreateDatabaseAsync(dbName); @@ -46,6 +55,11 @@ public async static Task Initialize(TestContext textContext) [ClassCleanup] public async static Task Cleanup() { + if (TestDbLinq != null) + { + await TestDbLinq.DeleteStreamAsync(); + } + if (TestDb != null) { await TestDb.DeleteStreamAsync(); @@ -55,12 +69,14 @@ public async static Task Cleanup() [TestInitialize] public async Task TestInitialize() { + TestLinqContainer = await TestDbLinq.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")); TestContainer = await TestDb.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")); } [TestCleanup] public async Task TestCleanup() { + await TestLinqContainer.DeleteContainerStreamAsync(); await TestContainer.DeleteContainerStreamAsync(); } @@ -70,33 +86,23 @@ public override LinqTestOutput ExecuteTest(LinqTestInput input) } [TestMethod] - public void TestMemberInitializerDotNet() + public void TestMemberInitializerDotNetCustomSerializer() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + Func> getQuery; + (_, getQuery) = this.InsertDataAndGetQueryables(true, TestLinqContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestLinqContainer).Result; - List inputs = new List(); - foreach (bool useCamelCaseSerializer in new bool[] { true, false }) + List inputs = new List { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; - - List camelCaseSettingInputs = new List - { - // TODO (10/13/23): extend this and other tests cases as more LINQ features are added (GROUP BY, etc.) - new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - - // Negative test case: serializing only field name using custom serializer not currently supported - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) - }; - - inputs.AddRange(camelCaseSettingInputs); - } + new LinqTestInput("Filter w/ constant value", b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value", b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer", b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference", b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization", b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData), + new LinqTestInput("OrderBy query", b => getQuery(b).Select(x => x).OrderBy(x => x.NumericField).Take(5), skipVerification : true, inputData: insertedData), + new LinqTestInput("Conditional", b => getQuery(b).Select(c => c.NumericField > 1 ? "true" : "false"), skipVerification : true, inputData: insertedData), + }; this.ExecuteTestSuite(inputs); } @@ -106,9 +112,9 @@ public void TestMemberInitializerNewtonsoft() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -121,8 +127,6 @@ public void TestMemberInitializerNewtonsoft() new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - - // Negative test case: serializing only field name using custom serializer not currently supported new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoft() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; @@ -137,9 +141,9 @@ public void TestMemberInitializerDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -152,8 +156,6 @@ public void TestMemberInitializerDataMember() new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDataMember() { NumericField = 1, StringField = "1" } : new DataObjectDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - - // Negative test case: serializing only field name using custom serializer not currently supported new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDataMember() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; @@ -164,28 +166,55 @@ public void TestMemberInitializerDataMember() } [TestMethod] - public void TestMemberInitializerMultiSerializer() + public void TestMemberInitializerNewtonsoftDotNet() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; List camelCaseSettingInputs = new List { new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" } : new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + }; + + inputs.AddRange(camelCaseSettingInputs); + } - // Negative test case: serializing only field name using custom serializer not currently supported - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectMultiSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + this.ExecuteTestSuite(inputs); + } + + [TestMethod] + public void TestMemberInitializerNewtonsoftDataMember() + { + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); + + string insertedData = this.GetInsertedData(TestContainer).Result; + + List inputs = new List(); + foreach (bool useCamelCaseSerializer in new bool[] { true, false }) + { + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + + List camelCaseSettingInputs = new List + { + new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMember() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; inputs.AddRange(camelCaseSettingInputs); @@ -194,7 +223,36 @@ public void TestMemberInitializerMultiSerializer() this.ExecuteTestSuite(inputs); } - private (Func>, Func>) InsertDataAndGetQueryables() where T : LinqTestObject + [TestMethod] + public void TestMemberInitializerDotNetDataMember() + { + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); + + string insertedData = this.GetInsertedData(TestContainer).Result; + + List inputs = new List(); + foreach (bool useCamelCaseSerializer in new bool[] { true, false }) + { + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + + List camelCaseSettingInputs = new List + { + new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" } : new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMember() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + }; + + inputs.AddRange(camelCaseSettingInputs); + } + + this.ExecuteTestSuite(inputs); + } + + private (Func>, Func>) InsertDataAndGetQueryables(bool customSerializer, Container container) where T : LinqTestObject { static T createDataObj(int index, bool camelCase) { @@ -205,16 +263,28 @@ static T createDataObj(int index, bool camelCase) return obj; } - Func> getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, camelCaseSerialization: true); - Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, camelCaseSerialization: false); + CosmosLinqSerializerOptions linqSerializerOptionsCamelCase = new CosmosLinqSerializerOptions + { + PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, + }; + + CosmosLinqSerializerOptions linqSerializerOptionsDefault = new CosmosLinqSerializerOptions(); + + Func> getQueryCamelCase = null; + if (!customSerializer) + { + getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, container, linqSerializerOptionsCamelCase); + } + + Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, container, linqSerializerOptionsDefault); return (getQueryCamelCase, getQueryDefault); } - private async Task GetInsertedData() + private async Task GetInsertedData(Container container) { List insertedDataList = new List(); - using (FeedIterator feedIterator = TestContainer.GetItemQueryStreamIterator("SELECT * FROM c")) + using (FeedIterator feedIterator = container.GetItemQueryStreamIterator("SELECT * FROM c")) { while (feedIterator.HasMoreResults) { @@ -244,64 +314,91 @@ private async Task GetInsertedData() } } - string insertedData = JsonConvert.SerializeObject(insertedDataList.Select(item => item), new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented }); - return insertedData; + return JsonConvert.SerializeObject(insertedDataList.Select(item => item), new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented }); } - private class SystemTextJsonSerializer : CosmosSerializer + private class DataObjectDotNet : LinqTestObject { - private readonly JsonObjectSerializer systemTextJsonSerializer; + [JsonPropertyName("NumberValueDotNet")] + public double NumericField { get; set; } + + [JsonPropertyName("StringValueDotNet")] + public string StringField { get; set; } + + [System.Text.Json.Serialization.JsonIgnore] + public string IgnoreField { get; set; } + + public string id { get; set; } + + public string Pk { get; set; } + + public DataObjectDotNet() { } - public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions) + public DataObjectDotNet(double numericField, string stringField, string id, string pk) { - this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); + this.NumericField = numericField; + this.StringField = stringField; + this.IgnoreField = "Ignore"; + this.id = id; + this.Pk = pk; } - public override T FromStream(Stream stream) + public override string ToString() { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; + } + } - using (stream) - { - if (stream.CanSeek && stream.Length == 0) - { - return default; - } + private class DataObjectNewtonsoft : LinqTestObject + { + [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] + public double NumericField { get; set; } - if (typeof(Stream).IsAssignableFrom(typeof(T))) - { - return (T)(object)stream; - } + [Newtonsoft.Json.JsonProperty(PropertyName = "StringValueNewtonsoft")] + public string StringField { get; set; } - return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); - } + [Newtonsoft.Json.JsonIgnore] + public string IgnoreField { get; set; } + + public string id { get; set; } + + public string Pk { get; set; } + + public DataObjectNewtonsoft() { } + + public DataObjectNewtonsoft(double numericField, string stringField, string id, string pk) + { + this.NumericField = numericField; + this.StringField = stringField; + this.IgnoreField = "ignore"; + this.id = id; + this.Pk = pk; } - public override Stream ToStream(T input) + public override string ToString() { - MemoryStream streamPayload = new MemoryStream(); - this.systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default); - streamPayload.Position = 0; - return streamPayload; + return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; } } - private class DataObjectDotNet : LinqTestObject + [DataContract] + private class DataObjectDataMember : LinqTestObject { - [JsonPropertyName("numberValueDotNet")] + [DataMember(Name = "NumericFieldDataMember")] public double NumericField { get; set; } - [JsonPropertyName("stringValueDotNet")] + [DataMember(Name = "StringFieldDataMember")] public string StringField { get; set; } + [DataMember(Name = "id")] public string id { get; set; } + [DataMember(Name = "Pk")] public string Pk { get; set; } - public DataObjectDotNet() { } + public DataObjectDataMember() { } - public DataObjectDotNet(double numericField, string stringField, string id, string pk) + public DataObjectDataMember(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; @@ -315,21 +412,23 @@ public override string ToString() } } - private class DataObjectNewtonsoft : LinqTestObject + private class DataObjectNewtonsoftDotNet : LinqTestObject { [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] + [JsonPropertyName("numberValueDotNet")] public double NumericField { get; set; } [Newtonsoft.Json.JsonProperty(PropertyName = "StringValueNewtonsoft")] + [JsonPropertyName("stringValueDotNet")] public string StringField { get; set; } public string id { get; set; } public string Pk { get; set; } - public DataObjectNewtonsoft() { } + public DataObjectNewtonsoftDotNet() { } - public DataObjectNewtonsoft(double numericField, string stringField, string id, string pk) + public DataObjectNewtonsoftDotNet(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; @@ -344,23 +443,23 @@ public override string ToString() } [DataContract] - private class DataObjectDataMember : LinqTestObject + private class DataObjectNewtonsoftDataMember : LinqTestObject { + [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] [DataMember(Name = "NumericFieldDataMember")] public double NumericField { get; set; } + [Newtonsoft.Json.JsonProperty(PropertyName = "StringValueNewtonsoft")] [DataMember(Name = "StringFieldDataMember")] public string StringField { get; set; } - [DataMember(Name = "id")] public string id { get; set; } - [DataMember(Name = "Pk")] public string Pk { get; set; } - public DataObjectDataMember() { } + public DataObjectNewtonsoftDataMember() { } - public DataObjectDataMember(double numericField, string stringField, string id, string pk) + public DataObjectNewtonsoftDataMember(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; @@ -374,13 +473,14 @@ public override string ToString() } } - private class DataObjectMultiSerializer : LinqTestObject + [DataContract] + private class DataObjectDotNetDataMember : LinqTestObject { - [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] + [DataMember(Name = "NumericFieldDataMember")] [JsonPropertyName("numberValueDotNet")] public double NumericField { get; set; } - [Newtonsoft.Json.JsonProperty(PropertyName = "StringValueNewtonsoft")] + [DataMember(Name = "StringFieldDataMember")] [JsonPropertyName("stringValueDotNet")] public string StringField { get; set; } @@ -388,9 +488,9 @@ private class DataObjectMultiSerializer : LinqTestObject public string Pk { get; set; } - public DataObjectMultiSerializer() { } + public DataObjectDotNetDataMember() { } - public DataObjectMultiSerializer(double numericField, string stringField, string id, string pk) + public DataObjectDotNetDataMember(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 7dd766d5d3..83de702ef3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -40,9 +40,11 @@ - - + + + + @@ -123,6 +125,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -264,13 +269,19 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest + + + PreserveNewest + + PreserveNewest diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index dde2d208bc..94aae6155b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -352,6 +352,34 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": { + "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + } + }, + "Members": {}, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.DedicatedGatewayRequestOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 8e50a21dd0..0931271d92 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -10,7 +10,10 @@ namespace Microsoft.Azure.Cosmos.Linq using System.IO; using System.Linq; using System.Linq.Expressions; + using System.Reflection; + using System.Text.Json.Serialization; using global::Azure.Core.Serialization; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -18,6 +21,8 @@ namespace Microsoft.Azure.Cosmos.Linq [TestClass] public class CosmosLinqJsonConverterTests { + private readonly CosmosLinqSerializerOptions defaultOptions = new(); + [TestMethod] public void DateTimeKindIsPreservedTest() { @@ -37,63 +42,59 @@ public void DateTimeKindIsPreservedTest() [TestMethod] public void EnumIsPreservedAsINTest() { - // Arrange - CosmosLinqSerializerOptions options = new() - { - //CustomCosmosSerializer = new TestCustomJsonSerializer() - }; + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonSerializer()); - // Act TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; + Expression> expr = a => values.Contains(a.Value); - string sql = SqlTranslator.TranslateExpression(expr.Body, options); - // Assert - // Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); // <- TODO - Desired Behavior with CustomSerializer - Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sql); // <- Actual behavior, with ability to set custom serializor reverted + Expression> exprNewtonsoft = a => values.Contains(a.Value); + string sqlDefault = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, defaultOptions); + + Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); + Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlDefault); } [TestMethod] public void EnumIsPreservedAsEQUALSTest() { - // Arrange - CosmosLinqSerializerOptions options = new() - { - // CustomCosmosSerializer = new TestCustomJsonSerializer() - }; + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonSerializer()); - // Act TestEnum statusValue = TestEnum.One; - Expression> expr = a => a.Value == statusValue; + Expression> expr = a => a.Value == statusValue; string sql = SqlTranslator.TranslateExpression(expr.Body, options); - // Assert - // Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); // <- THIS is the correct value, if we are able to use the custom serializer - Assert.AreEqual("(a[\"Value\"] = 0)", sql); // <- THIS is the current mis-behavior of the SDK + Expression> exprDefault = a => a.Value == statusValue; + string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprDefault.Body, defaultOptions); + + Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); } [TestMethod] public void EnumIsPreservedAsEXPRESSIONTest() { - // Arrange - CosmosLinqSerializerOptions options = new() - { - // CustomCosmosSerializer = new TestCustomJsonSerializer() - }; - - // Act + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonSerializer()); // Get status constant ConstantExpression status = Expression.Constant(TestEnum.One); // Get member access expression - ParameterExpression arg = Expression.Parameter(typeof(TestEnumNewtonsoftDocument), "a"); + ParameterExpression arg = Expression.Parameter(typeof(TestEnumDocument), "a"); + ParameterExpression argNewtonsoft = Expression.Parameter(typeof(TestEnumNewtonsoftDocument), "a"); // Access the value property MemberExpression docValueExpression = Expression.MakeMemberAccess( arg, + typeof(TestEnumDocument).GetProperty(nameof(TestEnumDocument.Value))! + ); + MemberExpression docValueExpressionDefault = Expression.MakeMemberAccess( + argNewtonsoft, typeof(TestEnumNewtonsoftDocument).GetProperty(nameof(TestEnumNewtonsoftDocument.Value))! ); @@ -102,15 +103,22 @@ public void EnumIsPreservedAsEXPRESSIONTest() docValueExpression, status ); + BinaryExpression expressionDefault = Expression.Equal( + docValueExpressionDefault, + status + ); // Create lambda expression - Expression> lambda = - Expression.Lambda>(expression, arg); - + Expression> lambda = + Expression.Lambda>(expression, arg); string sql = SqlTranslator.TranslateExpression(lambda.Body, options); - // Assert + Expression> lambdaNewtonsoft = + Expression.Lambda>(expressionDefault, argNewtonsoft); + string sqlDefault = SqlTranslator.TranslateExpression(lambdaNewtonsoft.Body, defaultOptions); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlDefault); } enum TestEnum @@ -122,19 +130,18 @@ enum TestEnum class TestEnumDocument { - [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] // TODO: Remove this once we have the ability to use custom serializer for LINQ queries public TestEnum Value { get; set; } } class TestEnumNewtonsoftDocument { - [JsonConverter(typeof(StringEnumConverter))] + [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] public TestEnum Value { get; set; } } class TestDocument { - [JsonConverter(typeof(DateJsonConverter))] + [Newtonsoft.Json.JsonConverter(typeof(DateJsonConverter))] public DateTime StartDate { get; set; } } @@ -156,8 +163,10 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s [TestMethod] public void TestNewtonsoftExtensionDataQuery() { + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, null); + Expression> expr = a => (string)a.NewtonsoftExtensionData["foo"] == "bar"; - string sql = SqlTranslator.TranslateExpression(expr.Body); + string sql = SqlTranslator.TranslateExpression(expr.Body, defaultOptions); Assert.AreEqual("(a[\"foo\"] = \"bar\")", sql); } @@ -165,11 +174,11 @@ public void TestNewtonsoftExtensionDataQuery() [TestMethod] public void TestSystemTextJsonExtensionDataQuery() { + CosmosLinqSerializerOptionsInternal dotNetOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + Expression> expr = a => ((object)a.NetExtensionData["foo"]) == "bar"; - string sql = SqlTranslator.TranslateExpression(expr.Body); + string sql = SqlTranslator.TranslateExpression(expr.Body, dotNetOptions); - // TODO: This is a limitation in the translator. It should be able to handle STJ extension data, if a custom - // JSON serializer is specified. Assert.AreEqual("(a[\"NetExtensionData\"][\"foo\"] = \"bar\")", sql); } @@ -182,6 +191,66 @@ class DocumentWithExtensionData public Dictionary NetExtensionData { get; set; } } + /// + // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs + /// + class TestCustomJsonLinqSerializer : CosmosLinqSerializer + { + private readonly JsonObjectSerializer systemTextJsonSerializer; + + public static readonly System.Text.Json.JsonSerializerOptions JsonOptions = new() + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + Converters = { + new System.Text.Json.Serialization.JsonStringEnumConverter(), + } + }; + + public TestCustomJsonLinqSerializer() + { + this.systemTextJsonSerializer = new JsonObjectSerializer(JsonOptions); + } + + public override T FromStream(Stream stream) + { + using (stream) + { + if (stream.CanSeek && stream.Length == 0) + { + return default; + } + + if (typeof(Stream).IsAssignableFrom(typeof(T))) + { + return (T)(object)stream; + } + + return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + } + } + + public override Stream ToStream(T input) + { + MemoryStream stream = new(); + + this.systemTextJsonSerializer.Serialize(stream, input, input.GetType(), default); + stream.Position = 0; + return stream; + } + + public override string SerializeMemberName(MemberInfo memberInfo) + { + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + + string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name) + ? jsonPropertyNameAttribute.Name + : memberInfo.Name; + + return memberName; + } + } + /// // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs /// @@ -223,9 +292,9 @@ public override T FromStream(Stream stream) public override Stream ToStream(T input) { - MemoryStream stream = new (); + MemoryStream stream = new(); - this.systemTextJsonSerializer.Serialize(stream, input, typeof(T), default); + this.systemTextJsonSerializer.Serialize(stream, input, input.GetType(), default); stream.Position = 0; return stream; }