using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Amazon.DynamoDBv2.Model; using Amazon.DynamoDBv2; using Amazon.Runtime.Internal; using Amazon.DynamoDBv2.DocumentModel; using NLog.Conditions; using ServerCore; using ServerBase; using DYNAMO_DB_TABLE_FULL_NAME = System.String; namespace ServerBase; public static class DynamoDbItemRequestHelper { public enum AttribValueAction { None = 0, Increase, // Atomic 기반 증가 Decrease, // Atomic 기반 감소 Update // 값의 변경 } public class AttribAction { public AttribValueAction ValueAction { get; set; } = AttribValueAction.None; public AttributeValue Value { get; set; } = new(); } public static (Result, DynamoDbItemRequestQueryContext?) makeUpdateItemRequestWithDoc( DynamoDbDocBase targetDoc , Dictionary toChangeAttibValues , DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler = null ) where TAttrib : AttribBase { ConditionValidCheckHelper.throwIfFalseWithCondition(() => 0 < toChangeAttibValues.Count, () => $"Invalid toChangeAttibValues.Count !!! : 0 < {toChangeAttibValues.Count}"); var server_logic = ServerLogicApp.getServerLogicApp(); NullReferenceCheckHelper.throwIfNull(server_logic, () => $"server_logic client is null !!! "); var db_connector = server_logic.getDynamoDbClient(); NullReferenceCheckHelper.throwIfNull(db_connector, () => $"db_connector client is null !!! "); var result = new Result(); var primary_key = targetDoc.getPrimaryKey(); var query_builder = new DynamoDbItemRequestHelper.UpdateItemRequestBuilder(db_connector.getTableFullName(targetDoc.TableName)); query_builder.withKeys(primary_key.toKeyWithAttributeValue()); var attrib_path_json_string = targetDoc.toJsonStringOfAttribs(); var update_expression = "SET "; var expression_idx = 0; var expression_attribute_names = new Dictionary(); var expression_attribute_values = new Dictionary(); var update_item_request = query_builder.getItemRequest(); foreach (var each in toChangeAttibValues) { var attrib_key = each.Key; var attrib_value = each.Value; var target_key = JsonHelper.getJsonPropertyName(attrib_key); (var is_success, var attribute_expression) = DynamoDbClientHelper.toAttributeExpressionFromJson(attrib_path_json_string, target_key); if (false == is_success) { var err_msg = $"Failed to DynamoDbClientHelper.toAttributeExpressionFromJson() !!! : attribPath:{attrib_path_json_string}, targetKey:{target_key} - {primary_key.toBasicString()}"; result.setFail(ServerErrorCode.AttribPathMakeFailed, err_msg); Log.getLogger().error(result.toBasicString()); continue; } var attribute_names = DynamoDbClientHelper.toExpressionAttributeNamesFromJson(attrib_path_json_string, target_key); foreach (var name in attribute_names) { expression_attribute_names.TryAdd(name.Key, name.Value); } if (expression_idx > 0) update_expression += ", "; var data = attrib_value.Value; if (AttribValueAction.Increase == attrib_value.ValueAction) { var start_key = $":start_{expression_idx}"; var incr_key = $":incr_{expression_idx}"; update_expression += $"{attribute_expression} = if_not_exists({attribute_expression}, {start_key}) + {incr_key}"; expression_attribute_values.Add(incr_key, data); expression_attribute_values.TryAdd(start_key, new AttributeValue { N = "0" }); } else if(AttribValueAction.Decrease== attrib_value.ValueAction) { var start_key = $":start_{expression_idx}"; var decr_key = $":decr_{expression_idx}"; update_expression += $"{attribute_expression} = if_not_exists({attribute_expression}, {start_key}) - {decr_key}"; expression_attribute_values.Add(decr_key, data); expression_attribute_values.TryAdd(start_key, new AttributeValue { N = "0" }); } else if (AttribValueAction.Update == attrib_value.ValueAction) { var new_value_key = $":newValue_{expression_idx}"; update_expression += $"{attribute_expression} = {new_value_key}"; expression_attribute_values.Add(new_value_key, data); } expression_idx++; } query_builder.withExpressionAttributeNames(expression_attribute_names); query_builder.withUpdateExpression(update_expression); query_builder.withExpressionAttributeValues(expression_attribute_values); query_builder.withReturnValues(ReturnValue.ALL_NEW); (result, var builded_update_item_request) = query_builder.build(); if(result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(builded_update_item_request, () => $"builded_update_item_request is null !!! - {primary_key.toBasicString()}"); var exception_handler = new DynamoDbQueryExceptionNotifier.ExceptionHandler(DynamoDbQueryExceptionNotifier.ConditionalCheckFailed, ServerErrorCode.LackOfTotalCalium); return (result, builded_update_item_request.createItemRequestQueryContext(QueryType.Update, exceptionHandler)); } //========================================================================================= // UpdateItemRequest Builder //========================================================================================= public class UpdateItemRequestBuilder { private readonly UpdateItemRequest m_request; private Document? m_document; private readonly bool m_is_upsert; public UpdateItemRequestBuilder(DYNAMO_DB_TABLE_FULL_NAME tableFullName, bool isUpsert = false) { ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == tableFullName.isNullOrWhiteSpace(), () => $"Invalid TableFullName !!!, Null or WhiteSpace !!!"); m_request = new UpdateItemRequest { TableName = tableFullName, Key = new Dictionary(), AttributeUpdates = new Dictionary(), UpdateExpression = string.Empty, }; m_is_upsert = isUpsert; } public UpdateItemRequest getItemRequest() => m_request; public UpdateItemRequestBuilder withDocument(Document document) { m_document = document; return this; } public UpdateItemRequestBuilder withReturnValues(ReturnValue returnValue) { m_request.ReturnValues = returnValue; return this; } public UpdateItemRequestBuilder withConditionExpression(string conditionExpression) { m_request.ConditionExpression = conditionExpression; return this; } public UpdateItemRequestBuilder withExpressionAttributeNames(Dictionary expressionAttributeNames) { m_request.ExpressionAttributeNames = expressionAttributeNames; return this; } public UpdateItemRequestBuilder withExpressionAttributeValues(Dictionary expressionAttributeValues) { m_request.ExpressionAttributeValues = expressionAttributeValues; return this; } public UpdateItemRequestBuilder withKeys(Dictionary keyValue) { m_request.Key = keyValue; return this; } public UpdateItemRequestBuilder addKey(string keyName, string keyValue) { m_request.Key[keyName] = new AttributeValue { S = keyValue }; return this; } public UpdateItemRequestBuilder addKey(string keyName, int keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public UpdateItemRequestBuilder addKey(string keyName, long keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public UpdateItemRequestBuilder addKey(string keyName, float keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public UpdateItemRequestBuilder addKey(string keyName, double keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public UpdateItemRequestBuilder addKey(string keyName, bool keyValue) { m_request.Key[keyName] = new AttributeValue { BOOL = keyValue }; return this; } public UpdateItemRequestBuilder addKey(string keyName, AttributeValue keyValue) { m_request.Key[keyName] = keyValue; return this; } public UpdateItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity) { m_request.ReturnConsumedCapacity = returnConsumedCapacity; return this; } public UpdateItemRequestBuilder withReturnItemCollectionMetrics(ReturnItemCollectionMetrics returnItemCollectionMetrics) { m_request.ReturnItemCollectionMetrics = returnItemCollectionMetrics; return this; } public UpdateItemRequestBuilder withUpdateExpression(string updateExpression) { m_request.UpdateExpression = updateExpression; return this; } public UpdateItemRequestBuilder withConditionCheck(string conditionExpression) { m_request.ConditionExpression = conditionExpression; return this; } public (Result, UpdateItemRequest?) build() { var result = new Result(); if(null != m_document) { result = m_document.tryFillupUpdateItemRequest(m_request, m_is_upsert); if(result.isFail()) { return (result, null); } } else { if ( false == m_is_upsert && null != m_request.ConditionExpression && m_request.ConditionExpression.isNullOrWhiteSpace() ) { m_request.ConditionExpression = $"attribute_exists({PrimaryKey.PK_Define}) AND attribute_exists({PrimaryKey.SK_Define})"; } } return (result, m_request); } } //========================================================================================= // GetItemRequest Builder //========================================================================================= public class GetItemRequestBuilder { private readonly GetItemRequest m_request; public GetItemRequestBuilder(string tableName) { m_request = new GetItemRequest { TableName = tableName, Key = new Dictionary(), ExpressionAttributeNames = new Dictionary() }; } public GetItemRequestBuilder withKeys(Dictionary keyValue) { m_request.Key = keyValue; return this; } public GetItemRequestBuilder addKey(string keyName, string keyValue) { m_request.Key[keyName] = new AttributeValue { S = keyValue }; return this; } public GetItemRequestBuilder addKey(string keyName, int keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public GetItemRequestBuilder addKey(string keyName, long keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public GetItemRequestBuilder addKey(string keyName, double keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public GetItemRequestBuilder addKey(string keyName, float keyValue) { m_request.Key[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public GetItemRequestBuilder addKey(string keyName, bool keyValue) { m_request.Key[keyName] = new AttributeValue { BOOL = keyValue }; return this; } public GetItemRequestBuilder withConsistentRead(bool isConsistentRead) { m_request.ConsistentRead = isConsistentRead; return this; } public GetItemRequestBuilder withProjectionExpression(string projectExpression) { m_request.ProjectionExpression = projectExpression; return this; } public GetItemRequestBuilder withExpressionAttributeNames(Dictionary expressionAttributeNames) { m_request.ExpressionAttributeNames = expressionAttributeNames; return this; } public GetItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity) { m_request.ReturnConsumedCapacity = returnConsumedCapacity; return this; } public GetItemRequest build() { return m_request; } } //========================================================================================= // PutItemRequest Builder //========================================================================================= public class PutItemRequestBuilder { private readonly PutItemRequest m_request; public PutItemRequestBuilder(string tableName) { m_request = new PutItemRequest { TableName = tableName, Item = new Dictionary(), ExpressionAttributeNames = new Dictionary(), ExpressionAttributeValues = new Dictionary() }; } public PutItemRequestBuilder withItem(Dictionary attributeValues) { m_request.Item = attributeValues; return this; } public PutItemRequestBuilder addItem(string keyName, string keyValue) { m_request.Item[keyName] = new AttributeValue { S = keyValue }; return this; } public PutItemRequestBuilder addItem(string keyName, int keyValue) { m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public PutItemRequestBuilder addItem(string keyName, long keyValue) { m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public PutItemRequestBuilder addItem(string keyName, double keyValue) { m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public PutItemRequestBuilder addItem(string keyName, float keyValue) { m_request.Item[keyName] = new AttributeValue { N = keyValue.ToString() }; return this; } public PutItemRequestBuilder addItem(string keyName, bool keyValue) { m_request.Item[keyName] = new AttributeValue { BOOL = keyValue }; return this; } public PutItemRequestBuilder withConditionalOperator(ConditionalOperator conditionalOperator) { m_request.ConditionalOperator = conditionalOperator; return this; } public PutItemRequestBuilder withConditionalCapacity(ReturnConsumedCapacity returnConsumedCapacity) { m_request.ReturnConsumedCapacity = returnConsumedCapacity; return this; } public PutItemRequestBuilder withConditionExpression(string conditionExpression) { m_request.ConditionExpression = conditionExpression; return this; } public PutItemRequestBuilder withExpressionAttributeNames(Dictionary expressAttributeNames) { m_request.ExpressionAttributeNames = expressAttributeNames; return this; } public PutItemRequestBuilder withExpressionAttributeValues(Dictionary expressAttributeValues) { m_request.ExpressionAttributeValues = expressAttributeValues; return this; } public PutItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity retrunConsumedCapacity) { m_request.ReturnConsumedCapacity = retrunConsumedCapacity; return this; } public PutItemRequestBuilder withReturnItemCollectionMetrics(ReturnItemCollectionMetrics returnItemCollectionMetrics) { m_request.ReturnItemCollectionMetrics = returnItemCollectionMetrics; return this; } public PutItemRequest build() { return m_request; } } //========================================================================================= // DeleteItemRequest Builder //========================================================================================= public class DeleteItemRequestBuilder { private readonly DeleteItemRequest m_request; public DeleteItemRequestBuilder(string tableName) { m_request = new DeleteItemRequest { TableName = tableName, Key = new Dictionary(), ExpressionAttributeNames = new Dictionary(), ExpressionAttributeValues = new Dictionary() }; } public DeleteItemRequestBuilder withKeys(Dictionary keys) { m_request.Key = keys; return this; } public DeleteItemRequestBuilder addKey(string attributeName, string attributeValue) { m_request.Key[attributeName] = new AttributeValue { S = attributeValue }; return this; } public DeleteItemRequestBuilder addKey(string attributeName, int attributeValue) { m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() }; return this; } public DeleteItemRequestBuilder addKey(string attributeName, long attributeValue) { m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() }; return this; } public DeleteItemRequestBuilder addKey(string attributeName, double attributeValue) { m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() }; return this; } public DeleteItemRequestBuilder addKey(string attributeName, float attributeValue) { m_request.Key[attributeName] = new AttributeValue { N = attributeValue.ToString() }; return this; } public DeleteItemRequestBuilder addKey(string attributeName, bool attributeValue) { m_request.Key[attributeName] = new AttributeValue { BOOL = attributeValue }; return this; } public DeleteItemRequestBuilder withConditionalOperator(ConditionalOperator conditionalOperator) { m_request.ConditionalOperator = conditionalOperator; return this; } public DeleteItemRequestBuilder withReturnValues(ReturnValue returnValue) { m_request.ReturnValues = returnValue; return this; } public DeleteItemRequestBuilder withConditionExpression(string conditionExpression) { m_request.ConditionExpression = conditionExpression; return this; } public DeleteItemRequestBuilder withExpressionAttributeNames(Dictionary expressionAttributeNames) { m_request.ExpressionAttributeNames = expressionAttributeNames; return this; } public DeleteItemRequestBuilder withExpressionAttributeValues(Dictionary expressionAttributeValues) { m_request.ExpressionAttributeValues = expressionAttributeValues; return this; } public DeleteItemRequestBuilder withReturnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity) { m_request.ReturnConsumedCapacity = returnConsumedCapacity; return this; } public DeleteItemRequestBuilder withReturnItemCollectionMetrics(ReturnItemCollectionMetrics returnItemCollectionMetrics) { m_request.ReturnItemCollectionMetrics = returnItemCollectionMetrics; return this; } public DeleteItemRequest build() { return m_request; } } public static DynamoDbItemRequestQueryContext createItemRequestQueryContext( this AmazonDynamoDBRequest itemRequest , QueryType queryType , DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler = null) { return new DynamoDbItemRequestQueryContext(itemRequest, queryType, exceptionHandler); } public static bool isValid(this AmazonDynamoDBRequest itemRequest) { //if (document.getPK() == string.Empty || document.getSK() == string.Empty || document.getDocType() == string.Empty) //{ // Log.getLogger().error($"PK or SK or DocType is empty !!! : {document.toBasicString()}"); // return false; //} return true; } }