From 700cfd169ccbe0ca96334327d8c6030943a10f3b Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria <96malhar@gmail.com> Date: Mon, 29 Jul 2024 16:25:20 -0700 Subject: [PATCH 1/3] Add the ability to mock BatchGet and MultiTableBatchGet operations --- .../DynamoDBv2/Custom/DataModel/BatchGet.cs | 299 ++++++++---------- .../DynamoDBv2/Custom/DataModel/Context.cs | 13 +- .../Custom/DataModel/IDynamoDBContext.cs | 7 +- .../Custom/DataModel/_async/BatchGet.Async.cs | 39 +-- .../Custom/DataModel/_async/Context.Async.cs | 28 +- .../_async/IDynamoDBContext.Async.cs | 13 +- .../Custom/DataModel/_bcl/BatchGet.Sync.cs | 39 +-- .../Custom/DataModel/_bcl/Context.Sync.cs | 13 +- .../DataModel/_bcl/IDynamoDBContext.Sync.cs | 4 +- .../Custom/MockabilityTests/BatchGetTests.cs | 167 ++++++++++ 10 files changed, 361 insertions(+), 261 deletions(-) create mode 100644 sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/BatchGetTests.cs diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs index 29379dd4a929..be5ae971a655 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs @@ -14,11 +14,9 @@ */ using System; -using System.Collections; using System.Collections.Generic; -using System.Reflection; -using Amazon.DynamoDBv2.Model; using Amazon.DynamoDBv2.DocumentModel; + #if AWS_ASYNC_API using System.Threading.Tasks; #endif @@ -27,163 +25,149 @@ namespace Amazon.DynamoDBv2.DataModel { /// - /// Represents a non-generic object for retrieving a batch of items + /// Represents a non-generic interface for retrieving a batch of items /// from a single DynamoDB table /// - public abstract partial class BatchGet + public partial interface IBatchGet { - #region Internal/protected properties - /// - /// Gets and sets the UntypedResults property. + /// Returns the total number of keys associated with this Batch request. /// - protected List UntypedResults { get; set; } - internal DynamoDBContext Context { get; set; } - internal DynamoDBFlatConfig Config { get; set; } - internal List Keys { get; set; } - internal DocumentBatchGet DocumentBatch { get; set; } - internal ItemStorageConfig ItemStorageConfig { get; set; } - - #endregion - - - #region Constructor - - internal BatchGet(DynamoDBContext context, DynamoDBFlatConfig config) - { - Context = context; - Config = config; - Keys = new List(); - } - - #endregion - - - #region Public properties + public int TotalKeys { get; } /// - /// List of results retrieved from DynamoDB. - /// Populated after Execute is called. + /// List of non-generic results retrieved from DynamoDB. /// - public List Results { get { return UntypedResults; } } + /// + /// This is only populated after a call to Execute. + /// + List UntypedResults { get; } /// /// If set to true, a consistent read is issued. Otherwise eventually-consistent is used. /// - public bool ConsistentRead { get; set; } - - #endregion + /// + /// Refer to the + /// Read Consistency topic in the DynamoDB Developer Guide for more information. + /// + bool ConsistentRead { get; set; } + } + /// + /// Represents a generic interface for retrieving a batch of items + /// from a single DynamoDB table + /// + public interface IBatchGet : IBatchGet + { + /// + /// List of generic results retrieved from DynamoDB. + /// + /// + /// This is only populated after a call to Execute. + /// + List Results { get; } - #region Protected methods + /// + /// Add a single item to get, identified by its hash primary key. + /// + /// Hash key of the item to get + void AddKey(object hashKey); /// - /// Executes a server call to batch-get the items requested. - /// Populates Results with the retrieved items. + /// Add a single item to get, identified by its hash-and-range primary key. /// - internal protected abstract void ExecuteHelper(); + /// Hash key of the item to get + /// Range key of the item to get + void AddKey(object hashKey, object rangeKey); -#if AWS_ASYNC_API /// - /// Executes an asynchronous server call to batch-get the items requested. - /// Populates Results with the retrieved items. + /// Add a single item to get. /// - internal protected abstract Task ExecuteHelperAsync(CancellationToken cancellationToken); -#endif - #endregion + /// Object key of the item to get + void AddKey(T keyObject); + /// + /// Creates a MultiTableBatchGet object that is a combination + /// of the current BatchGet and the specified BatchGets + /// + /// Other BatchGet objects + /// + /// An interface with the ability to perform MultiTableBatchGet operations. + /// + IMultiTableBatchGet Combine(params IBatchGet[] otherBatches); + } - #region Internal methods + /// + /// Represents a non-generic object for retrieving a batch of items + /// from a single DynamoDB table + /// + public abstract class BatchGet + { + internal DocumentBatchGet DocumentBatch { get; set; } internal abstract void CreateDocumentBatch(); - internal abstract void PopulateResults(List items); - #endregion + internal abstract void PopulateResults(List items); } /// /// Represents a strongly-typed object for retrieving a batch of items /// from a single DynamoDB table /// - public class BatchGet : BatchGet + public partial class BatchGet : BatchGet, IBatchGet { - #region Public properties + private readonly DynamoDBContext _context; + private readonly DynamoDBFlatConfig _config; + private readonly ItemStorageConfig _itemStorageConfig; + private readonly List _keys; - /// - /// List of results retrieved from DynamoDB. - /// Populated after Execute is called. - /// - new public List Results { get { return TypedResults; } } + /// + public int TotalKeys => _keys.Count; - #endregion + /// + public List UntypedResults { get; private set; } = new List(); + /// + public List Results { get; private set; } = new List(); - #region Public methods + /// + public bool ConsistentRead { get; set; } - /// - /// Add a single item to get, identified by its hash primary key. - /// - /// Hash key of the item to get + /// public void AddKey(object hashKey) { AddKey(hashKey, null); } - /// - /// Add a single item to get, identified by its hash-and-range primary key. - /// - /// Hash key of the item to get - /// Range key of the item to get + /// public void AddKey(object hashKey, object rangeKey) { - Key key = Context.MakeKey(hashKey, rangeKey, ItemStorageConfig, Config); - Keys.Add(key); + Key key = _context.MakeKey(hashKey, rangeKey, _itemStorageConfig, _config); + _keys.Add(key); } - /// - /// Add a single item to get. - /// - /// Object key of the item to get + /// public void AddKey(T keyObject) { - Key key = Context.MakeKey(keyObject, ItemStorageConfig, Config); - Keys.Add(key); + Key key = _context.MakeKey(keyObject, _itemStorageConfig, _config); + _keys.Add(key); } - /// - /// Creates a MultiTableBatchGet object that is a combination - /// of the current BatchGet and the specified BatchGets - /// - /// Other BatchGet objects - /// - /// MultiTableBatchGet consisting of the multiple BatchGet objects: - /// the current batch and the passed-in batches. - /// - public MultiTableBatchGet Combine(params BatchGet[] otherBatches) + /// + public IMultiTableBatchGet Combine(params IBatchGet[] otherBatches) { return new MultiTableBatchGet(this, otherBatches); } - #endregion - - - #region Constructor - internal BatchGet(DynamoDBContext context, DynamoDBFlatConfig config) - : base(context, config) { - ItemStorageConfig = context.StorageConfigCache.GetConfig(config); + _context = context; + _config = config; + _itemStorageConfig = context.StorageConfigCache.GetConfig(config); + _keys = new List(); } - #endregion - - - #region Internal/protected/private members - - /// - /// Executes the batch get - /// - internal protected override void ExecuteHelper() + private void ExecuteHelper() { CreateDocumentBatch(); DocumentBatch.ExecuteHelper(); @@ -191,10 +175,7 @@ internal protected override void ExecuteHelper() } #if AWS_ASYNC_API - /// - /// Executes the batch get asynchronously - /// - internal protected override async Task ExecuteHelperAsync(CancellationToken cancellationToken) + private async Task ExecuteHelperAsync(CancellationToken cancellationToken) { CreateDocumentBatch(); await DocumentBatch.ExecuteHelperAsync(cancellationToken).ConfigureAwait(false); @@ -202,80 +183,78 @@ internal protected override async Task ExecuteHelperAsync(CancellationToken canc } #endif - /// - /// Gets and sets the TypedResults property. - /// - protected List TypedResults { get; set; } internal override void CreateDocumentBatch() { - var storageConfig = Context.StorageConfigCache.GetConfig(Config); - var table = Context.GetTargetTable(storageConfig, Config); + var table = _context.GetTargetTable(_itemStorageConfig, _config); DocumentBatchGet docBatch = new DocumentBatchGet(table) { - AttributesToGet = storageConfig.AttributesToGet, - ConsistentRead = this.ConsistentRead + AttributesToGet = _itemStorageConfig.AttributesToGet, + ConsistentRead = ConsistentRead }; - docBatch.Keys.AddRange(Keys); + docBatch.Keys.AddRange(_keys); DocumentBatch = docBatch; } + internal override void PopulateResults(List items) { UntypedResults = new List(); - TypedResults = new List(); + Results = new List(); foreach (var doc in items) { - var item = Context.FromDocumentHelper(doc, Config); - TypedResults.Add(item); + var item = _context.FromDocumentHelper(doc, _config); + Results.Add(item); UntypedResults.Add(item); } } - - #endregion } /// - /// Class for retrieving a batch of items from multiple DynamoDB tables, + /// Interface for retrieving a batch of items from multiple DynamoDB tables, /// using multiple strongly-typed BatchGet objects /// - public partial class MultiTableBatchGet + public partial interface IMultiTableBatchGet { - #region Private members - - private List allBatches = new List(); - - #endregion + /// + /// Gets the total number of primary keys to be loaded from DynamoDB, + /// across all batches + /// + int TotalKeys { get; } + /// + /// Add a BatchGet object to the multi-table batch request + /// + /// BatchGet to add + void AddBatch(IBatchGet batch); + } - #region Constructor + /// + /// Class for retrieving a batch of items from multiple DynamoDB tables, + /// using multiple strongly-typed BatchGet objects + /// + public partial class MultiTableBatchGet : IMultiTableBatchGet + { + private List allBatches = new List(); /// /// Constructs a MultiTableBatchGet object from a number of /// BatchGet objects /// /// Collection of BatchGet objects - public MultiTableBatchGet(params BatchGet[] batches) + public MultiTableBatchGet(params IBatchGet[] batches) { - allBatches = new List(batches); + allBatches = new List(batches); } - internal MultiTableBatchGet(BatchGet first, params BatchGet[] rest) + internal MultiTableBatchGet(IBatchGet first, params IBatchGet[] rest) { - allBatches = new List(); + allBatches = new List(); allBatches.Add(first); allBatches.AddRange(rest); } - #endregion - - - #region Public properties - - /// - /// Gets the total number of primary keys to be loaded from DynamoDB, - /// across all batches - /// + /// public int TotalKeys { get @@ -283,62 +262,58 @@ public int TotalKeys int count = 0; foreach (var batch in allBatches) { - count += batch.Keys.Count; + count += batch.TotalKeys; } return count; } } - #endregion - - - #region Public methods - - /// - /// Add a BatchGet object to the multi-table batch request - /// - /// BatchGet to add - public void AddBatch(BatchGet batch) + /// + public void AddBatch(IBatchGet batch) { allBatches.Add(batch); } - internal void ExecuteHelper() + private void ExecuteHelper() { MultiTableDocumentBatchGet superBatch = new MultiTableDocumentBatchGet(); + var errorMsg = $"All batches must be of type {nameof(BatchGet)}"; foreach (var batch in allBatches) { - batch.CreateDocumentBatch(); - superBatch.AddBatch(batch.DocumentBatch); + var abstractBatch = batch as BatchGet ?? throw new InvalidOperationException(errorMsg); + abstractBatch.CreateDocumentBatch(); + superBatch.AddBatch(abstractBatch.DocumentBatch); } superBatch.ExecuteHelper(); foreach (var batch in allBatches) { - batch.PopulateResults(batch.DocumentBatch.Results); + var abstractBatch = batch as BatchGet ?? throw new InvalidOperationException(errorMsg); + abstractBatch.PopulateResults(abstractBatch.DocumentBatch.Results); } } #if AWS_ASYNC_API - internal async Task ExecuteHelperAsync(CancellationToken cancellationToken) + private async Task ExecuteHelperAsync(CancellationToken cancellationToken) { MultiTableDocumentBatchGet superBatch = new MultiTableDocumentBatchGet(); + var errorMsg = $"All batches must be of type {nameof(BatchGet)}"; foreach (var batch in allBatches) { - batch.CreateDocumentBatch(); - superBatch.AddBatch(batch.DocumentBatch); + var abstractBatch = batch as BatchGet ?? throw new InvalidOperationException(errorMsg); + abstractBatch.CreateDocumentBatch(); + superBatch.AddBatch(abstractBatch.DocumentBatch); } await superBatch.ExecuteHelperAsync(cancellationToken).ConfigureAwait(false); foreach (var batch in allBatches) { - batch.PopulateResults(batch.DocumentBatch.Results); + var abstractBatch = batch as BatchGet ?? throw new InvalidOperationException(errorMsg); + abstractBatch.PopulateResults(abstractBatch.DocumentBatch.Results); } } #endif - - #endregion } } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs index 329ad6834b2f..10e3cf10ce84 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs @@ -209,13 +209,8 @@ public void Dispose() #region BatchGet - /// - /// Creates a strongly-typed BatchGet object, allowing - /// a batch-get operation against DynamoDB. - /// - /// Type of objects to get - /// Empty strongly-typed BatchGet object - public BatchGet CreateBatchGet() + /// + public IBatchGet CreateBatchGet() { return CreateBatchGet((BatchGetConfig)null); } @@ -229,6 +224,8 @@ public BatchGet CreateBatchGet() /// Empty strongly-typed BatchGet object [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] public BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig) + /// + public IBatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new BatchGet(this, config); @@ -253,6 +250,8 @@ public BatchGet CreateBatchGet(BatchGetConfig batchGetConfig) /// Individual BatchGet objects /// Composite MultiTableBatchGet object public MultiTableBatchGet CreateMultiTableBatchGet(params BatchGet[] batches) + /// + public IMultiTableBatchGet CreateMultiTableBatchGet(params IBatchGet[] batches) { return new MultiTableBatchGet(batches); } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs index 19081d794dd5..dd6630ce041c 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using Amazon.DynamoDBv2.DocumentModel; -using Amazon.DynamoDBv2.Model; namespace Amazon.DynamoDBv2.DataModel { @@ -165,6 +164,8 @@ public partial interface IDynamoDBContext : IDisposable /// Empty strongly-typed BatchGet object [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig = null); + /// An interface with the ability to perform BatchGet operations + IBatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig = null); /// /// Creates a strongly-typed BatchGet object, allowing @@ -180,8 +181,8 @@ public partial interface IDynamoDBContext : IDisposable /// individual BatchGet objects. /// /// Individual BatchGet objects - /// Composite MultiTableBatchGet object - MultiTableBatchGet CreateMultiTableBatchGet(params BatchGet[] batches); + /// An interface with the ability to perform MultiTableBatchGet operations + IMultiTableBatchGet CreateMultiTableBatchGet(params IBatchGet[] batches); #endregion diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs index b40710792c17..dfae6e476d1d 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs @@ -14,48 +14,33 @@ */ #pragma warning disable 1574 -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using Amazon.DynamoDBv2.Model; -using Amazon.DynamoDBv2.DocumentModel; using System.Threading; using System.Threading.Tasks; -using Amazon.Runtime.Internal; namespace Amazon.DynamoDBv2.DataModel { - /// - /// Represents a non-generic object for retrieving a batch of items - /// from a single DynamoDB table - /// - public abstract partial class BatchGet + public partial interface IBatchGet { - #region Public methods - /// /// Executes a server call to batch-get the items requested. /// /// Token which can be used to cancel the task. /// /// A Task that can be used to poll or wait for results, or both. + Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); + } + + public partial class BatchGet : IBatchGet + { + /// public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { return ExecuteHelperAsync(cancellationToken); } - - #endregion } - /// - /// Class for retrieving a batch of items from multiple DynamoDB tables, - /// using multiple strongly-typed BatchGet objects - /// - public partial class MultiTableBatchGet + public partial interface IMultiTableBatchGet { - #region Public methods - /// /// Executes a multi-table batch request against all configured batches. /// Results are stored in the respective BatchGet objects. @@ -63,11 +48,15 @@ public partial class MultiTableBatchGet /// Token which can be used to cancel the task. /// /// A Task that can be used to poll or wait for results, or both. + Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); + } + + public partial class MultiTableBatchGet : IMultiTableBatchGet + { + /// public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { return ExecuteHelperAsync(cancellationToken); } - - #endregion } } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs index 5a320690d12c..8ca209c1a640 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/Context.Async.cs @@ -16,12 +16,9 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Amazon.DynamoDBv2.DocumentModel; -using Amazon.DynamoDBv2.Model; -using Amazon.Runtime.Internal; namespace Amazon.DynamoDBv2.DataModel { @@ -197,31 +194,14 @@ public Task DeleteAsync(object hashKey, object rangeKey, DeleteConfig deleteC #region BatchGet async - /// - /// Issues a batch-get request with multiple batches. - /// - /// Results are stored in the individual batches. - /// - /// - /// Configured BatchGet objects - /// - public Task ExecuteBatchGetAsync(params BatchGet[] batches) + /// + public Task ExecuteBatchGetAsync(params IBatchGet[] batches) { return ExecuteBatchGetAsync(batches, default(CancellationToken)); } - /// - /// Issues a batch-get request with multiple batches. - /// - /// Results are stored in the individual batches. - /// - /// - /// Configured BatchGet objects - /// - /// - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - public Task ExecuteBatchGetAsync(BatchGet[] batches, CancellationToken cancellationToken = default(CancellationToken)) + /// + public Task ExecuteBatchGetAsync(IBatchGet[] batches, CancellationToken cancellationToken = default(CancellationToken)) { MultiTableBatchGet superBatch = new MultiTableBatchGet(batches); return superBatch.ExecuteAsync(cancellationToken); diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs index 5b76a7754b96..5b49fdbd525f 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/IDynamoDBContext.Async.cs @@ -387,6 +387,17 @@ partial interface IDynamoDBContext #region BatchGet async + /// + /// Issues a batch-get request with multiple batches. + /// + /// Results are stored in the individual batches. + /// + /// + /// Configured BatchGet objects + /// + /// A Task that can be used to poll or wait for results, or both. + Task ExecuteBatchGetAsync(params IBatchGet[] batches); + /// /// Issues a batch-get request with multiple batches. /// @@ -397,7 +408,7 @@ partial interface IDynamoDBContext /// /// Token which can be used to cancel the task. /// A Task that can be used to poll or wait for results, or both. - Task ExecuteBatchGetAsync(BatchGet[] batches, CancellationToken cancellationToken = default(CancellationToken)); + Task ExecuteBatchGetAsync(IBatchGet[] batches, CancellationToken cancellationToken = default(CancellationToken)); #endregion diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs index 44b0291cebdc..abfa2f7ac9ee 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs @@ -13,51 +13,40 @@ * permissions and limitations under the License. */ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using Amazon.DynamoDBv2.Model; -using Amazon.DynamoDBv2.DocumentModel; - namespace Amazon.DynamoDBv2.DataModel { - /// - /// Represents a non-generic object for retrieving a batch of items - /// from a single DynamoDB table - /// - public abstract partial class BatchGet + public partial interface IBatchGet { - #region Public methods - /// /// Executes a server call to batch-get the items requested. /// + void Execute(); + } + + public partial class BatchGet : IBatchGet + { + /// public void Execute() { ExecuteHelper(); } - - #endregion } - /// - /// Class for retrieving a batch of items from multiple DynamoDB tables, - /// using multiple strongly-typed BatchGet objects - /// - public partial class MultiTableBatchGet + public partial interface IMultiTableBatchGet { - #region Public methods - /// /// Executes a multi-table batch request against all configured batches. /// Results are stored in the respective BatchGet objects. /// + void Execute(); + } + + public partial class MultiTableBatchGet + { + /// /> public void Execute() { ExecuteHelper(); } - - #endregion } } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs index 98a57588013a..a53c03bbb1fa 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/Context.Sync.cs @@ -15,10 +15,8 @@ using System; using System.Collections.Generic; -using System.Linq; using Amazon.DynamoDBv2.DocumentModel; -using Amazon.DynamoDBv2.Model; namespace Amazon.DynamoDBv2.DataModel { @@ -171,15 +169,8 @@ public void Delete(object hashKey, object rangeKey, DeleteConfig deleteConfig #endregion #region BatchGet - /// - /// Issues a batch-get request with multiple batches. - /// - /// Results are stored in the individual batches. - /// - /// - /// Configured BatchGet objects - /// - public void ExecuteBatchGet(params BatchGet[] batches) + /// + public void ExecuteBatchGet(params IBatchGet[] batches) { MultiTableBatchGet superBatch = new MultiTableBatchGet(batches); superBatch.Execute(); diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs index 9e484d83e019..3ddc517527b8 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/IDynamoDBContext.Sync.cs @@ -13,11 +13,9 @@ * permissions and limitations under the License. */ -using System; using System.Collections.Generic; using Amazon.DynamoDBv2.DocumentModel; -using Amazon.DynamoDBv2.Model; namespace Amazon.DynamoDBv2.DataModel { @@ -341,7 +339,7 @@ partial interface IDynamoDBContext /// /// Configured BatchGet objects /// - void ExecuteBatchGet(params BatchGet[] batches); + void ExecuteBatchGet(params IBatchGet[] batches); #endregion diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/BatchGetTests.cs b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/BatchGetTests.cs new file mode 100644 index 000000000000..7d7ce4df13f1 --- /dev/null +++ b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/MockabilityTests/BatchGetTests.cs @@ -0,0 +1,167 @@ +using Amazon.DynamoDBv2.DataModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Collections.Generic; + +namespace AWSSDK.UnitTests.DynamoDBv2.NetFramework.Custom.MockabilityTests +{ + [TestClass] + public class BatchGetTests + { + [TestMethod] + public void TestMockability_BatchGet() + { + var mockContext = new Mock(); + mockContext + .Setup(x => x.CreateBatchGet
(It.IsAny())) + .Returns(CreateBatchGetMock(new List
() + { + { new Address() { State = "CA", Zip = "12345" }} + })); + + var ddbContext = mockContext.Object; + var addressBatchGet = ddbContext.CreateBatchGet
(); + + Assert.AreEqual(0, addressBatchGet.TotalKeys); + Assert.AreEqual(0, addressBatchGet.Results.Count); + + addressBatchGet.AddKey("CA"); + Assert.AreEqual(1, addressBatchGet.TotalKeys); + + addressBatchGet.Execute(); + Assert.AreEqual(1, addressBatchGet.Results.Count); + Assert.AreEqual("CA", addressBatchGet.Results[0].State); + Assert.AreEqual("12345", addressBatchGet.Results[0].Zip); + } + + [TestMethod] + public void TestMockability_MultiTableBatchGet() + { + var mockContext = new Mock(); + mockContext + .Setup(x => x.CreateMultiTableBatchGet()) + .Returns(CreateMultiTableBatchGetMock()); + + var ddbContext = mockContext.Object; + var multiBatchGet = ddbContext.CreateMultiTableBatchGet(); + + var addressBatchGet = CreateBatchGetMock
(new List
+ { + { new Address() { State = "CA", Zip = "12345" }} + }); + + var personBatchGet = CreateBatchGetMock(new List + { + { new Person() { FirstName = "John", LastName = "Doe" }} + }); + + multiBatchGet.AddBatch(addressBatchGet); + multiBatchGet.AddBatch(personBatchGet); + + Assert.AreEqual(0, multiBatchGet.TotalKeys); + Assert.AreEqual(0, addressBatchGet.Results.Count); + Assert.AreEqual(0, personBatchGet.Results.Count); + + addressBatchGet.AddKey("CA"); + personBatchGet.AddKey("John"); + Assert.AreEqual(2, multiBatchGet.TotalKeys); + + multiBatchGet.Execute(); + Assert.AreEqual(1, addressBatchGet.Results.Count); + Assert.AreEqual("CA", addressBatchGet.Results[0].State); + Assert.AreEqual("12345", addressBatchGet.Results[0].Zip); + Assert.AreEqual(1, personBatchGet.Results.Count); + Assert.AreEqual("John", personBatchGet.Results[0].FirstName); + Assert.AreEqual("Doe", personBatchGet.Results[0].LastName); + } + + public IBatchGet CreateBatchGetMock(List results) + { + var batchGet = new Mock>(); + var dummyResults = new List(); + var keys = new List(); + + batchGet + .Setup(x => x.Execute()) + .Callback(() => + { + dummyResults.Clear(); + dummyResults.AddRange(results); + }); + + batchGet + .Setup(x => x.Results) + .Returns(dummyResults); + + batchGet + .Setup(x => x.AddKey(It.IsAny())) + .Callback((object hashKey) => + { + keys.Add(hashKey.ToString()); + }); + + batchGet + .Setup(x => x.AddKey(It.IsAny(), It.IsAny())) + .Callback((object hashKey, object rangeKey) => + { + keys.Add(hashKey.ToString() + rangeKey.ToString()); + }); + + batchGet + .SetupGet(x => x.TotalKeys) + .Returns(() => keys.Count); + + return batchGet.Object; + } + + public IMultiTableBatchGet CreateMultiTableBatchGetMock() + { + var multiBatchGet = new Mock(); + var batches = new List(); + + multiBatchGet + .Setup(x => x.Execute()) + .Callback(() => + { + foreach (var batch in batches) + { + batch.Execute(); + } + }); + + multiBatchGet + .Setup(x => x.AddBatch(It.IsAny())) + .Callback((IBatchGet batch) => + { + batches.Add(batch); + }); + + multiBatchGet. + SetupGet(x => x.TotalKeys) + .Returns(() => + { + var totalKeys = 0; + foreach (var batch in batches) + { + totalKeys += batch.TotalKeys; + } + return totalKeys; + }); + + + return multiBatchGet.Object; + } + } + + public class Address + { + public string State { get; set; } + public string Zip { get; set; } + } + + public class Person + { + public string FirstName { get; set; } + public string LastName { get; set; } + } +} From 72b5113e06befa1adc1e5ef5db65b2f31287eb14 Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria <96malhar@gmail.com> Date: Fri, 2 Aug 2024 13:50:34 -0700 Subject: [PATCH 2/3] Amend the abstract BatchGet class to implement the non-generic IBatchGet interface --- .../DynamoDBv2/Custom/DataModel/BatchGet.cs | 28 ++++++++++--------- .../Custom/DataModel/IDynamoDBContext.cs | 3 +- .../Custom/DataModel/_async/BatchGet.Async.cs | 10 +++++-- .../Custom/DataModel/_bcl/BatchGet.Sync.cs | 10 +++++-- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs index be5ae971a655..7dae11417f1c 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/BatchGet.cs @@ -101,13 +101,22 @@ public interface IBatchGet : IBatchGet /// Represents a non-generic object for retrieving a batch of items /// from a single DynamoDB table /// - public abstract class BatchGet + public abstract partial class BatchGet : IBatchGet { internal DocumentBatchGet DocumentBatch { get; set; } internal abstract void CreateDocumentBatch(); internal abstract void PopulateResults(List items); + + /// + public abstract int TotalKeys { get; } + + /// + public List UntypedResults { get; } = new(); + + /// + public bool ConsistentRead { get; set; } } /// @@ -119,19 +128,13 @@ public partial class BatchGet : BatchGet, IBatchGet private readonly DynamoDBContext _context; private readonly DynamoDBFlatConfig _config; private readonly ItemStorageConfig _itemStorageConfig; - private readonly List _keys; - - /// - public int TotalKeys => _keys.Count; + private readonly List _keys = new(); /// - public List UntypedResults { get; private set; } = new List(); + public override int TotalKeys => _keys.Count; /// - public List Results { get; private set; } = new List(); - - /// - public bool ConsistentRead { get; set; } + public List Results { get; } = new(); /// public void AddKey(object hashKey) @@ -164,7 +167,6 @@ internal BatchGet(DynamoDBContext context, DynamoDBFlatConfig config) _context = context; _config = config; _itemStorageConfig = context.StorageConfigCache.GetConfig(config); - _keys = new List(); } private void ExecuteHelper() @@ -199,8 +201,8 @@ internal override void CreateDocumentBatch() internal override void PopulateResults(List items) { - UntypedResults = new List(); - Results = new List(); + UntypedResults.Clear(); + Results.Clear(); foreach (var doc in items) { var item = _context.FromDocumentHelper(doc, _config); diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs index dd6630ce041c..131c91580a7d 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs @@ -165,6 +165,7 @@ public partial interface IDynamoDBContext : IDisposable [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig = null); /// An interface with the ability to perform BatchGet operations + /// A BatchGet object using this context's configuration, which can be used to prepare and execute a BatchGet request IBatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig = null); /// @@ -181,7 +182,7 @@ public partial interface IDynamoDBContext : IDisposable /// individual BatchGet objects. /// /// Individual BatchGet objects - /// An interface with the ability to perform MultiTableBatchGet operations + /// A MultiTableBatchGet object using this context's configuration, which can be used to prepare and execute a MultiTableBatchGet request IMultiTableBatchGet CreateMultiTableBatchGet(params IBatchGet[] batches); #endregion diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs index dfae6e476d1d..561e70161b56 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_async/BatchGet.Async.cs @@ -30,10 +30,16 @@ public partial interface IBatchGet Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); } - public partial class BatchGet : IBatchGet + public abstract partial class BatchGet { /// - public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) + public abstract Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)); + } + + public partial class BatchGet : BatchGet, IBatchGet + { + /// + public override Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { return ExecuteHelperAsync(cancellationToken); } diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs index abfa2f7ac9ee..2585d47c63c9 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs @@ -23,10 +23,16 @@ public partial interface IBatchGet void Execute(); } - public partial class BatchGet : IBatchGet + public abstract partial class BatchGet { /// - public void Execute() + public abstract void Execute(); + } + + public partial class BatchGet : BatchGet, IBatchGet + { + /// + public override void Execute() { ExecuteHelper(); } From 17a433eb7876667f38649459703839444e4a25fd Mon Sep 17 00:00:00 2001 From: Malhar Khimsaria <96malhar@gmail.com> Date: Mon, 5 Aug 2024 14:58:40 -0700 Subject: [PATCH 3/3] Rebase and amend methods that accept the separate BatchGetConfig --- .../DynamoDBv2/Custom/DataModel/Context.cs | 27 +++---------------- .../Custom/DataModel/IDynamoDBContext.cs | 11 +++----- .../Custom/DataModel/_bcl/BatchGet.Sync.cs | 2 +- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs index 10e3cf10ce84..b16b61b105d3 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs @@ -215,41 +215,20 @@ public IBatchGet CreateBatchGet() return CreateBatchGet((BatchGetConfig)null); } - /// - /// Creates a strongly-typed BatchGet object, allowing - /// a batch-get operation against DynamoDB. - /// - /// Type of objects to get - /// Config object which can be used to override that table used. - /// Empty strongly-typed BatchGet object - [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] - public BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig) /// + [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] public IBatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig) { DynamoDBFlatConfig config = new DynamoDBFlatConfig(operationConfig, this.Config); return new BatchGet(this, config); } - /// - /// Creates a strongly-typed BatchGet object, allowing - /// a batch-get operation against DynamoDB. - /// - /// Type of objects to get - /// Config object that can be used to override properties on the table's context for this request - /// Empty strongly-typed BatchGet object - public BatchGet CreateBatchGet(BatchGetConfig batchGetConfig) + /// + public IBatchGet CreateBatchGet(BatchGetConfig batchGetConfig) { return new BatchGet(this, new DynamoDBFlatConfig(batchGetConfig?.ToDynamoDBOperationConfig(), Config)); } - /// - /// Creates a MultiTableBatchGet object, composed of multiple - /// individual BatchGet objects. - /// - /// Individual BatchGet objects - /// Composite MultiTableBatchGet object - public MultiTableBatchGet CreateMultiTableBatchGet(params BatchGet[] batches) /// public IMultiTableBatchGet CreateMultiTableBatchGet(params IBatchGet[] batches) { diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs index 131c91580a7d..cc8120571cd7 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/IDynamoDBContext.cs @@ -153,7 +153,7 @@ public partial interface IDynamoDBContext : IDisposable /// /// Type of objects to get /// Empty strongly-typed BatchGet object - BatchGet CreateBatchGet(); + IBatchGet CreateBatchGet(); /// /// Creates a strongly-typed BatchGet object, allowing @@ -161,11 +161,8 @@ public partial interface IDynamoDBContext : IDisposable /// /// Type of objects to get /// Config object which can be used to override that table used. - /// Empty strongly-typed BatchGet object - [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] - BatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig = null); - /// An interface with the ability to perform BatchGet operations /// A BatchGet object using this context's configuration, which can be used to prepare and execute a BatchGet request + [Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")] IBatchGet CreateBatchGet(DynamoDBOperationConfig operationConfig = null); /// @@ -174,8 +171,8 @@ public partial interface IDynamoDBContext : IDisposable /// /// Type of objects to get /// Config object that can be used to override properties on the table's context for this request - /// Empty strongly-typed BatchGet object - public BatchGet CreateBatchGet(BatchGetConfig batchGetConfig); + /// A BatchGet object based on the provided , which can be used to prepare and execute a BatchGet request + IBatchGet CreateBatchGet(BatchGetConfig batchGetConfig); /// /// Creates a MultiTableBatchGet object, composed of multiple diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs index 2585d47c63c9..121e45c69fbf 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/_bcl/BatchGet.Sync.cs @@ -47,7 +47,7 @@ public partial interface IMultiTableBatchGet void Execute(); } - public partial class MultiTableBatchGet + public partial class MultiTableBatchGet : IMultiTableBatchGet { /// /> public void Execute()