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

612 lines
23 KiB
C#

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<IQueryDbRequester> m_db_requesters = new();
private IQueryDbRequester? m_executing_query_nullable;
private readonly List<AmazonWebServiceResponse> m_db_responses = new();
public QueryRunnerWithItemRequest()
{ }
public async Task<Result> onTryCommitQueryRunner()
{
await Task.CompletedTask;
var result = new Result();
return result;
}
public async Task<Result> 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<Result> 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<DBRequestGetWithTransact>();
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<DBRequestGetBatch>();
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<DBRequestWriteWithTransact>();
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<DBRequestWriteBatch>();
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<WriteRequest>();
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<DBRequestWriteWithTransact>();
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<DBRequestUpdate>();
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<DBRequestWriteWithTransact>();
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<DBRequestWriteBatch>();
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<WriteRequest>();
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<Result> 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<TDbRequester>()
where TDbRequester : class, IQueryDbRequester, new()
{
var db_requester = getDbRequester<TDbRequester>();
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<TDbRequester>();
}
public TDbRequester? getAfterAddDbRequester<TDbRequester>()
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<TDbResponseType>()
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<TDbRequester>()
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();
}
}