From 10ab70be7703e99d467b5ff790601a560830415a Mon Sep 17 00:00:00 2001 From: irina-herciu Date: Wed, 9 Apr 2025 12:21:15 +0300 Subject: [PATCH] Support ScannedCount on DynamoDb Document Model --- .../778f8bc5-440c-4d7f-8f02-2224fbc2649d.json | 11 ++ .../DynamoDBv2/Custom/DocumentModel/Search.cs | 38 ++++- .../DynamoDBv2/Properties/AssemblyInfo.cs | 1 + ...K.UnitTests.DynamoDBv2.NetFramework.csproj | 16 +- .../UnitTests/Custom/MockingTests.cs | 10 +- .../UnitTests/Custom/SearchTests.cs | 145 ++++++++++++++++++ ...SSDK.UnitTestUtilities.NetFramework.csproj | 15 +- 7 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 generator/.DevConfigs/778f8bc5-440c-4d7f-8f02-2224fbc2649d.json create mode 100644 sdk/test/Services/DynamoDBv2/UnitTests/Custom/SearchTests.cs diff --git a/generator/.DevConfigs/778f8bc5-440c-4d7f-8f02-2224fbc2649d.json b/generator/.DevConfigs/778f8bc5-440c-4d7f-8f02-2224fbc2649d.json new file mode 100644 index 000000000000..4597dafee1fa --- /dev/null +++ b/generator/.DevConfigs/778f8bc5-440c-4d7f-8f02-2224fbc2649d.json @@ -0,0 +1,11 @@ +{ + "services": [ + { + "serviceName": "DynamoDBv2", + "type": "patch", + "changeLogMessages": [ + "Exposed ScannedCount property on the Search class within the Document Model." + ] + } + ] +} \ No newline at end of file diff --git a/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Search.cs b/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Search.cs index 3078ef91182a..b9dd72576382 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Search.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Search.cs @@ -163,6 +163,22 @@ public partial interface ISearch /// int Count { get; } + /// + /// Gets the total number of items evaluated, before any ScanFilter is applied. + /// + /// The number of items evaluated, before any ScanFilter is applied. A high ScannedCount + /// value with few, or no, Count results indicates an inefficient Scan operation. + /// For more information, see Count + /// and ScannedCount in the Amazon DynamoDB Developer Guide. + /// + /// + /// + /// If you did not use a filter in the request, then ScannedCount is the same as + /// Count. + /// + /// + int ScannedCount { get; } + /// /// Name of the index to query or scan against. /// @@ -258,7 +274,10 @@ internal set public int Segment { get; set; } /// - public int Count { get { return GetCount(); } } + public int Count => GetCount(); + + /// + public int ScannedCount => scannedCount; /// public string IndexName { get; internal set; } @@ -341,6 +360,7 @@ internal List GetNextSetHelper() } } NextKey = scanResult.LastEvaluatedKey; + scannedCount = scanResult.ScannedCount.GetValueOrDefault(); if (NextKey == null || NextKey.Count == 0) { IsDone = true; @@ -391,6 +411,7 @@ internal List GetNextSetHelper() } } NextKey = queryResult.LastEvaluatedKey; + scannedCount = queryResult.ScannedCount.GetValueOrDefault(); if (NextKey == null || NextKey.Count == 0) { IsDone = true; @@ -455,6 +476,8 @@ internal async Task> GetNextSetHelperAsync(CancellationToken canc } } NextKey = scanResult.LastEvaluatedKey; + scannedCount = scanResult.ScannedCount.GetValueOrDefault(); + if (NextKey == null || NextKey.Count == 0) { IsDone = true; @@ -497,6 +520,8 @@ internal async Task> GetNextSetHelperAsync(CancellationToken canc } } NextKey = queryResult.LastEvaluatedKey; + scannedCount = queryResult.ScannedCount.GetValueOrDefault(); + if (NextKey == null || NextKey.Count == 0) { IsDone = true; @@ -517,10 +542,12 @@ internal List GetRemainingHelper() while (!IsDone) { + var previousScannedCount = scannedCount; foreach (Document doc in GetNextSetHelper()) { ret.Add(doc); } + scannedCount += previousScannedCount; } return ret; @@ -533,10 +560,12 @@ internal async Task> GetRemainingHelperAsync(CancellationToken ca while (!IsDone) { + var previousScannedCount = scannedCount; foreach (Document doc in await GetNextSetHelperAsync(cancellationToken).ConfigureAwait(false)) { ret.Add(doc); } + scannedCount += previousScannedCount; } return ret; @@ -545,6 +574,8 @@ internal async Task> GetRemainingHelperAsync(CancellationToken ca private int count; + private int scannedCount; + private SearchType SearchMethod { get; set; } internal Table SourceTable { get; set; } @@ -648,6 +679,8 @@ private int GetCount() var scanResult = internalClient.Scan(scanReq); count = Matches.Count + scanResult.Count.GetValueOrDefault(); + scannedCount = scanResult.ScannedCount.GetValueOrDefault(); + return count; case SearchType.Query: QueryRequest queryReq = new QueryRequest @@ -673,6 +706,8 @@ private int GetCount() var queryResult = internalClient.Query(queryReq); count = Matches.Count + queryResult.Count.GetValueOrDefault(); + scannedCount = queryResult.ScannedCount.GetValueOrDefault(); + return count; default: throw new InvalidOperationException("Unknown Search Method"); @@ -687,6 +722,7 @@ private int GetCount() internal void Reset() { count = -1; + scannedCount = 0; IsDone = false; NextKey = null; Matches = new List(); diff --git a/sdk/src/Services/DynamoDBv2/Properties/AssemblyInfo.cs b/sdk/src/Services/DynamoDBv2/Properties/AssemblyInfo.cs index e158b5008ede..6f8c7c936593 100644 --- a/sdk/src/Services/DynamoDBv2/Properties/AssemblyInfo.cs +++ b/sdk/src/Services/DynamoDBv2/Properties/AssemblyInfo.cs @@ -18,6 +18,7 @@ #else #error Unknown platform constant - unable to set correct AssemblyDescription #endif +[assembly: InternalsVisibleTo("AWSSDK.UnitTests.DynamoDBv2.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyProduct("Amazon Web Services SDK for .NET")] diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj b/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj index f958bebf82b6..6f48c6c07e0a 100644 --- a/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj +++ b/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj @@ -1,4 +1,4 @@ - + true net472 @@ -34,11 +34,23 @@ 9.0 + + + + ..\..\..\..\awssdk.dll.snk + + + + + $(AWSKeyFile) + + + - + diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockingTests.cs b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockingTests.cs index 9beb4b085e9e..a48ec038f5a1 100644 --- a/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockingTests.cs +++ b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockingTests.cs @@ -132,12 +132,18 @@ public async Task TestMockingTableClient_ScanAsync() { var mockClient = new Mock(); mockClient.Setup(x => x.ScanAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new ScanResponse { Items = new() }); + .ReturnsAsync(new ScanResponse + { + Items = new(), + ScannedCount = 2 + }); var table = new TableBuilder(mockClient.Object, "TestTable").AddHashKey("Id", DynamoDBEntryType.String).Build(); + var search = table.Scan(new ScanFilter()); // This calls the low-level ScanAsync, which should be valid on the mocked client - await table.Scan(new ScanOperationConfig()).GetNextSetAsync(); + await search.GetNextSetAsync(); + Assert.AreEqual(2,search.ScannedCount); } [TestMethod] diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/Custom/SearchTests.cs b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/SearchTests.cs new file mode 100644 index 000000000000..2bb2b21254da --- /dev/null +++ b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/SearchTests.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using Amazon.DynamoDBv2; +using Amazon.DynamoDBv2.DocumentModel; +using Amazon.DynamoDBv2.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace AWSSDK_DotNet.UnitTests +{ + [TestClass] + public class SearchTests + { + private readonly Table _mockTable; + private readonly Mock _mockDynamoDBClient; + private readonly Search _search; + + public SearchTests() + { + _mockDynamoDBClient = new Mock(); + _mockTable = new TableBuilder(_mockDynamoDBClient.Object, "TestTable") + .AddHashKey("Id", DynamoDBEntryType.String).Build(); + + // Initialize Search with mocked dependencies + _search = new Search + { + SourceTable = _mockTable, + TableName = "TestTable", + CollectResults = true, + Filter = new Filter() + }; + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void Reset_ShouldResetSearchState() + { + // Arrange + _search.Matches.Add(new Document()); + + // Act + _search.Reset(); + + // Assert + Assert.AreEqual(0,_search.Matches.Count); + Assert.IsFalse(_search.IsDone); + Assert.IsNull(_search.NextKey); + Assert.IsTrue(_search.CollectResults); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void GetNextSetHelper_ShouldReturnDocuments() + { + // Arrange + var mockDocument = new Document(); + var mockScanResponse = new ScanResponse + { + Items = new List> { new Dictionary() }, + LastEvaluatedKey = null, + ScannedCount = 10, + }; + + _mockDynamoDBClient + .Setup(client => client.Scan(It.IsAny())) + .Returns(mockScanResponse); + + // Act + var result = _search.GetNextSetHelper(); + + // Assert + Assert.AreEqual(1,result.Count); + Assert.AreEqual(mockDocument, result[0]); + Assert.IsTrue(_search.IsDone); + Assert.AreEqual(10, _search.ScannedCount); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void GetRemainingHelper_ShouldReturnAllDocuments() + { + // Arrange + var mockDocument = new Document(); + var mockScanResponse = new ScanResponse + { + Items = new List> { new Dictionary() }, + LastEvaluatedKey = new Dictionary() + { + {"test",new AttributeValue("test")} + }, + ScannedCount = 10, + }; + + var mockLastScanResponse = new ScanResponse + { + Items = new List> { new Dictionary() }, + LastEvaluatedKey = null, + ScannedCount = 20, + }; + + _mockDynamoDBClient + .Setup(client => client.Scan(It.Is(x=> x.ExclusiveStartKey==null))) + .Returns(mockScanResponse); + + _mockDynamoDBClient + .Setup(client => client.Scan(It.Is(x=> x.ExclusiveStartKey != null && x.ExclusiveStartKey.Count==1))) + .Returns(mockLastScanResponse); + + + // Act + var result = _search.GetRemainingHelper(); + + // Assert + Assert.AreEqual(2, result.Count); + Assert.AreEqual(mockDocument, result[0]); + Assert.IsTrue(_search.IsDone); + Assert.AreEqual(30, _search.ScannedCount); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void GetCount_ShouldReturnCorrectCount() + { + // Arrange// Arrange + var mockScanResponse = new ScanResponse + { + Items = new List> { new(), new() }, + LastEvaluatedKey = null, + Count = 2, + ScannedCount = 10, + }; + + _mockDynamoDBClient + .Setup(client => client.Scan(It.IsAny())) + .Returns(mockScanResponse); + _search.Reset(); + // Act + var count = _search.Count; + + // Assert + Assert.AreEqual(2, count); + } + + + } +} \ No newline at end of file diff --git a/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj b/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj index ac49aaa7d208..488c036b0c4a 100644 --- a/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj +++ b/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj @@ -15,10 +15,21 @@ false false false - + true CS1591 - + + + + ..\..\..\awssdk.dll.snk + + + + + $(AWSKeyFile) + + +