-
Notifications
You must be signed in to change notification settings - Fork 865
Support ScannedCount on DynamoDb Document Model #3751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"services": [ | ||
{ | ||
"serviceName": "DynamoDBv2", | ||
"type": "patch", | ||
"changeLogMessages": [ | ||
"Exposed ScannedCount property on the Search class within the Document Model." | ||
] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -163,6 +163,22 @@ public partial interface ISearch | |
/// </summary> | ||
int Count { get; } | ||
|
||
/// <summary> | ||
/// Gets the total number of items evaluated, before any ScanFilter is applied. | ||
/// <para> | ||
/// The number of items evaluated, before any <c>ScanFilter</c> is applied. A high <c>ScannedCount</c> | ||
/// value with few, or no, <c>Count</c> results indicates an inefficient <c>Scan</c> operation. | ||
/// For more information, see <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#Count">Count | ||
/// and ScannedCount</a> in the <i>Amazon DynamoDB Developer Guide</i>. | ||
/// </para> | ||
/// | ||
/// <para> | ||
/// If you did not use a filter in the request, then <c>ScannedCount</c> is the same as | ||
/// <c>Count</c>. | ||
/// </para> | ||
/// </summary> | ||
int ScannedCount { get; } | ||
|
||
/// <summary> | ||
/// Name of the index to query or scan against. | ||
/// </summary> | ||
|
@@ -258,7 +274,10 @@ internal set | |
public int Segment { get; set; } | ||
|
||
/// <inheritdoc/> | ||
public int Count { get { return GetCount(); } } | ||
public int Count => GetCount(); | ||
|
||
/// <inheritdoc/> | ||
public int ScannedCount => scannedCount; | ||
|
||
/// <inheritdoc/> | ||
public string IndexName { get; internal set; } | ||
|
@@ -341,6 +360,7 @@ internal List<Document> GetNextSetHelper() | |
} | ||
} | ||
NextKey = scanResult.LastEvaluatedKey; | ||
scannedCount = scanResult.ScannedCount.GetValueOrDefault(); | ||
if (NextKey == null || NextKey.Count == 0) | ||
{ | ||
IsDone = true; | ||
|
@@ -391,6 +411,7 @@ internal List<Document> GetNextSetHelper() | |
} | ||
} | ||
NextKey = queryResult.LastEvaluatedKey; | ||
scannedCount = queryResult.ScannedCount.GetValueOrDefault(); | ||
if (NextKey == null || NextKey.Count == 0) | ||
{ | ||
IsDone = true; | ||
|
@@ -455,6 +476,8 @@ internal async Task<List<Document>> 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<List<Document>> GetNextSetHelperAsync(CancellationToken canc | |
} | ||
} | ||
NextKey = queryResult.LastEvaluatedKey; | ||
scannedCount = queryResult.ScannedCount.GetValueOrDefault(); | ||
|
||
if (NextKey == null || NextKey.Count == 0) | ||
{ | ||
IsDone = true; | ||
|
@@ -517,10 +542,12 @@ internal List<Document> 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<List<Document>> GetRemainingHelperAsync(CancellationToken ca | |
|
||
while (!IsDone) | ||
{ | ||
var previousScannedCount = scannedCount; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once |
||
foreach (Document doc in await GetNextSetHelperAsync(cancellationToken).ConfigureAwait(false)) | ||
{ | ||
ret.Add(doc); | ||
} | ||
scannedCount += previousScannedCount; | ||
} | ||
|
||
return ret; | ||
|
@@ -545,6 +574,8 @@ internal async Task<List<Document>> 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<Document>(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
#else | ||
#error Unknown platform constant - unable to set correct AssemblyDescription | ||
#endif | ||
[assembly: InternalsVisibleTo("AWSSDK.UnitTests.DynamoDBv2.NetFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI this file is generated, it'll be overwritten by the generator the next time it runs. I do see why you had to add it since a lot of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dscpinheiro in this case is there any issue if I update the AssemblyInfo template and add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That could work, but I think you'd also need to update the test projects to be signed... I'm honestly not a fan of making internals visible; I know we did that for We can until Norm's back to get his opinion. |
||
|
||
[assembly: AssemblyConfiguration("")] | ||
[assembly: AssemblyProduct("Amazon Web Services SDK for .NET")] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IAmazonDynamoDB> _mockDynamoDBClient; | ||
private readonly Search _search; | ||
|
||
public SearchTests() | ||
{ | ||
_mockDynamoDBClient = new Mock<IAmazonDynamoDB>(); | ||
_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<Dictionary<string, AttributeValue>> { new Dictionary<string, AttributeValue>() }, | ||
LastEvaluatedKey = null, | ||
ScannedCount = 10, | ||
}; | ||
|
||
_mockDynamoDBClient | ||
.Setup(client => client.Scan(It.IsAny<ScanRequest>())) | ||
.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<Dictionary<string, AttributeValue>> { new Dictionary<string, AttributeValue>() }, | ||
LastEvaluatedKey = new Dictionary<string, AttributeValue>() | ||
{ | ||
{"test",new AttributeValue("test")} | ||
}, | ||
ScannedCount = 10, | ||
}; | ||
|
||
var mockLastScanResponse = new ScanResponse | ||
{ | ||
Items = new List<Dictionary<string, AttributeValue>> { new Dictionary<string, AttributeValue>() }, | ||
LastEvaluatedKey = null, | ||
ScannedCount = 20, | ||
}; | ||
|
||
_mockDynamoDBClient | ||
.Setup(client => client.Scan(It.Is<ScanRequest>(x=> x.ExclusiveStartKey==null))) | ||
.Returns(mockScanResponse); | ||
|
||
_mockDynamoDBClient | ||
.Setup(client => client.Scan(It.Is<ScanRequest>(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<Dictionary<string, AttributeValue>> { new(), new() }, | ||
LastEvaluatedKey = null, | ||
Count = 2, | ||
ScannedCount = 10, | ||
}; | ||
|
||
_mockDynamoDBClient | ||
.Setup(client => client.Scan(It.IsAny<ScanRequest>())) | ||
.Returns(mockScanResponse); | ||
_search.Reset(); | ||
// Act | ||
var count = _search.Count; | ||
|
||
// Assert | ||
Assert.AreEqual(2, count); | ||
} | ||
|
||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding from our conversation is the scanned count on
Search
would always be an accumulation of everyGetNextSetHelper
call. TheGetRemainingHelper
implementation is doing an accumulation where if I call get next once that sounds the scan count to 100 and then call get remaining scans lets say another 300 through multiple get next calls the end value of Scanned count is 400. But as I read this if I only call get next always resets the scanned count what was scanned in that one.I think for consistency sake we should have scanned count be an accumulation and be sure to document that in the docs for
ScannedCount
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after the last discussion, I've checked the behavior on Java SDK in order to have a similar approach, and there is no accumulation done on paginated search for
scannedCount
. https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/Page.html#scannedCount(). Also there is no similar method in Java withGetRemaining
, and this is why on this method thescannedCount
is accumulating the values.