using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; using System.Text; using System.Threading.Tasks; using Amazon.DynamoDBv2.DocumentModel; using StackExchange.Redis; using Amazon.DynamoDBv2.Model; using ServerCore; using ServerBase; namespace ServerBase; //============================================================================================= // Amazon.DynamoDBv2.DocumentModel 기반 쿼리를 실행해 주는 클래스 이다. // // 1. 선택된 테이블에서 쿼리가 가능 하다. // 2. Get(Read)과 Write는 각각 트랜젝션이 보장 되므로 주의 한다 // 3. DocumentTransactGet + DocumentTransactWrite 실행시 동일한 PK에 대한 트랜잭션 보장은 되지 않는다. // 4. 쿼리는 Get(Read), Write 는 최초 등록된 순서대로 실행 된다. //============================================================================================= public partial class QueryRunnerWithDocument : IQueryRunner { private QueryBatchBase? m_query_batch; private Dictionary> m_reserved_db_requesters = new(); private List m_to_try_db_requesters = new(); private IQueryDbRequester? m_executing_query_nullable; private MultiTableDocumentBatchGet? m_batch_get; private MultiTableDocumentBatchWrite? m_batch_write; private MultiTableDocumentTransactGet? m_transact_get; private MultiTableDocumentTransactWrite? m_transact_write; public QueryRunnerWithDocument() { } public async Task onTryCommitQueryRunner() { var result = new Result(); foreach (var db_requester in m_to_try_db_requesters) { result = await db_requester.doDbRequestAsync(); if(result.isFail()) { return result; } } return result; } public async Task onCommitQueryRunner() { var result = new Result(); foreach (var db_requester in m_to_try_db_requesters) { m_executing_query_nullable = db_requester; var err_msg = $"DBRequesterType:{db_requester.getTypeName()}, QueryCount:{db_requester.getQueryCount()}"; Log.getLogger().info(err_msg); if (true == db_requester.isEqualQuery()) { ArgumentNullException.ThrowIfNull(m_batch_get, $"m_batch_get is null !!!"); await m_batch_get.ExecuteAsync(); } else if (true == db_requester.isEqualQuery()) { ArgumentNullException.ThrowIfNull(m_batch_write, $"m_batch_write is null !!!"); await m_batch_write.ExecuteAsync(); } else if (true == db_requester.isEqualQuery()) { ArgumentNullException.ThrowIfNull(m_transact_get, $"m_transact_get is null !!!"); await m_transact_get.ExecuteAsync(); } else if (true == db_requester.isEqualQuery()) { ArgumentNullException.ThrowIfNull(m_transact_write, $"m_transact_write is null !!!"); await m_transact_write.ExecuteAsync(); } else { err_msg = $"Not found Query with Document !!!"; result.setFail(ServerErrorCode.DynamoDbQueryNotFoundDocumentQuery, err_msg); return result; } } return result; } public async Task onRollbackQueryRunner() { await Task.CompletedTask; m_reserved_db_requesters.Clear(); m_to_try_db_requesters.Clear(); m_batch_get?.Batches.Clear(); m_batch_write?.Batches.Clear(); m_transact_get?.TransactionParts.Clear(); m_transact_write?.TransactionParts.Clear(); } public async Task pushQueryContexts(List toQueryConexts) { ArgumentNullException.ThrowIfNull(toQueryConexts, $"toQueryConexts is null !!!"); var result = new Result(); var err_msg = string.Empty; foreach (var query_context in toQueryConexts) { result = await pushQueryContext(query_context); if(result.isFail()) { return result; } } return result; } protected async Task pushQueryContext(DynamoDbDocumentQueryContext queryContext) { var result = new Result(); var err_msg = string.Empty; var query_batch = getQueryBatch(); NullReferenceCheckHelper.throwIfNull(query_batch, () => $"query_batch is null !!! - {queryContext.toBasicString()}"); var dynamo_db_connector = query_batch.getDynamoDbConnector(); var table = dynamo_db_connector.getTableByFullName(queryContext.getTableFullName()); if (false == queryContext.isValid()) { err_msg = $"DynamoDbDocumentQueryContext invalid !!! - {queryContext.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsNullInQueryContext, err_msg); Log.getLogger().error(result.toBasicString()); return result; } var document = queryContext.getDocument(); NullReferenceCheckHelper.throwIfNull(document, () => $"document is null !!! - {queryContext.toBasicString()}"); if (false == document.isValid()) { err_msg = $"DynamoDbDocument invalid !!! : {document.toBasicString()} - {queryContext.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(table, queryContext); break; } case QueryType.Update: { result = await pushUpdateQuery(table, queryContext); if(result.isFail()) { return result; } break; } case QueryType.Upsert: { result = await pushUpdateQuery(table, queryContext, true); if (result.isFail()) { return result; } break; } case QueryType.Delete: { pushDeleteQuery(table, queryContext); break; } case QueryType.ExistsDelete: { pushDeleteQueryWithExistAttribute(table, queryContext); break; } default: err_msg = $"Invalid QueryType getQueryType() !!! : queryType:{query_type} - {queryContext.toBasicString()}, {document.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentQueryContextTypeInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return result; } var msg = $"{this.getTypeName()} : {queryContext.getQueryType().convertEnumToEnumTypeAndValueString()} : {document.toBasicString()}"; Log.getLogger().info(msg); return await Task.FromResult(result); } public void pushSelectQuery(Table table, DynamoDbDocumentQueryContext queryContext) { ArgumentNullException.ThrowIfNull(table, $"table is null !!!"); ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!"); var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var to_select_ducument = queryContext.getDocument(); ArgumentNullException.ThrowIfNull(to_select_ducument, $"to_select_ducument is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var get_with_transact_request = getOrAddDbRequesterWithDocument(table); 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.AddKey(to_select_ducument); db_requster = get_with_transact_request; } else { var get_with_batch_request = getOrAddDbRequesterWithDocument(table); ArgumentNullException.ThrowIfNull(get_with_batch_request, $"get_with_batch_request is null !!!"); var generic_type = get_with_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.AddKey(to_select_ducument); db_requster = get_with_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public void pushInsertQuery(Table table, DynamoDbDocumentQueryContext queryContext) { ArgumentNullException.ThrowIfNull(table, $"table is null !!!"); ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!"); var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var db_connector = query_batch.getDynamoDbConnector(); ArgumentNullException.ThrowIfNull(db_connector, $"db_connector is null !!!"); var to_insert_ducument = queryContext.getDocument(); ArgumentNullException.ThrowIfNull(to_insert_ducument, $"to_insert_ducument is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var write_with_transact_request = getOrAddDbRequesterWithDocument(table); 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.AddDocumentToPut(to_insert_ducument, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForNotExistKey()); db_requster = write_with_transact_request; } else { var write_with_batch_request = getOrAddDbRequesterWithDocument(table); ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!"); var generic_type = write_with_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.AddDocumentToPut(to_insert_ducument); db_requster = write_with_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public async Task pushUpdateQuery(Table table, DynamoDbDocumentQueryContext queryContext, bool isUpsert = false) { ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!"); var result = new Result(); var err_msg = string.Empty; var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var dynamo_db_connector = query_batch.getDynamoDbConnector(); var to_update_ducument = queryContext.getDocument(); ArgumentNullException.ThrowIfNull(to_update_ducument, $"to_update_ducument is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var write_with_transact_request = getOrAddDbRequesterWithDocument(table); 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 !!!"); if (true == isUpsert) { generic_type.AddDocumentToUpdate( to_update_ducument ); } else { generic_type.AddDocumentToUpdate( to_update_ducument, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey() ); } db_requster = write_with_transact_request; } else { if (true == isUpsert) { result = await to_update_ducument.tryFillupTimestampsForUpsert(dynamo_db_connector, table.TableName); if(result.isFail()) { err_msg = $"Failed to tryFillupTimestampsForUpsert() !!! : {result.toBasicString()} - {queryContext.toBasicString()}, {to_update_ducument.toBasicString()}"; Log.getLogger().error(err_msg); return result; } } else { (result, var primary_key) = to_update_ducument.toPrimaryKey(); if(result.isFail()) { return result; } NullReferenceCheckHelper.throwIfNull(primary_key, () => $"primary_key is null !!!"); (result, _) = await dynamo_db_connector.isExistPrimaryKey(table.TableName, primary_key, true); if (result.isFail()) { return result; } } var write_with_batch_request = getOrAddDbRequesterWithDocument(table); ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!"); var generic_type = write_with_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.AddDocumentToPut(to_update_ducument); db_requster = write_with_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); return result; } public void pushDeleteQuery(Table table, DynamoDbDocumentQueryContext queryContext) { ArgumentNullException.ThrowIfNull(table, $"table is null !!!"); ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!"); var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var to_delete_ducument = queryContext.getDocument(); ArgumentNullException.ThrowIfNull(to_delete_ducument, $"to_delete_ducument is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var write_with_transact_request = getOrAddDbRequesterWithDocument(table); 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.AddItemToDelete(to_delete_ducument); db_requster = write_with_transact_request; } else { var write_with_batch_request = getOrAddDbRequesterWithDocument(table); ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!"); var generic_type = write_with_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); generic_type.AddItemToDelete(to_delete_ducument); db_requster = write_with_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public void pushDeleteQueryWithExistAttribute(Table table, DynamoDbDocumentQueryContext queryContext) { ArgumentNullException.ThrowIfNull(table, $"table is null !!!"); ArgumentNullException.ThrowIfNull(queryContext, $"queryContext is null !!!"); var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); var db_connector = query_batch.getDynamoDbConnector(); var to_delete_ducument = queryContext.getDocument(); ArgumentNullException.ThrowIfNull(to_delete_ducument, $"to_delete_ducument is null !!!"); IQueryDbRequester db_requster; if (true == query_batch.isUseTransact()) { var write_with_transact_request = getOrAddDbRequesterWithDocument(table); 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.AddItemToDelete(to_delete_ducument, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey()); db_requster = write_with_transact_request; } else { var write_with_batch_request = getOrAddDbRequesterWithDocument(table); ArgumentNullException.ThrowIfNull(write_with_batch_request, $"write_with_batch_request is null !!!"); var generic_type = write_with_batch_request.getDbRequestGenericType(); ArgumentNullException.ThrowIfNull(generic_type, $"generic_type is null !!!"); db_requster = write_with_batch_request; } registerExceptionHandler(db_requster, queryContext.getExceptionHandler()); } public async Task tryRegisterQueryContext(IQueryContext queryContext) { var result = new Result(); var doc_query_context = queryContext as DynamoDbDocumentQueryContext; ArgumentNullReferenceCheckHelper.throwIfNull(doc_query_context, () => $"doc_query_context is null !!!"); result = await pushQueryContext(doc_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 doc_base = rowData as DynamoDbDocBase; ArgumentNullReferenceCheckHelper.throwIfNull(doc_base, () => $"doc_base is null !!! - {rowData.getTypeName()}"); if(QueryType.None != toApplyQueryType) { doc_base.setQueryType(toApplyQueryType); } (result, var query_context) = await doc_base.toDocumentQueryContext(); if(result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(query_context, () => $"query_context is null !!!"); return (result, query_context); } public MultiTableDocumentBatchGet getOrNewMultiTableDocumentBatchGet() { if(null == m_batch_get) { m_batch_get = new MultiTableDocumentBatchGet(); } return m_batch_get; } public MultiTableDocumentBatchWrite getOrNewMultiTableDocumentBatchWrite() { if (null == m_batch_write) { m_batch_write = new MultiTableDocumentBatchWrite(); } return m_batch_write; } public MultiTableDocumentTransactGet getOrNewMultiTableDocumentTransactGet() { if (null == m_transact_get) { m_transact_get = new MultiTableDocumentTransactGet(); } return m_transact_get; } public MultiTableDocumentTransactWrite getOrNewMultiTableDocumentTransactWrite() { if (null == m_transact_write) { m_transact_write = new MultiTableDocumentTransactWrite(); } return m_transact_write; } public TDbRequester? getOrAddDbRequesterWithDocument(Table table) where TDbRequester : class, IQueryDbRequester, new() { ArgumentNullException.ThrowIfNull(table, $"table is null !!!"); var db_requester = getDbRequester(table); if (null != db_requester) { return db_requester; } var query_batch = getQueryBatch(); ArgumentNullException.ThrowIfNull(query_batch, $"query_batch is null !!!"); db_requester = new TDbRequester(); db_requester.setQueryBatch(query_batch); var db_requester_with_document = db_requester as IWithDocumentModel; ArgumentNullException.ThrowIfNull(db_requester_with_document, $"db_requester_with_document is null !!!"); db_requester_with_document.onCreateDocumentModel(table); var db_requester_type = typeof(TDbRequester); m_reserved_db_requesters[table][db_requester_type] = db_requester; m_to_try_db_requesters.Add(db_requester); return db_requester; } public TDbRequester? getDbRequester(Table table) where TDbRequester : class, IQueryDbRequester { if(false == m_reserved_db_requesters.TryGetValue(table, out var found_db_requesters)) { found_db_requesters = new Dictionary(); m_reserved_db_requesters.Add(table, found_db_requesters); } var db_requester_type = typeof(TDbRequester); if(false == found_db_requesters.TryGetValue(db_requester_type, out var found_db_requester)) { return null; } return found_db_requester as TDbRequester; } 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_result = 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_result); } // 트랜잭션 취소 원인에 따라 등록된 ServerErrorCode로 재설정 한다. // 성공일 경우에는 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_result.setFail(exception_handler.ReturnToErrorCode, err_msg); } } return (result, exception_result); } private DynamoDbQueryExceptionNotifier getDynamoDbQueryExceptionNotifierOfExecutingQuery() { ArgumentNullException.ThrowIfNull(m_executing_query_nullable, $"m_executing_query_nullable is null !!!"); return m_executing_query_nullable.getDynamoDbQueryExceptionNotifier(); } }