-module(goods_transaction).

%% API
-export([
        add_sql/1
        ,transaction/1
        ,transaction/2
    ]).

-include("common.hrl").

%%% ****************************************************************************************************
%%% 事务-扩展版: 开启事务时，即兼容应用层逻辑代码连贯思维，又减少非必要数据传入db相关进程，
%%%              只将SqlList传入db相关进程，避免多余内存拷贝
%%%
%%% 注意: 主要用于物品相关模块，使用时需要符合约定，不符合约定的请使用原始版本 db:transaction/1
%%% 约定：1.Fun中开启了事务的若干sql，不能存在相互依赖关系
%%%       2.Fun中开启了事务的若干sql，必须位于同一进程
%%%
%%% 不符约定示例1:
%%%     旧版本新增一个物品，事务-扩展版中执行Fun逻辑时goods表的insert操作还没有入库，
%%%     只是写入了进程字典缓存起来，此时select查询出来的物品是不存在的
%%% 不符约定示例2:
%%%     打开一个配置了绑钻和公会贡献的礼包，由于开启事务进程字典位于玩家进程，公会贡献逻辑位于公会进程
%%%     此时发生异常公会进程的sql操作回滚不了，这时请使用原始版本 db:transaction/1
%%%
%%% 原理：?MODULE:transaction/2事务开始时，设置进程字典，中间的数据执行必须调用?MODULE:add_sql/1
%%%       存在时不会立即回写sql，而是将sql存入事务进程字典，待Fun中的逻辑执行成功后才从事务进程字典取出SqlList,
%%%       然后开启db:transaction/2批量回写db。此时传入db:transaction/2中的参数只有sql列表，不存在其它state，
%%%       减少内存拷贝
%%% ****************************************************************************************************
%%% 当数据库事务出现问题、回滚后，会执行ErrFun函数
-define(GOODS_TRANSACTION, goods_transaction).

%% 增加一个SQL
add_sql(SQL) ->
    case get(?GOODS_TRANSACTION) of
        undefined ->
            db:execute(SQL);
        SQLs ->
            put(?GOODS_TRANSACTION, [SQL|SQLs])
    end.

transaction(Fun) ->
    transaction(Fun, undefined).
transaction(Fun, ErrFun) ->
    % 获取事务进程字典
    case get(?GOODS_TRANSACTION) of
        undefined ->
            put(?GOODS_TRANSACTION, []),
            % 执行fun逻辑
            case do_transaction_login_fun(Fun, ErrFun) of
                {ok, LogicRes} ->
                    %% 获取事务进程字典中fun逻辑的SqlList, 批量回写db
                    do_transaction_sql_fun(LogicRes, ErrFun);
                _Error ->
                    _Error
            end;
        _ ->
            do_transaction_err_fun(ErrFun),
            {error, in_goods_transaction}
    end.

do_transaction_login_fun(Fun, ErrFun) ->
    try
        LogicRes = Fun(),
        {ok, LogicRes}
    catch
        ?EXCEPTION(throw, {error, _} = _Reason, _Stack) ->
            %% 外部存在匹配db_error错误的处理，需要获取主动throw的相关错误
            do_transaction_login_fun_error(ErrFun),
            {db_error, _Reason};
        ?EXCEPTION(_Class, _Reason, _Stack) ->
            do_transaction_login_fun_error(ErrFun),
            {_Class, _Reason, ?GET_STACK(_Stack)}
    end.

do_transaction_login_fun_error(ErrFun) ->
    erase(?GOODS_TRANSACTION),
    do_transaction_err_fun(ErrFun).

do_transaction_sql_fun(LogicRes, ErrFun) ->
    case lists:reverse(erase(?GOODS_TRANSACTION)) of
        [] ->
            LogicRes;
        SqlList ->
            ExecuteFun =
                fun() ->
                    [
                        db:execute(SQL)|| SQL <- SqlList
                    ],
                    ok
                end,
            LastErrFun = fun() -> do_transaction_err_fun(ErrFun) end,
            case db:transaction(ExecuteFun, LastErrFun) of
                ok -> LogicRes;
                Error ->
                    Error
            end
    end.

do_transaction_err_fun(undefined) ->
    lib_goods_dict:close_dict();
do_transaction_err_fun(ErrFun) ->
    ErrFun(),
    lib_goods_dict:close_dict().
