PageRenderTime 76ms CodeModel.GetById 41ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Paramore.Brighter.DynamoDb/DynamoDbTableBuilder.cs

https://github.com/iancooper/Paramore
C# | 208 lines | 148 code | 32 blank | 28 comment | 17 complexity | 96ff850dccdf842bfc068ebd90b29229 MD5 | raw file
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using Amazon.DynamoDBv2;
  6. using Amazon.DynamoDBv2.Model;
  7. namespace Paramore.Brighter.DynamoDb
  8. {
  9. public class DynamoDbTableBuilder
  10. {
  11. private readonly IAmazonDynamoDB _client;
  12. public DynamoDbTableBuilder(IAmazonDynamoDB client)
  13. {
  14. _client = client;
  15. }
  16. /// <summary>
  17. /// Build a table from a create table request
  18. /// We filter out any attributes that are not B, S, or N because DynamoDB does not currently support
  19. /// creation of these types via the API. As these are not used for hash or range keys they are not
  20. /// required to store items in DynamoDB
  21. /// </summary>
  22. /// <param name="createTableRequest">The request to build tables from</param>
  23. /// <param name="ct"></param>
  24. /// <returns>The response to table creation</returns>
  25. public async Task<CreateTableResponse> Build(CreateTableRequest createTableRequest, CancellationToken ct = default(CancellationToken))
  26. {
  27. var modifiedTableRequest = RemoveNonSchemaAttributes(createTableRequest);
  28. return await _client.CreateTableAsync(modifiedTableRequest, ct);
  29. }
  30. /// <summary>
  31. /// Delete the specified tables. Note that tables will be in TableStatus.Deleting until gone
  32. /// </summary>
  33. /// <param name="tableNames">The list of tables to delette</param>
  34. /// <param name="ct">A cancellation token</param>
  35. /// <returns>The response to table deletion</returns>
  36. public async Task<DeleteTableResponse[]> Delete(IEnumerable<string> tableNames, CancellationToken ct = default(CancellationToken))
  37. {
  38. var allDeletes = tableNames.Select(tn => _client.DeleteTableAsync(tn, ct)).ToList();
  39. return await Task.WhenAll(allDeletes);
  40. }
  41. //EnsureTablesGone. Deleting until cannot be found
  42. public async Task EnsureTablesDeleted(IEnumerable<string> tableNames, CancellationToken ct = default(CancellationToken))
  43. {
  44. Dictionary<string, bool> tableResults = null;
  45. do
  46. {
  47. var tableQuery = new DynampDbTableQuery();
  48. tableResults = await tableQuery.HasTables(_client, tableNames, ct: ct);
  49. } while (tableResults.Any(tr => tr.Value));
  50. }
  51. /// <summary>
  52. /// Ensures, due to the asynchronous nature of DDL operations that a table is ready i.e. in desired state
  53. /// </summary>
  54. /// <param name="tableNames">The tables to check</param>
  55. /// <param name="targetStatus">The status that defines ready</param>
  56. /// <param name="ct">A cancellation token</param>
  57. /// <returns></returns>
  58. public async Task EnsureTablesReady(IEnumerable<string> tableNames, TableStatus targetStatus, CancellationToken ct = default(CancellationToken))
  59. {
  60. // Let us wait until all tables are created. Call DescribeTable.
  61. var tableStates = (from tableName in tableNames
  62. select new DynamoDbTableStatus{TableName = tableName, IsReady = false}).ToList();
  63. do
  64. {
  65. await Task.Delay(5000, ct);
  66. try
  67. {
  68. var allQueries = tableStates.Where(ts => ts.IsReady == false).Select(ts => _client.DescribeTableAsync(ts.TableName, ct));
  69. var allResults = await Task.WhenAll(allQueries);
  70. foreach (var result in allResults)
  71. {
  72. if (result.Table.TableStatus == targetStatus)
  73. {
  74. var tableStatus = tableStates.First(ts => ts.TableName == result.Table.TableName);
  75. tableStatus.IsReady = true;
  76. }
  77. }
  78. }
  79. catch (ResourceNotFoundException)
  80. {
  81. // DescribeTable is eventually consistent. So you might
  82. // get resource not found. So we handle the potential exception.
  83. }
  84. } while (tableStates.Any(ts => !ts.IsReady));
  85. }
  86. public async Task<(bool exist, IEnumerable<string> missing)> HasTables(IEnumerable<string> tableNames, CancellationToken ct = default(CancellationToken))
  87. {
  88. var tableCheck = tableNames.ToDictionary(tableName => tableName, tableName => false);
  89. string lastEvalutatedTableName = null;
  90. do
  91. {
  92. var tablesResponse = await _client.ListTablesAsync();
  93. foreach (var tableName in tablesResponse.TableNames)
  94. {
  95. if (tableCheck.ContainsKey(tableName))
  96. tableCheck[tableName] = true;
  97. }
  98. lastEvalutatedTableName = tablesResponse.LastEvaluatedTableName;
  99. } while (lastEvalutatedTableName != null);
  100. return tableCheck.Any(kv => kv.Value) ?
  101. (true, tableCheck.Where(tbl => tbl.Value).Select(tbl => tbl.Key)) :
  102. (false, Enumerable.Empty<string>());
  103. }
  104. public CreateTableRequest RemoveNonSchemaAttributes(CreateTableRequest tableRequest)
  105. {
  106. var keyMatchedAttributes = new List<AttributeDefinition>();
  107. //get the unfiltered markup
  108. var existingAttributes = tableRequest.AttributeDefinitions;
  109. foreach (var attribute in existingAttributes)
  110. {
  111. var added = AddKeyUsedFields(tableRequest, attribute, keyMatchedAttributes);
  112. if (!added)
  113. {
  114. added = AddGobalSecondaryIndexUsedFields(tableRequest, attribute, keyMatchedAttributes);
  115. }
  116. if (!added)
  117. {
  118. AddLocalSecondaryIndexUsedFields(tableRequest, attribute, keyMatchedAttributes);
  119. }
  120. }
  121. //swap for the filtered list
  122. tableRequest.AttributeDefinitions = keyMatchedAttributes;
  123. return tableRequest;
  124. }
  125. private static bool AddLocalSecondaryIndexUsedFields(CreateTableRequest tableRequest, AttributeDefinition attribute,
  126. List<AttributeDefinition> keyMatchedAttributes)
  127. {
  128. foreach (var index in tableRequest.LocalSecondaryIndexes)
  129. {
  130. foreach (var keySchemaElement in index.KeySchema)
  131. {
  132. if (keySchemaElement.AttributeName == attribute.AttributeName)
  133. {
  134. keyMatchedAttributes.Add(attribute);
  135. return true;
  136. }
  137. }
  138. }
  139. return false;
  140. }
  141. private static bool AddGobalSecondaryIndexUsedFields(CreateTableRequest tableRequest, AttributeDefinition attribute,
  142. List<AttributeDefinition> keyMatchedAttributes)
  143. {
  144. foreach (var index in tableRequest.GlobalSecondaryIndexes)
  145. {
  146. foreach (var keySchemaElement in index.KeySchema)
  147. {
  148. if (keySchemaElement.AttributeName == attribute.AttributeName)
  149. {
  150. keyMatchedAttributes.Add(attribute);
  151. return true;
  152. }
  153. }
  154. }
  155. return false;
  156. }
  157. private static bool AddKeyUsedFields(CreateTableRequest tableRequest, AttributeDefinition attribute,
  158. List<AttributeDefinition> keyMatchedAttributes)
  159. {
  160. foreach (var keySchemaElement in tableRequest.KeySchema)
  161. {
  162. if (keySchemaElement.AttributeName == attribute.AttributeName)
  163. {
  164. keyMatchedAttributes.Add(attribute);
  165. return true;
  166. }
  167. }
  168. return false;
  169. }
  170. private class DynamoDbTableStatus
  171. {
  172. public string TableName { get; set; }
  173. public bool IsReady { get; set; }
  174. }
  175. }
  176. }