<commit_before>-module(gen_adapter).

-include("dderl.hrl").
-include("gres.hrl").
-include("dderl_sqlbox.hrl").

-include_lib("imem/include/imem_sql.hrl").

-export([ process_cmd/6
        , init/0
        , add_cmds_views/5
        , gui_resp/2
        , build_resp_fun/3
        , process_query/4
        , build_column_json/1
        , build_column_csv/2
        , extract_modified_rows/1
        , decrypt_to_term/1
        , encrypt_to_binary/1
        , get_deps/0
        , opt_bind_json_obj/2
        , add_conn_info/2
        ]).

-export([get_csv_col_sep_char/1, get_csv_row_sep_char/1]).

init() -> ok.

-spec add_conn_info(any(), map()) -> any().
add_conn_info(Priv, ConnInfo) when is_map(ConnInfo) -> Priv.

-spec add_cmds_views({atom(), pid()} | undefined, ddEntityId(), atom(), boolean(), [tuple()]) -> [ddEntityId() | need_replace] | {error, binary()}.
add_cmds_views(_, _, _, _, []) -> [];
add_cmds_views(Sess, UserId, A, R, [{N,C,Con}|Rest]) ->
    add_cmds_views(Sess, UserId, A, R, [{N,C,Con,#viewstate{}}|Rest]);
add_cmds_views(Sess, UserId, A, Replace, [{N,C,Con,#viewstate{}=V}|Rest]) ->
    case dderl_dal:get_view(Sess, N, A, UserId) of
        {error, _} = Error -> Error;
        undefined ->
            Id = dderl_dal:add_command(Sess, UserId, A, N, C, Con, []),
            ViewId = dderl_dal:add_view(Sess, UserId, N, Id, V),
            [ViewId | add_cmds_views(Sess, UserId, A, Replace, Rest)];
        View ->
            if
                Replace ->
                    dderl_dal:update_command(Sess, View#ddView.cmd, UserId, N, C, []),
                    ViewId = dderl_dal:add_view(Sess, UserId, N, View#ddView.cmd, V),
                    [ViewId | add_cmds_views(Sess, UserId, A, Replace, Rest)];
                true ->
                    [need_replace | add_cmds_views(Sess, UserId, A, Replace, Rest)]
            end
    end.

opt_bind_json_obj(Sql, Adapter) ->
    AdapterMod = list_to_existing_atom(atom_to_list(Adapter) ++ "_adapter"),
    Types = AdapterMod:bind_arg_types(),
    case sql_params(Sql, Types) of
        {match, []} -> [];
        {match, Parameters} ->
            [{<<"binds">>,
              [{<<"types">>, Types},
               {<<"pars">>,
                [{P, [{<<"typ">>,T},
                      {<<"dir">>,case D of
                                     <<"_IN_">> -> <<"in">>;
                                     <<"_OUT_">> -> <<"out">>;
                                     <<"_INOUT_">> -> <<"inout">>;
                                     _ -> <<"in">>
                                 end},
                      {<<"val">>,<<>>}]}
                 || [P,T,D] <- lists:usort(Parameters)]}]
             }];
        % No bind parameters can be extracted
        % possibly query string is not parameterized
        _ -> []
    end.

sql_params(Sql, Types) ->
    RegEx = "[^a-zA-Z0-9(]*:(" ++ string:join([binary_to_list(T) || T <- Types], "|")
            ++ ")((_IN_|_OUT_|_INOUT_){0,1})[^ ,\)\n\r;]+",
    try
        {ok, PTree} = sqlparse:parsetree(Sql),
        Params = sqlparse:foldtd(
                   fun({param, P}, A) ->
                           case re:run(
                                  P, RegEx,
                                  [global,{capture, [0,1,2], binary}]) of
                               {match, Prms} -> Prms ++ A;
                               _ -> A
                           end;
                      (_, A) -> A
                   end, [], PTree),
        {match, Params}
    catch C:R ->
              ?Warn("~p~n~p", [{C,R}, erlang:get_stacktrace()]),
              re:run(Sql, RegEx, [global,{capture, [0,1,2], binary}])
    end.

-spec process_cmd({[binary()], [{binary(), list()}]}, atom(), {atom(), pid()}, ddEntityId(), pid(), term()) -> term().
process_cmd({[<<"parse_stmt">>], ReqBody}, Adapter, _Sess, _UserId, From, _Priv) -><commit_msg>Fixes #337 fallback regexp fix<commit_after>-module(gen_adapter).

-include("dderl.hrl").
-include("gres.hrl").
-include("dderl_sqlbox.hrl").

-include_lib("imem/include/imem_sql.hrl").

-export([ process_cmd/6
        , init/0
        , add_cmds_views/5
        , gui_resp/2
        , build_resp_fun/3
        , process_query/4
        , build_column_json/1
        , build_column_csv/2
        , extract_modified_rows/1
        , decrypt_to_term/1
        , encrypt_to_binary/1
        , get_deps/0
        , opt_bind_json_obj/2
        , add_conn_info/2
        ]).

-export([get_csv_col_sep_char/1, get_csv_row_sep_char/1]).

init() -> ok.

-spec add_conn_info(any(), map()) -> any().
add_conn_info(Priv, ConnInfo) when is_map(ConnInfo) -> Priv.

-spec add_cmds_views({atom(), pid()} | undefined, ddEntityId(), atom(), boolean(), [tuple()]) -> [ddEntityId() | need_replace] | {error, binary()}.
add_cmds_views(_, _, _, _, []) -> [];
add_cmds_views(Sess, UserId, A, R, [{N,C,Con}|Rest]) ->
    add_cmds_views(Sess, UserId, A, R, [{N,C,Con,#viewstate{}}|Rest]);
add_cmds_views(Sess, UserId, A, Replace, [{N,C,Con,#viewstate{}=V}|Rest]) ->
    case dderl_dal:get_view(Sess, N, A, UserId) of
        {error, _} = Error -> Error;
        undefined ->
            Id = dderl_dal:add_command(Sess, UserId, A, N, C, Con, []),
            ViewId = dderl_dal:add_view(Sess, UserId, N, Id, V),
            [ViewId | add_cmds_views(Sess, UserId, A, Replace, Rest)];
        View ->
            if
                Replace ->
                    dderl_dal:update_command(Sess, View#ddView.cmd, UserId, N, C, []),
                    ViewId = dderl_dal:add_view(Sess, UserId, N, View#ddView.cmd, V),
                    [ViewId | add_cmds_views(Sess, UserId, A, Replace, Rest)];
                true ->
                    [need_replace | add_cmds_views(Sess, UserId, A, Replace, Rest)]
            end
    end.

opt_bind_json_obj(Sql, Adapter) ->
    AdapterMod = list_to_existing_atom(atom_to_list(Adapter) ++ "_adapter"),
    Types = AdapterMod:bind_arg_types(),
    case sql_params(Sql, Types) of
        {match, []} -> [];
        {match, Parameters} ->
            [{<<"binds">>,
              [{<<"types">>, Types},
               {<<"pars">>,
                [{P, [{<<"typ">>,T},
                      {<<"dir">>,case D of
                                     <<"_IN_">> -> <<"in">>;
                                     <<"_OUT_">> -> <<"out">>;
                                     <<"_INOUT_">> -> <<"inout">>;
                                     _ -> <<"in">>
                                 end},
                      {<<"val">>,<<>>}]}
                 || [P,T,D] <- lists:usort(Parameters)]}]
             }];
        % No bind parameters can be extracted
        % possibly query string is not parameterized
        _ -> []
    end.

sql_params(Sql, Types) ->
    RegEx = "[^a-zA-Z0-9() =><]*:(" ++ string:join([binary_to_list(T) || T <- Types], "|")
            ++ ")((_IN_|_OUT_|_INOUT_){0,1})[^ ,\)\n\r;]+",
    try
        {ok, PTree} = sqlparse:parsetree(Sql),
        Params = sqlparse:foldtd(
                   fun({param, P}, A) ->
                           case re:run(
                                  P, RegEx,
                                  [global,{capture, [0,1,2], binary}]) of
                               {match, Prms} -> Prms ++ A;
                               _ -> A
                           end;
                      (_, A) -> A
                   end, [], PTree),
        {match, Params}
    catch C:R ->
              ?Warn("~p~n~p", [{C,R}, erlang:get_stacktrace()]),
              re:run(Sql, RegEx, [global,{capture, [0,1,2], binary}])
    end.

-spec process_cmd({[binary()], [{binary(), list()}]}, atom(), {atom(), pid()}, ddEntityId(), pid(), term()) -> term().
process_cmd({[<<"parse_stmt">>], ReqBody}, Adapter, _Sess, _UserId, From, _Priv) ->
{'fraction_non_alphanumeric': 0.18400721091939223, 'fraction_numerical': 0.007082152974504249, 'mean_word_length': 1.7214435879467413, 'pattern_counts': {'":': 0, '<': 68, '<?xml version=': 0, '>': 120, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2715375', 'n_tokens_mistral': 2713, 'n_tokens_neox': 2620, 'n_words': 470}