Current section
Files
Jump to
Current section
Files
src/gabsurd@client.erl
-module(gabsurd@client).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gabsurd/client.gleam").
-export([unique_integer/0, start/1, start_with/5, connection/1, query_one/2, query_many/2, exec/2]).
-export_type([db/0, gabsurd_error/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.
?MODULEDOC(
" Database client for gabsurd — wraps pog connection and provides\n"
" helpers for executing parrot-generated queries against PostgreSQL.\n"
).
-type db() :: {db, gleam@erlang@process:pid_(), pog:connection()}.
-type gabsurd_error() :: {query_error, binary()} |
{unexpected_row_count, binary()} |
not_found |
{connection_error, binary()}.
-file("src/gabsurd/client.gleam", 34).
?DOC(" Generate a unique integer for pool names.\n").
-spec unique_integer() -> integer().
unique_integer() ->
erlang:unique_integer().
-file("src/gabsurd/client.gleam", 39).
?DOC(
" Start a database connection from a DATABASE_URL string.\n"
" Uses a unique pool name to avoid collisions when multiple\n"
" connections are started (e.g., in tests).\n"
).
-spec start(binary()) -> {ok, gleam@otp@actor:started(db())} |
{error, gleam@otp@actor:start_error()}.
start(Url) ->
Id = erlang:unique_integer(),
Name = gleam_erlang_ffi:new_name(
<<"gabsurd_"/utf8, (erlang:integer_to_binary(Id))/binary>>
),
Config_result = pog:url_config(Name, Url),
case Config_result of
{error, _} ->
{error, {init_failed, <<"invalid DATABASE_URL"/utf8>>}};
{ok, Config} ->
Config@1 = begin
_pipe = Config,
pog:pool_size(_pipe, 2)
end,
case pog:start(Config@1) of
{ok, Started} ->
{ok,
{started,
erlang:element(2, Started),
{db,
erlang:element(2, Started),
erlang:element(3, Started)}}};
{error, Reason} ->
{error, Reason}
end
end.
-file("src/gabsurd/client.gleam", 60).
?DOC(" Start a connection from individual parts (useful for testing).\n").
-spec start_with(binary(), integer(), binary(), binary(), binary()) -> {ok,
gleam@otp@actor:started(db())} |
{error, gleam@otp@actor:start_error()}.
start_with(Host, Port, Database, User, Password) ->
Id = erlang:unique_integer(),
Name = gleam_erlang_ffi:new_name(
<<"gabsurd_"/utf8, (erlang:integer_to_binary(Id))/binary>>
),
Config = begin
_pipe = pog:default_config(Name),
_pipe@1 = pog:host(_pipe, Host),
_pipe@2 = pog:port(_pipe@1, Port),
_pipe@3 = pog:database(_pipe@2, Database),
_pipe@4 = pog:user(_pipe@3, User),
_pipe@5 = pog:password(_pipe@4, {some, Password}),
_pipe@6 = pog:ssl(_pipe@5, ssl_disabled),
pog:pool_size(_pipe@6, 2)
end,
case pog:start(Config) of
{ok, Started} ->
{ok,
{started,
erlang:element(2, Started),
{db, erlang:element(2, Started), erlang:element(3, Started)}}};
{error, Reason} ->
{error, Reason}
end.
-file("src/gabsurd/client.gleam", 90).
?DOC(" Get the underlying pog connection.\n").
-spec connection(db()) -> pog:connection().
connection(Db) ->
erlang:element(3, Db).
-file("src/gabsurd/client.gleam", 164).
-spec query_error_to_gabsurd(pog:query_error()) -> gabsurd_error().
query_error_to_gabsurd(Error) ->
case Error of
{constraint_violated, Message, _, _} ->
{query_error, Message};
{postgresql_error, Code, _, Message@1} ->
{query_error,
<<<<Code/binary, ": "/utf8>>/binary, Message@1/binary>>};
{unexpected_argument_count, Expected, Got} ->
{query_error,
<<<<<<"unexpected argument count: expected "/utf8,
(erlang:integer_to_binary(Expected))/binary>>/binary,
" got "/utf8>>/binary,
(erlang:integer_to_binary(Got))/binary>>};
{unexpected_argument_type, Expected@1, Got@1} ->
{query_error,
<<<<<<"unexpected argument type: expected "/utf8,
Expected@1/binary>>/binary,
" got "/utf8>>/binary,
Got@1/binary>>};
{unexpected_result_type, Errors} ->
{query_error,
<<"decode error: "/utf8,
(gleam@string:join(
gleam@list:map(
Errors,
fun(E) ->
<<<<(erlang:element(2, E))/binary,
" got "/utf8>>/binary,
(erlang:element(3, E))/binary>>
end
),
<<", "/utf8>>
))/binary>>};
query_timeout ->
{query_error, <<"query timeout"/utf8>>};
connection_unavailable ->
{connection_error, <<"no connection available"/utf8>>}
end.
-file("src/gabsurd/client.gleam", 160).
-spec add_params(pog:'query'(NGH), list(parrot@dev:param())) -> pog:'query'(NGH).
add_params(Query, Params) ->
gleam@list:fold(
Params,
Query,
fun(Acc, P) -> pog:parameter(Acc, gabsurd@param:to_pog(P)) end
).
-file("src/gabsurd/client.gleam", 95).
?DOC(" Execute a `:one` query — returns a single row of type `a`, or Error if no row.\n").
-spec query_one(
db(),
{binary(), list(parrot@dev:param()), gleam@dynamic@decode:decoder(NFU)}
) -> {ok, NFU} | {error, gabsurd_error()}.
query_one(Db, Sql_params_decoder) ->
{Sql, Params, Decoder} = Sql_params_decoder,
Result = begin
_pipe = Sql,
_pipe@1 = pog:'query'(_pipe),
_pipe@2 = pog:returning(_pipe@1, Decoder),
_pipe@3 = add_params(_pipe@2, Params),
pog:execute(_pipe@3, erlang:element(3, Db))
end,
case Result of
{ok, {returned, 1, [Row]}} ->
{ok, Row};
{ok, {returned, 0, _}} ->
{error, not_found};
{ok, {returned, N, _}} ->
{error,
{unexpected_row_count,
<<"expected 1 row, got "/utf8,
(erlang:integer_to_binary(N))/binary>>}};
{error, Error} ->
{error, query_error_to_gabsurd(Error)}
end.
-file("src/gabsurd/client.gleam", 120).
?DOC(" Execute a `:many` query — returns a list of rows of type `a`.\n").
-spec query_many(
db(),
{binary(), list(parrot@dev:param()), gleam@dynamic@decode:decoder(NFZ)}
) -> {ok, list(NFZ)} | {error, gabsurd_error()}.
query_many(Db, Sql_params_decoder) ->
{Sql, Params, Decoder} = Sql_params_decoder,
Result = begin
_pipe = Sql,
_pipe@1 = pog:'query'(_pipe),
_pipe@2 = pog:returning(_pipe@1, Decoder),
_pipe@3 = add_params(_pipe@2, Params),
pog:execute(_pipe@3, erlang:element(3, Db))
end,
case Result of
{ok, {returned, _, Rows}} ->
{ok, Rows};
{error, Error} ->
{error, query_error_to_gabsurd(Error)}
end.
-file("src/gabsurd/client.gleam", 141).
?DOC(
" Execute a `:exec` query — returns Nil on success.\n"
" Void-returning functions are cast to ::text in SQL so pgo can decode them.\n"
).
-spec exec(db(), {binary(), list(parrot@dev:param())}) -> {ok, nil} |
{error, gabsurd_error()}.
exec(Db, Sql_and_params) ->
{Sql, Params} = Sql_and_params,
Result = begin
_pipe = Sql,
_pipe@1 = pog:'query'(_pipe),
_pipe@2 = pog:returning(
_pipe@1,
gleam@dynamic@decode:at(
[0],
gleam@dynamic@decode:optional(
{decoder, fun gleam@dynamic@decode:decode_string/1}
)
)
),
_pipe@3 = add_params(_pipe@2, Params),
pog:execute(_pipe@3, erlang:element(3, Db))
end,
case Result of
{ok, _} ->
{ok, nil};
{error, Error} ->
{error, query_error_to_gabsurd(Error)}
end.