src/rally_runtime@db.erl

-module(rally_runtime@db).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally_runtime/db.gleam").
-export([open/1, one/1, bool_to_int/1, nullable_text/1, collapse_whitespace/1, 'query'/4, transaction/2, get_timing/0, init_timing/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

-file("src/rally_runtime/db.gleam", 15).
-spec open(binary()) -> {ok, sqlight:connection()} | {error, sqlight:error()}.
open(Path) ->
    gleam@result:'try'(
        sqlight:open(Path),
        fun(Conn) ->
            gleam@result:'try'(
                sqlight:exec(<<"PRAGMA journal_mode=WAL;"/utf8>>, Conn),
                fun(_) ->
                    gleam@result:'try'(
                        sqlight:exec(<<"PRAGMA busy_timeout=5000;"/utf8>>, Conn),
                        fun(_) ->
                            gleam@result:'try'(
                                sqlight:exec(
                                    <<"PRAGMA foreign_keys=ON;"/utf8>>,
                                    Conn
                                ),
                                fun(_) -> {ok, Conn} end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/rally_runtime/db.gleam", 23).
-spec one(list(AKXW)) -> gleam@option:option(AKXW).
one(Rows) ->
    case Rows of
        [Row] ->
            {some, Row};

        _ ->
            none
    end.

-file("src/rally_runtime/db.gleam", 30).
-spec bool_to_int(boolean()) -> sqlight:value().
bool_to_int(Val) ->
    sqlight:int(case Val of
            true ->
                1;

            false ->
                0
        end).

-file("src/rally_runtime/db.gleam", 37).
-spec nullable_text(gleam@option:option(binary())) -> sqlight:value().
nullable_text(Val) ->
    case Val of
        {some, S} ->
            sqlight:text(S);

        none ->
            sqlight_ffi:null()
    end.

-file("src/rally_runtime/db.gleam", 114).
-spec log_result({ok, list(any())} | {error, sqlight:error()}) -> nil.
log_result(Result) ->
    case Result of
        {ok, Rows} ->
            logging:log(
                debug,
                <<<<"→ "/utf8,
                        (erlang:integer_to_binary(erlang:length(Rows)))/binary>>/binary,
                    " row(s)"/utf8>>
            );

        {error, Err} ->
            logging:log(
                warning,
                <<"→ DB ERROR: "/utf8, (erlang:element(3, Err))/binary>>
            )
    end.

-file("src/rally_runtime/db.gleam", 133).
-spec do_collapse_whitespace(binary()) -> binary().
do_collapse_whitespace(Sql) ->
    case gleam_stdlib:contains_string(Sql, <<"  "/utf8>>) of
        true ->
            do_collapse_whitespace(
                gleam@string:replace(Sql, <<"  "/utf8>>, <<" "/utf8>>)
            );

        _ ->
            Sql
    end.

-file("src/rally_runtime/db.gleam", 125).
-spec collapse_whitespace(binary()) -> binary().
collapse_whitespace(Sql) ->
    _pipe = Sql,
    _pipe@1 = gleam@string:replace(_pipe, <<"\n"/utf8>>, <<" "/utf8>>),
    _pipe@2 = gleam@string:replace(_pipe@1, <<"\t"/utf8>>, <<" "/utf8>>),
    _pipe@3 = do_collapse_whitespace(_pipe@2),
    gleam@string:trim(_pipe@3).

-file("src/rally_runtime/db.gleam", 99).
-spec log_query(binary(), integer(), integer()) -> nil.
log_query(Sql, Param_count, Elapsed_ms) ->
    Msg = <<<<<<<<<<(collapse_whitespace(Sql))/binary, " | params: "/utf8>>/binary,
                    (erlang:integer_to_binary(Param_count))/binary>>/binary,
                " ("/utf8>>/binary,
            (erlang:integer_to_binary(Elapsed_ms))/binary>>/binary,
        "ms)"/utf8>>,
    logging:log(debug, Msg).

-file("src/rally_runtime/db.gleam", 47).
?DOC(
    " Timed query wrapper. Same signature as sqlight.query but adds debug\n"
    " logging with query text, param count, elapsed time, and row count.\n"
    " Accumulates timing in the process dictionary for per-request totals.\n"
).
-spec 'query'(
    binary(),
    sqlight:connection(),
    list(sqlight:value()),
    gleam@dynamic@decode:decoder(AKYB)
) -> {ok, list(AKYB)} | {error, sqlight:error()}.
'query'(Sql, Conn, Params, Decoder) ->
    Start = gleam@time@timestamp:system_time(),
    Result = sqlight:'query'(Sql, Conn, Params, Decoder),
    Elapsed_ms = begin
        _pipe = gleam@time@timestamp:difference(
            Start,
            gleam@time@timestamp:system_time()
        ),
        gleam@time@duration:to_milliseconds(_pipe)
    end,
    rally_runtime_db_ffi:add_db_timing(Elapsed_ms),
    log_query(Sql, erlang:length(Params), Elapsed_ms),
    log_result(Result),
    Result.

-file("src/rally_runtime/db.gleam", 66).
-spec transaction(
    sqlight:connection(),
    fun(() -> {ok, AKYG} | {error, sqlight:error()})
) -> {ok, AKYG} | {error, sqlight:error()}.
transaction(Conn, Body) ->
    Id = gleam@int:absolute_value(erlang:unique_integer()),
    Savepoint = <<"sp_"/utf8, (erlang:integer_to_binary(Id))/binary>>,
    gleam@result:'try'(
        sqlight:exec(
            <<<<"SAVEPOINT "/utf8, Savepoint/binary>>/binary, ";"/utf8>>,
            Conn
        ),
        fun(_) -> case Body() of
                {ok, Val} ->
                    gleam@result:'try'(
                        sqlight:exec(
                            <<<<"RELEASE "/utf8, Savepoint/binary>>/binary,
                                ";"/utf8>>,
                            Conn
                        ),
                        fun(_) -> {ok, Val} end
                    );

                {error, Err} ->
                    _ = sqlight:exec(
                        <<<<"ROLLBACK TO "/utf8, Savepoint/binary>>/binary,
                            ";"/utf8>>,
                        Conn
                    ),
                    gleam@result:'try'(
                        sqlight:exec(
                            <<<<"RELEASE "/utf8, Savepoint/binary>>/binary,
                                ";"/utf8>>,
                            Conn
                        ),
                        fun(_) -> {error, Err} end
                    )
            end end
    ).

-file("src/rally_runtime/db.gleam", 88).
?DOC(
    " Get accumulated DB timing for the current request.\n"
    " Returns #(total_milliseconds, query_count).\n"
).
-spec get_timing() -> {integer(), integer()}.
get_timing() ->
    rally_runtime_db_ffi:get_db_timing().

-file("src/rally_runtime/db.gleam", 93).
?DOC(" Reset accumulated DB timing. Call at the start of each request/message.\n").
-spec init_timing() -> nil.
init_timing() ->
    rally_runtime_db_ffi:init_db_timing().