src/surreal_query.erl

%%% @doc Query builder for Surreal.
-module(surreal_query).

-export([make_one/1, make_all/1]).

-type string_format() :: binary() | atom() | bitstring().
-type query() ::
    {block, Inside :: list(query())}
    | {select}
    | {select, Field :: string_format()}
    | {from, Target :: string_format()}
    | {from, Targets :: list(string_format())}
    | {where, {Operator :: string_format(), Left :: string_format(), Right :: term()}}
    | {where, {Left :: string_format(), Right :: term()}}
    | {where,
        Conditions :: list(
            {Operator :: string_format(), Left :: string_format(), Right :: term()}
            | {Left :: string_format(), Right :: term()}
        )}
    | {create, Something :: string_format()}
    | {set, {Key :: string_format(), Value :: term()}}
    | {set, list({Key :: string_format(), Value :: term()})}
    | {var, {Key :: string_format(), Value :: term()}}
    | {sleep, Millis :: integer()}
    | {delete, Something :: string_format()}
    | {update, Something :: string_format()}
    | {use, {namespace, Namespace :: string_format()}}
    | {use, {database, Database :: string_format()}}
    | {use, {Namespace :: string_format(), Database :: string_format()}}.

%% Generate a query string from list of queries (list of operations).
-spec make_all(Queries :: list(list(query()))) -> string().
make_all(Queries) ->
    Results = lists:map(fun make_one/1, Queries),
    WithFormatted = lists:join("\n", Results),

    lists:flatten(WithFormatted).

%% Generate a query string from list of operations.
-spec make_one(Query :: list(query())) -> string().
make_one(Query) ->
    Results = lists:map(fun make_one_command/1, Query),
    WithFormatted = lists:join(" ", Results) ++ ";",

    lists:flatten(WithFormatted).

%% @private
%% BLOCK Builders
make_one_command({block, Statements}) when is_list(Statements) ->
    Results = lists:map(fun make_one_command/1, Statements),
    WithFormatted = lists:join(" ", Results),

    io_lib:format("(~s)", [WithFormatted]);
%% SELECT Builders
make_one_command({select}) ->
    "SELECT *";
make_one_command({select, Field}) when is_binary(Field); is_atom(Field); is_bitstring(Field) ->
    io_lib:format("SELECT ~s", [Field]);
make_one_command({select, Fields}) when is_list(Fields) ->
    ToString = lists:map(fun atom_to_list/1, Fields),
    WithFormatted = lists:flatten(lists:join(", ", ToString)),

    io_lib:format("SELECT ~s", [WithFormatted]);
%% FROM Builders
make_one_command({from, Target}) when is_binary(Target); is_atom(Target); is_bitstring(Target) ->
    io_lib:format("FROM ~s", [Target]);
make_one_command({from, Targets}) when is_list(Targets) ->
    ToString = lists:map(fun atom_to_list/1, Targets),
    WithFormatted = lists:flatten(lists:join(", ", ToString)),

    io_lib:format("FROM ~s", [WithFormatted]);
%% WHERE Builders
make_one_command({where, {Operator, Left, Right}}) ->
    io_lib:format("WHERE ~s ~s ~p", [Left, Operator, Right]);
make_one_command({where, {Left, Right}}) ->
    io_lib:format("WHERE ~s = ~p", [Left, Right]);
make_one_command({where, Conditions}) when is_list(Conditions) ->
    NewConditions = lists:map(fun(Condition) -> {where, Condition} end, Conditions),
    WithConcat = lists:map(
        fun(Condition) ->
            "WHERE " ++ Actual = make_one_command(Condition),
            Actual
        end,

        NewConditions
    ),
    WithFormatted = lists:flatten(lists:join(", ", WithConcat)),

    io_lib:format("WHERE ~s", [WithFormatted]);
%% CREATE Builders
make_one_command({create, Something}) ->
    io_lib:format("CREATE ~s", [Something]);
%% SET Builders
make_one_command({set, {Key, Value}}) ->
    io_lib:format("SET ~s = ~p", [Key, Value]);
make_one_command({set, AllKv}) when is_list(AllKv) ->
    NewKv = lists:map(fun(Kv) -> {set, Kv} end, AllKv),
    WithConcat = lists:map(
        fun(Condition) ->
            "SET " ++ Actual = make_one_command(Condition),
            Actual
        end,

        NewKv
    ),
    WithFormatted = lists:flatten(lists:join(", ", WithConcat)),

    io_lib:format("SET ~s", [WithFormatted]);
%% LET Builders
make_one_command({var, {Key, Value}}) ->
    io_lib:format("LET $~s = ~p", [Key, Value]);
%% SLEEP Builders
make_one_command({sleep, MsDuration}) ->
    io_lib:format("SLEEP ~pms", [MsDuration]);
%% DELETE Builders
make_one_command({delete, Something}) ->
    io_lib:format("DELETE ~s", [Something]);
%% UPDATE Builders
make_one_command({update, Something}) ->
    io_lib:format("UPDATE ~s", [Something]);
%% USE Builders
make_one_command({use, {namespace, Namespace}}) ->
    io_lib:format("USE NS ~s", [Namespace]);
make_one_command({use, {database, Database}}) ->
    io_lib:format("USE DB ~s", [Database]);
make_one_command({use, {Namespace, Database}}) ->
    io_lib:format("USE NS ~s DB ~s", [Namespace, Database]);
make_one_command(_Other) ->
    "".