using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Amazon.Runtime; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using StackExchange.Redis; using ServerCore; using ServerBase; namespace ServerBase; //============================================================================================= // Amazon.DynamoDBv2.Model 기반 쿼리를 실행해 주는 클래스 이다. // // 1. 복수개의 테이블에 쿼리가 가능 하다. // 2. 세부적인 Attribute 단위 쿼리가 가능 하다. // 3. TransactGetItemsRequest(Read)과 TransactWriteItemsRequest(Write) 각각 트랜젝션이 보장 되므로 주의 한다. // 4. TransactGetItemsRequest + TransactWriteItemsRequest 실행시 동일한 PK에 대한 트랜잭션 보장은 되지 않는다. // 5. 쿼리는 Get(Read), Write 는 최초 등록된 순서대로 실행 된다. // 6. 주요 코어 처리 객체 // BatchGetItemRequest : 배치 기반 읽기 쿼리 // BatchWriteItemRequest : 배치 기반 쓰기 쿼리 // UpdateItemRequest : 특정 대상을 위한 Upsert 쿼리 // TransactGetItemsRequest : 배치 + 트랜잭션 기반 읽기 쿼리 // TransactWriteItemsRequest : 배치 + 트랜잭션 기반 쓰기 쿼리 //============================================================================================= public partial class QueryRunnerWithItemRequest : IQueryRunner { private QueryBatchBase? m_query_batch; private readonly List m_db_requesters = new(); private IQueryDbRequester? m_executing_query_nullable; private readonly List m_db_responses = new(); public QueryRunnerWithItemRequest() { } public async Task onTryCommitQueryRunner() { await Task.CompletedTask; var result = new Result(); return result; } public async Task onCommitQueryRunner() { var result = new Result(); foreach (var db_requester in m_db_requesters) { m_executing_query_nullable = db_requester; var err_msg = $"DBRequesterType:{db_requester.getTypeName()}, QueryCount:{db_requester.getQueryCount()}"; Log.getLogger().info(err_msg); result = await db_requester.doDbRequestAsync(); if (result.isFail()) { break; } } return result; } public async Task onRollbackQueryRunner() { await Task.CompletedTask; m_db_requesters.Clear(); m_db_responses.Clear(); } protected async Task pushQueryContext(DynamoDbItemRequestQueryContext queryContext) { ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!"); var result = new Result(); var err_msg = string.Empty; if (false == queryContext.isValid()) { err_msg = $"DynamoDbDocumentQueryContext invalid !!! - {queryContext.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsNullInQueryContext, err_msg); Log.getLogger().error(result.toBasicString()); return result; } var item_request = queryContext.getAmazonDynamoDBRequest(); NullReferenceCheckHelper.throwIfNull(item_request, () => $"item_request is null !!!"); if (false == item_request.isValid()) { err_msg = $"DynamoDbDocument invalid !!! : {item_request.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return result; } var query_type = queryContext.getQueryType(); switch (query_type) { case QueryType.Insert: { pushInsertQuery(queryContext); break; } case QueryType.Update: { result = pushUpdateQuery(queryContext, false); if (result.isFail()) { return result; } break; } case QueryType.Upsert: { result = pushUpdateQuery(queryContext, true); if (result.isFail()) { return result; } break; } case QueryType.Delete: { pushDeleteQuery(queryContext); break; } default: err_msg = $"Invalid QueryType getQueryType() !!! : queryType:{query_type} - {queryContext.toBasicString()}, {item_request.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbItemRequestQueryContextTypeInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return result; } var msg = $"{this.getTypeName()} : {queryContext.getQueryType().convertEnumToEnumTypeAndValueString()} : {item_request.toBasicString()}"; Log.getLogger().info(msg); return await Task.FromResult(result); } public void pushSelectQuery(DynamoDbItemRequestQueryContext queryContext) { var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var to_select_db_item_request = queryContext.getAmazonDynamoDBRequest() as GetItemRequest; ArgumentNullException.ThrowIfNull(to_select_db_item_request, $"to_select_db_item_request is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var transactGetItem = new TransactGetItem() { Get = new Get() { TableName = to_select_db_item_request.TableName, Key = to_select_db_item_request.Key } }; var get_with_transact_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(get_with_transact_request, $"get_with_transact_request is null !!!"); var generic_type = get_with_transact_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.TransactItems.Add(transactGetItem); db_requster = get_with_transact_request; } else { var get_batch_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(get_batch_request, $"get_batch_request is null !!!"); var generic_type = get_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.RequestItems.TryGetValue(to_select_db_item_request.TableName, out var get_requests); if (null == get_requests) { get_requests = new KeysAndAttributes(); generic_type.RequestItems.Add(to_select_db_item_request.TableName, get_requests); } get_requests.Keys.Add(to_select_db_item_request.Key); db_requster = get_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public void pushInsertQuery(DynamoDbItemRequestQueryContext queryContext) { var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var to_insert_db_item_request = queryContext.getAmazonDynamoDBRequest() as PutItemRequest; ArgumentNullException.ThrowIfNull(to_insert_db_item_request, $"to_insert_db_item_request is null !!!"); IQueryDbRequester db_requster; to_insert_db_item_request.fillupTimestamps(); if (true == query_batch.isUseTransact()) { var transactWriteItem = new TransactWriteItem() { Put = new Put() { TableName = to_insert_db_item_request.TableName, Item = to_insert_db_item_request.Item, ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, ConditionExpression = to_insert_db_item_request.ConditionExpression, } }; var write_with_transact_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!"); var generic_type = write_with_transact_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.TransactItems.Add(transactWriteItem); db_requster = write_with_transact_request; } else { var write_batch_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(write_batch_request, $"write_batch_request is null !!!"); var generic_type = write_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.RequestItems.TryGetValue(to_insert_db_item_request.TableName, out var write_requests); if (null == write_requests) { write_requests = new List(); generic_type.RequestItems.Add(to_insert_db_item_request.TableName, write_requests); } write_requests.Add( new WriteRequest { PutRequest = new PutRequest { Item = to_insert_db_item_request.Item } } ); db_requster = write_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public Result pushUpdateQuery(DynamoDbItemRequestQueryContext queryContext, bool isUpsert = false) { var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var to_update_db_item_request = queryContext.getAmazonDynamoDBRequest() as UpdateItemRequest; ArgumentNullException.ThrowIfNull(to_update_db_item_request, $"to_update_db_item_request is null !!!"); var result = new Result(); var err_msg = string.Empty; if(to_update_db_item_request.UpdateExpression.isNullOrWhiteSpace()) { err_msg = $"UpdateItemRequest.UpdateExpression is Empty !!! - {queryContext.toBasicString()}, {to_update_db_item_request.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbItemRequestUpdateExpressionEmpty, err_msg); Log.getLogger().error(err_msg); return result; } result = to_update_db_item_request.tryFillupTimestamps(isUpsert); if (result.isFail()) { err_msg = $"Failed to tryFillupTimestamps() !!! : {result.toBasicString()} - {queryContext.toBasicString()}, {to_update_db_item_request.toBasicString()}"; Log.getLogger().error(err_msg); return result; } IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var transactWriteItem = new TransactWriteItem() { Update = new Update() { TableName = to_update_db_item_request.TableName, Key = to_update_db_item_request.Key, ExpressionAttributeNames = to_update_db_item_request.ExpressionAttributeNames, ExpressionAttributeValues = to_update_db_item_request.ExpressionAttributeValues, ConditionExpression = to_update_db_item_request.ConditionExpression, UpdateExpression = to_update_db_item_request.UpdateExpression, ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, } }; var write_with_transact_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!"); var generic_type = write_with_transact_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.TransactItems.Add(transactWriteItem); db_requster = write_with_transact_request; } else { var update_request = getAfterAddDbRequester(); ArgumentNullException.ThrowIfNull(update_request, $"update_request is null !!!"); var generic_type = update_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.TableName = to_update_db_item_request.TableName; generic_type.Key = to_update_db_item_request.Key; generic_type.ExpressionAttributeNames = to_update_db_item_request.ExpressionAttributeNames; generic_type.ExpressionAttributeValues = to_update_db_item_request.ExpressionAttributeValues; generic_type.ConditionExpression = to_update_db_item_request.ConditionExpression; generic_type.UpdateExpression = to_update_db_item_request.UpdateExpression; generic_type.ReturnValues = to_update_db_item_request.ReturnValues; db_requster = update_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); return result; } public void pushDeleteQuery(DynamoDbItemRequestQueryContext queryContext) { var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var to_delete_db_item_request = queryContext.getAmazonDynamoDBRequest() as DeleteItemRequest; ArgumentNullException.ThrowIfNull(to_delete_db_item_request, $"to_delete_db_item_request is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var transactWriteItem = new TransactWriteItem() { Delete = new Delete() { TableName = to_delete_db_item_request.TableName, Key = to_delete_db_item_request.Key, ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, } }; var write_with_transact_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(write_with_transact_request, $"write_with_transact_request is null !!!"); var generic_type = write_with_transact_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.TransactItems.Add(transactWriteItem); db_requster = write_with_transact_request; } else { var write_batch_request = getOrAddDbRequester(); ArgumentNullException.ThrowIfNull(write_batch_request, $"write_batch_request is null !!!"); var generic_type = write_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.RequestItems.TryGetValue(to_delete_db_item_request.TableName, out var write_requests); if (null == write_requests) { write_requests = new List(); generic_type.RequestItems.Add(to_delete_db_item_request.TableName, write_requests); } write_requests.Add( new WriteRequest { DeleteRequest = new DeleteRequest { Key = to_delete_db_item_request.Key } } ); db_requster = write_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public async Task tryRegisterQueryContext(IQueryContext queryContext) { var query_batch = getQueryBatch(); NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!!"); var result = new Result(); DynamoDbItemRequestQueryContext? item_request_query_context; if(queryContext is DynamoDbDocumentQueryContext document_query_context) { (result, item_request_query_context) = document_query_context.getDocument().toItemRequestQueryContext(document_query_context.getTableFullName(), queryContext.getQueryType()); if(result.isFail()) { return result; } } else { item_request_query_context = queryContext as DynamoDbItemRequestQueryContext; } ArgumentNullReferenceCheckHelper.throwIfNull(item_request_query_context, () => $"item_request_query_context is null !!!"); result = await pushQueryContext(item_request_query_context); if (result.isFail()) { return result; } return result; } public async Task<(Result, IQueryContext?)> tryCreateQueryContext(IRowData rowData, QueryType toApplyQueryType = QueryType.None) { var result = new Result(); var err_msg = string.Empty; var query_batch = getQueryBatch(); NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!!"); var dynamo_db_connector = query_batch.getDynamoDbConnector(); var doc_base = rowData as DynamoDbDocBase; ArgumentNullReferenceCheckHelper.throwIfNull(doc_base, () => $"doc_base is null !!! - {rowData.getTypeName()}"); if (QueryType.None != toApplyQueryType) { doc_base.setQueryType(toApplyQueryType); } var query_type = doc_base.getQueryType(); if (QueryType.None == query_type) { err_msg = $"Invalid QueryType !!! : QueryType.None != DocBase.QueryType:{query_type} - {doc_base.toBasicString()}"; result.setFail(ServerErrorCode.DbQueryTypeInvalid, err_msg); return (result, null); } (result, var document) = await doc_base.onCopyToDocument(); if(result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(document, () => $"document is null !!! - {doc_base.toBasicString()}"); (result, var item_request_query_context) = document.toItemRequestQueryContext(dynamo_db_connector.getTableFullName(doc_base.TableName), doc_base.getQueryType()); if (result.isFail()) { return (result, null); } return (result, item_request_query_context); } public TDbRequester? getOrAddDbRequester() where TDbRequester : class, IQueryDbRequester, new() { var db_requester = getDbRequester(); if (null != db_requester) { return db_requester; } db_requester = new TDbRequester(); var batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(batch, $"batch is null !!!"); db_requester.setQueryBatch(batch); m_db_requesters.Add(db_requester); return getDbRequester(); } public TDbRequester? getAfterAddDbRequester() where TDbRequester : class, IQueryDbRequester, new() { var db_requester = new TDbRequester(); var batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(batch, $"batch is null !!!"); db_requester.setQueryBatch(batch); m_db_requesters.Add(db_requester); return db_requester; } public TDbResponseType? getDbResponse() where TDbResponseType : AmazonWebServiceResponse { foreach (var db_response in m_db_responses) { var response_type = db_response as TDbResponseType; if (null != response_type) { return response_type; } } return null; } public TDbRequester? getDbRequester() where TDbRequester : class, IQueryDbRequester { foreach (var db_requester in m_db_requesters) { var to_return_type = db_requester as TDbRequester; if (null != to_return_type) { return to_return_type; } } return null; } private void registerExceptionHandler(IQueryDbRequester queryDbRequester, DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler) { var exception_notifier = queryDbRequester.getDynamoDbQueryExceptionNotifier(); ArgumentNullException.ThrowIfNull(exception_notifier, $"exception_notifier is null !!!"); exception_notifier.registerExceptionHandler(exceptionHandler); } public async Task<(Result, Result)> onHandleTransactionCancelException(TransactionCanceledException exception) { await Task.CompletedTask; ArgumentNullException.ThrowIfNull(exception, $"exception is null !!!"); var result = new Result(); var exception_resullt = new Result(); var err_msg = string.Empty; var exception_notifier = getDynamoDbQueryExceptionNotifierOfExecutingQuery(); if (null == exception_notifier) { err_msg = $"Not found ExceptionNotifier !!!"; result.setFail(ServerErrorCode.DynamoDbQueryExceptionNotifierNotFound, err_msg); Log.getLogger().error(result.toBasicString()); return (result, exception_resullt); } // 트랜잭션 취소 원인에 따라 등록된 ServerErrorCode로 재설정 한다. var query_count = exception.CancellationReasons.Count; for (var query_seq_index = 0; query_seq_index < query_count; ++query_seq_index) { var reason = exception.CancellationReasons[query_seq_index]; // 성공일 경우 패스 !!! if (true == reason.isSuccess()) { continue; } var query_seq_no = query_seq_index + 1; err_msg = $"TransactionCanceledException Reason : QuerySeq:{query_seq_no}/{query_count}, Code:{reason.Code}, Message:{reason.Message}, Item:{reason.Item}"; Log.getLogger().fatal(err_msg); var exception_handler = exception_notifier.findExceptionHandler(query_seq_no, reason.Code); if (null != exception_handler) { exception_resullt.setFail(exception_handler.ReturnToErrorCode, err_msg); } } return (result, exception_resullt); } private DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifierOfExecutingQuery() { ArgumentNullException.ThrowIfNull(m_executing_query_nullable, $"m_executing_query_nullable is null !!!"); return m_executing_query_nullable.getDynamoDbQueryExceptionNotifier(); } }