Files
caliverse_server/ServerBase/DB/QueryRunnerWithDocument.cs
2025-05-01 07:20:41 +09:00

645 lines
25 KiB
C#

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<Table, Dictionary<Type, IQueryDbRequester>> m_reserved_db_requesters = new();
private List<IQueryDbRequester> 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<Result> 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<Result> 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<DBRequestGetWithDocumentBatchGet>())
{
ArgumentNullException.ThrowIfNull(m_batch_get, $"m_batch_get is null !!!");
await m_batch_get.ExecuteAsync();
}
else if (true == db_requester.isEqualQuery<DBRequestWriteWithDocumentBatchWrite>())
{
ArgumentNullException.ThrowIfNull(m_batch_write, $"m_batch_write is null !!!");
await m_batch_write.ExecuteAsync();
}
else if (true == db_requester.isEqualQuery<DBRequestGetWithDocumentTransactGet>())
{
ArgumentNullException.ThrowIfNull(m_transact_get, $"m_transact_get is null !!!");
await m_transact_get.ExecuteAsync();
}
else if (true == db_requester.isEqualQuery<DBRequestWriteWithDocumentTransactWrite>())
{
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<Result> pushQueryContexts(List<DynamoDbDocumentQueryContext> 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<Result> 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<DBRequestGetWithDocumentTransactGet>(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<DBRequestGetWithDocumentBatchGet>(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<DBRequestWriteWithDocumentTransactWrite>(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<DBRequestWriteWithDocumentBatchWrite>(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<Result> 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<DBRequestWriteWithDocumentTransactWrite>(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<DBRequestWriteWithDocumentBatchWrite>(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<DBRequestWriteWithDocumentTransactWrite>(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<DBRequestWriteWithDocumentBatchWrite>(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<DBRequestWriteWithDocumentTransactWrite>(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<DBRequestWriteWithDocumentBatchWrite>(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<Result> 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<DynamoDbDocBase>();
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<TDbRequester>(Table table)
where TDbRequester : class, IQueryDbRequester, new()
{
ArgumentNullException.ThrowIfNull(table, $"table is null !!!");
var db_requester = getDbRequester<TDbRequester>(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<TDbRequester>(Table table)
where TDbRequester : class, IQueryDbRequester
{
if(false == m_reserved_db_requesters.TryGetValue(table, out var found_db_requesters))
{
found_db_requesters = new Dictionary<Type, IQueryDbRequester>();
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();
}
}