-module(queryb). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([to_sql/3, to_postgresql/1]). -export_type([query_error/0, 'query'/0, value/0, query_value/0, condition/0, case_sensitivity/0, direction/0]). -type query_error() :: {constraint_violation, binary(), binary()} | {database_error, binary()}. -type 'query'() :: {select, binary(), list({gleam@option:option(binary()), binary()}), {binary(), direction()}, integer(), integer(), list(condition())} | {update, binary(), list({binary(), query_value()}), list(condition())} | {delete, binary(), list(condition())}. -type value() :: {text_value, binary()} | {int_value, integer()} | {float_value, float()} | null_value. -type query_value() :: {relation_value, gleam@option:option(binary()), binary()} | {parameter, integer()}. -type condition() :: {equal, query_value(), query_value()} | {string_contains, query_value(), query_value(), case_sensitivity()} | {'or', list(condition())}. -type case_sensitivity() :: case_sensitive | case_insensitive. -type direction() :: ascending | descending. -spec escape_name(binary(), binary()) -> binary(). escape_name(Name, Quote) -> <<<<"\""/utf8, (gleam@string:replace(Name, <<"\""/utf8>>, Quote))/binary>>/binary, "\""/utf8>>. -spec value_to_sql(binary(), query_value(), binary(), binary()) -> binary(). value_to_sql(Query, Value, Param, Quote) -> case Value of {relation_value, none, Column} -> <>; {relation_value, {some, Table}, Column@1} -> <<<<<>/binary, "."/utf8>>/binary, (escape_name(Column@1, Quote))/binary>>; {parameter, Index} -> <<<>/binary, (gleam@int:to_string(Index))/binary>> end. -spec updates_to_sql( binary(), list({binary(), query_value()}), boolean(), binary(), binary() ) -> binary(). updates_to_sql(Query, Updates, First, Param, Quote) -> case Updates of [] -> Query; [{Name, Value} | Updates@1] -> Query@1 = case First of true -> <>; false -> <> end, Query@2 = <<<>/binary, " = "/utf8>>, Query@3 = value_to_sql(Query@2, Value, Param, Quote), updates_to_sql(Query@3, Updates@1, false, Param, Quote) end. -spec condition_to_sql(binary(), condition(), binary(), binary()) -> binary(). condition_to_sql(Query, Condition, Param, Quote) -> case Condition of {'or', Conditions} -> gleam@list:index_fold( Conditions, Query, fun(Query@1, Condition@1, I) -> Query@2 = case I of 0 -> Query@1; _ -> <> end, <<(condition_to_sql( <>, Condition@1, Param, Quote ))/binary, ")"/utf8>> end ); {equal, Left, Right} -> Query@3 = <<(value_to_sql(Query, Left, Param, Quote))/binary, " = "/utf8>>, value_to_sql(Query@3, Right, Param, Quote); {string_contains, Left@1, Right@1, Sensitivity} -> Query@4 = value_to_sql(Query, Left@1, Param, Quote), Query@5 = case Sensitivity of case_sensitive -> <>; case_insensitive -> <> end, <<(value_to_sql(Query@5, Right@1, Param, Quote))/binary, "||'%'"/utf8>> end. -spec conditions_to_sql( binary(), list(condition()), boolean(), binary(), binary() ) -> binary(). conditions_to_sql(Query, Conditions, First, Param, Quote) -> case Conditions of [] -> Query; [Condition | Conditions@1] -> Query@1 = case First of true -> <>; false -> <> end, Query@2 = condition_to_sql(Query@1, Condition, Param, Quote), conditions_to_sql(Query@2, Conditions@1, false, Param, Quote) end. -spec select_column(binary(), gleam@option:option(binary()), binary(), binary()) -> binary(). select_column(Query, Table, Column, Quote) -> case Table of none -> <>; {some, Table@1} -> <<<<<>/binary, "."/utf8>>/binary, (escape_name(Column, Quote))/binary>> end. -spec select_columns( binary(), list({gleam@option:option(binary()), binary()}), boolean(), binary() ) -> binary(). select_columns(Query, Names, First, Quote) -> case Names of [] -> Query; [{Table, Column} | Names@1] when First -> Query@1 = select_column(Query, Table, Column, Quote), select_columns(Query@1, Names@1, false, Quote); [{Table@1, Column@1} | Names@2] -> Query@2 = select_column( <>, Table@1, Column@1, Quote ), select_columns(Query@2, Names@2, false, Quote) end. -spec to_sql('query'(), binary(), binary()) -> binary(). to_sql(Query, Param, Quote) -> case Query of {delete, Table, Conditions} -> Query@1 = <<"delete from "/utf8, (escape_name(Table, Quote))/binary>>, conditions_to_sql(Query@1, Conditions, true, Param, Quote); {update, Table@1, Updates, Conditions@1} -> Query@2 = <<"update "/utf8, (escape_name(Table@1, Quote))/binary>>, Query@3 = updates_to_sql(Query@2, Updates, true, Param, Quote), conditions_to_sql(Query@3, Conditions@1, true, Param, Quote); {select, Table@2, Columns, {Order_by_column, Direction}, Limit, Offset, Conditions@2} -> Query@4 = select_columns(<<"select "/utf8>>, Columns, true, Quote), Query@5 = <<<>/binary, (escape_name(Table@2, Quote))/binary>>, Query@6 = conditions_to_sql( Query@5, Conditions@2, true, Param, Quote ), Query@7 = <<<>/binary, (escape_name(Order_by_column, Quote))/binary>>, Query@8 = case Direction of ascending -> <>; descending -> <> end, Query@9 = <<<>/binary, (gleam@int:to_string(Limit))/binary>>, case Offset of 0 -> Query@9; _ -> <<<>/binary, (gleam@int:to_string(Offset))/binary>> end end. -spec to_postgresql('query'()) -> binary(). to_postgresql(Query) -> to_sql(Query, <<"$"/utf8>>, <<"\""/utf8>>).