Current section
Files
Jump to
Current section
Files
src/ducky.erl
-module(ducky).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ducky.gleam").
-export([path/1, field/2, get/2, connect/1, close/1, with_connection/2, transaction/2, 'query'/2, query_params/3]).
-export_type([error/0, value/0, row/0, data_frame/0, connection/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(
" Native DuckDB driver for Gleam.\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import ducky\n"
" import gleam/int\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" use conn <- ducky.with_connection(\":memory:\")\n"
"\n"
" // Create our duck pond\n"
" let assert Ok(_) = ducky.query(conn, \"\n"
" CREATE TABLE ducks (name TEXT, quack_volume INT, is_rubber BOOLEAN)\n"
" \")\n"
" let assert Ok(_) = ducky.query(conn, \"\n"
" INSERT INTO ducks VALUES\n"
" ('Sir Quacksalot', 95, false),\n"
" ('Duck Norris', 100, false),\n"
" ('Mallard Fillmore', 72, false),\n"
" ('Squeaky', 0, true)\n"
" \")\n"
"\n"
" // Find the loudest quacker\n"
" let assert Ok(result) = ducky.query(conn, \"\n"
" SELECT name, quack_volume FROM ducks\n"
" WHERE is_rubber = false\n"
" ORDER BY quack_volume DESC LIMIT 1\n"
" \")\n"
"\n"
" case result.rows {\n"
" [ducky.Row([ducky.Text(name), ducky.Integer(volume)])] ->\n"
" io.println(name <> \" wins at \" <> int.to_string(volume) <> \" decibels!\")\n"
" _ -> io.println(\"The pond is empty...\")\n"
" }\n"
" }\n"
" // => Duck Norris wins at 100 decibels!\n"
" ```\n"
).
-type error() :: {connection_failed, binary()} |
{query_syntax_error, binary()} |
{timeout, integer()} |
{type_mismatch, binary(), binary()} |
{unsupported_parameter_type, binary()} |
{database_error, binary()}.
-type value() :: null |
{boolean, boolean()} |
{tiny_int, integer()} |
{small_int, integer()} |
{integer, integer()} |
{big_int, integer()} |
{float, float()} |
{double, float()} |
{decimal, binary()} |
{text, binary()} |
{blob, bitstring()} |
{timestamp, integer()} |
{date, integer()} |
{time, integer()} |
{interval, integer(), integer(), integer()} |
{list, list(value())} |
{array, list(value())} |
{map, gleam@dict:dict(binary(), value())} |
{struct, gleam@dict:dict(binary(), value())}.
-type row() :: {row, list(value())}.
-type data_frame() :: {data_frame, list(binary()), list(row())}.
-opaque connection() :: {connection, ducky@internal@connection:connection()}.
-file("src/ducky.gleam", 143).
?DOC(" Returns the database path for a connection.\n").
-spec path(connection()) -> binary().
path(Conn) ->
ducky@internal@connection:path(erlang:element(2, Conn)).
-file("src/ducky.gleam", 290).
?DOC(
" Get a field value from a struct by field name.\n"
"\n"
" Returns None if the value is not a Struct or the field does not exist.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let person = Struct(dict.from_list([#(\"name\", Text(\"Alice\")), #(\"age\", Integer(30))]))\n"
" field(person, \"name\")\n"
" // => Some(Text(\"Alice\"))\n"
"\n"
" field(person, \"unknown\")\n"
" // => None\n"
" ```\n"
).
-spec field(value(), binary()) -> gleam@option:option(value()).
field(Value, Name) ->
case Value of
{struct, Fields} ->
_pipe = gleam_stdlib:map_get(Fields, Name),
gleam@option:from_result(_pipe);
_ ->
none
end.
-file("src/ducky.gleam", 297).
-spec list_at(list(DSQ), integer()) -> gleam@option:option(DSQ).
list_at(Items, Index) ->
case {Items, Index} of
{[], _} ->
none;
{[First | _], 0} ->
{some, First};
{[_ | Rest], N} when N > 0 ->
list_at(Rest, N - 1);
{_, _} ->
none
end.
-file("src/ducky.gleam", 270).
?DOC(
" Get a value from a row by column index.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let row = Row([Integer(1), Text(\"Alice\")])\n"
" get(row, 0)\n"
" // => Some(Integer(1))\n"
"\n"
" get(row, 5)\n"
" // => None\n"
" ```\n"
).
-spec get(row(), integer()) -> gleam@option:option(value()).
get(Row, Index) ->
case Row of
{row, Values} ->
list_at(Values, Index)
end.
-file("src/ducky.gleam", 307).
?DOC(" Creates a tagged tuple {tag, value} for NIF parameter encoding.\n").
-spec make_tagged(binary(), gleam@dynamic:dynamic_()) -> gleam@dynamic:dynamic_().
make_tagged(Tag, Value) ->
erlang:list_to_tuple([erlang:binary_to_atom(Tag), Value]).
-file("src/ducky.gleam", 312).
?DOC(" Creates an interval 4-tuple {interval, months, days, nanos}.\n").
-spec make_interval_tuple(integer(), integer(), integer()) -> gleam@dynamic:dynamic_().
make_interval_tuple(Months, Days, Nanos) ->
erlang:list_to_tuple(
[erlang:binary_to_atom(<<"interval"/utf8>>),
gleam_stdlib:identity(Months),
gleam_stdlib:identity(Days),
gleam_stdlib:identity(Nanos)]
).
-file("src/ducky.gleam", 322).
?DOC(" Converts a Value to a Dynamic for passing to the NIF.\n").
-spec value_to_dynamic(value()) -> {ok, gleam@dynamic:dynamic_()} |
{error, error()}.
value_to_dynamic(Value) ->
case Value of
null ->
{ok, gleam@dynamic:nil()};
{boolean, B} ->
{ok, gleam_stdlib:identity(B)};
{tiny_int, I} ->
{ok, gleam_stdlib:identity(I)};
{small_int, I@1} ->
{ok, gleam_stdlib:identity(I@1)};
{integer, I@2} ->
{ok, gleam_stdlib:identity(I@2)};
{big_int, I@3} ->
{ok, gleam_stdlib:identity(I@3)};
{float, F} ->
{ok, gleam_stdlib:identity(F)};
{double, F@1} ->
{ok, gleam_stdlib:identity(F@1)};
{text, S} ->
{ok, gleam_stdlib:identity(S)};
{blob, Bits} ->
{ok, gleam_stdlib:identity(Bits)};
{timestamp, Micros} ->
{ok,
make_tagged(<<"timestamp"/utf8>>, gleam_stdlib:identity(Micros))};
{date, Days} ->
{ok, make_tagged(<<"date"/utf8>>, gleam_stdlib:identity(Days))};
{time, Micros@1} ->
{ok, make_tagged(<<"time"/utf8>>, gleam_stdlib:identity(Micros@1))};
{interval, Months, Days@1, Nanos} ->
{ok, make_interval_tuple(Months, Days@1, Nanos)};
{decimal, S@1} ->
{ok, make_tagged(<<"decimal"/utf8>>, gleam_stdlib:identity(S@1))};
{list, _} ->
{error,
{unsupported_parameter_type,
<<"List, Array, Map, and Struct types cannot be used as query parameters"/utf8>>}};
{array, _} ->
{error,
{unsupported_parameter_type,
<<"List, Array, Map, and Struct types cannot be used as query parameters"/utf8>>}};
{map, _} ->
{error,
{unsupported_parameter_type,
<<"List, Array, Map, and Struct types cannot be used as query parameters"/utf8>>}};
{struct, _} ->
{error,
{unsupported_parameter_type,
<<"List, Array, Map, and Struct types cannot be used as query parameters"/utf8>>}}
end.
-file("src/ducky.gleam", 446).
?DOC(" Decodes a decimal tagged tuple {decimal, \"string\"}.\n").
-spec decode_decimal_value(gleam@dynamic:dynamic_()) -> value().
decode_decimal_value(Dyn) ->
Decoder = begin
gleam@dynamic@decode:subfield(
[1],
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Value) -> gleam@dynamic@decode:success(Value) end
)
end,
case gleam@dynamic@decode:run(Dyn, Decoder) of
{ok, Value@1} ->
{decimal, Value@1};
{error, _} ->
null
end.
-file("src/ducky.gleam", 498).
?DOC(" Decodes temporal tagged tuples (timestamp, date, time).\n").
-spec decode_temporal_tuple(gleam@dynamic:dynamic_()) -> value().
decode_temporal_tuple(Dyn) ->
Decoder = begin
gleam@dynamic@decode:subfield(
[0],
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Tag_dynamic) ->
gleam@dynamic@decode:subfield(
[1],
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Value) ->
Tag = case gleam_stdlib:classify_dynamic(Tag_dynamic) of
<<"Atom"/utf8>> ->
erlang:atom_to_binary(Tag_dynamic);
<<"String"/utf8>> ->
_pipe = gleam@dynamic@decode:run(
Tag_dynamic,
{decoder,
fun gleam@dynamic@decode:decode_string/1}
),
gleam@result:unwrap(_pipe, <<""/utf8>>);
_ ->
<<""/utf8>>
end,
gleam@dynamic@decode:success({Tag, Value})
end
)
end
)
end,
case gleam@dynamic@decode:run(Dyn, Decoder) of
{ok, {Tag@1, Value@1}} ->
case Tag@1 of
<<"timestamp"/utf8>> ->
{timestamp, Value@1};
<<"date"/utf8>> ->
{date, Value@1};
<<"time"/utf8>> ->
{time, Value@1};
_ ->
null
end;
{error, _} ->
null
end.
-file("src/ducky.gleam", 527).
?DOC(" Decodes interval 4-tuple {interval, months, days, nanos}.\n").
-spec decode_interval_tuple(gleam@dynamic:dynamic_()) -> value().
decode_interval_tuple(Dyn) ->
Decoder = begin
gleam@dynamic@decode:subfield(
[1],
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Months) ->
gleam@dynamic@decode:subfield(
[2],
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Days) ->
gleam@dynamic@decode:subfield(
[3],
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Nanos) ->
gleam@dynamic@decode:success(
{Months, Days, Nanos}
)
end
)
end
)
end
)
end,
case gleam@dynamic@decode:run(Dyn, Decoder) of
{ok, {Months@1, Days@1, Nanos@1}} ->
{interval, Months@1, Days@1, Nanos@1};
{error, _} ->
null
end.
-file("src/ducky.gleam", 541).
-spec error_from_tag(binary(), binary()) -> error().
error_from_tag(Tag, Message) ->
case Tag of
<<"connection_failed"/utf8>> ->
{connection_failed, Message};
<<"query_syntax_error"/utf8>> ->
{query_syntax_error, Message};
<<"unsupported_parameter_type"/utf8>> ->
{unsupported_parameter_type, Message};
<<"database_error"/utf8>> ->
{database_error, Message};
_ ->
{database_error,
<<<<<<"["/utf8, Tag/binary>>/binary, "] "/utf8>>/binary,
Message/binary>>}
end.
-file("src/ducky.gleam", 551).
-spec error_tuple_decoder() -> gleam@dynamic@decode:decoder({binary(), binary()}).
error_tuple_decoder() ->
gleam@dynamic@decode:subfield(
[0],
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Error_type_dyn) ->
gleam@dynamic@decode:subfield(
[1],
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Message) ->
Error_type = case gleam_stdlib:classify_dynamic(
Error_type_dyn
) of
<<"Atom"/utf8>> ->
erlang:atom_to_binary(Error_type_dyn);
_ ->
<<"unknown"/utf8>>
end,
gleam@dynamic@decode:success({Error_type, Message})
end
)
end
).
-file("src/ducky.gleam", 574).
?DOC(" Fallback decoder for unexpected error formats.\n").
-spec fallback_decode(gleam@dynamic:dynamic_()) -> error().
fallback_decode(Err) ->
case gleam@dynamic@decode:run(Err, error_tuple_decoder()) of
{ok, {Tag, Msg}} ->
error_from_tag(Tag, Msg);
{error, _} ->
{database_error,
<<"Unknown error: "/utf8, (gleam@string:inspect(Err))/binary>>}
end.
-file("src/ducky.gleam", 564).
?DOC(" Decodes an error from the NIF layer.\n").
-spec decode_nif_error(gleam@dynamic:dynamic_()) -> error().
decode_nif_error(Err) ->
Decoder = gleam@dynamic@decode:at([1], error_tuple_decoder()),
case gleam@dynamic@decode:run(Err, Decoder) of
{ok, {Tag, Msg}} ->
error_from_tag(Tag, Msg);
{error, _} ->
fallback_decode(Err)
end.
-file("src/ducky.gleam", 119).
?DOC(
" Opens a connection to a DuckDB database.\n"
"\n"
" Must call `close()` when done. Use `with_connection()` instead\n"
" for automatic cleanup.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" connect(\":memory:\")\n"
" // => Ok(Connection(...))\n"
"\n"
" connect(\"data.duckdb\")\n"
" // => Ok(Connection(...))\n"
" ```\n"
).
-spec connect(binary()) -> {ok, connection()} | {error, error()}.
connect(Path) ->
case Path of
<<""/utf8>> ->
{error, {connection_failed, <<"path cannot be empty"/utf8>>}};
_ ->
_pipe = ducky@internal@connection:do_connect(Path),
_pipe@1 = gleam@result:map(
_pipe,
fun(Internal) -> {connection, Internal} end
),
gleam@result:map_error(_pipe@1, fun decode_nif_error/1)
end.
-file("src/ducky.gleam", 137).
?DOC(
" Closes a database connection.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(conn) = connect(\":memory:\")\n"
" let assert Ok(_) = close(conn)\n"
" ```\n"
).
-spec close(connection()) -> {ok, nil} | {error, error()}.
close(Conn) ->
_pipe = ducky@internal@connection:do_close(erlang:element(2, Conn)),
gleam@result:map_error(_pipe, fun decode_nif_error/1).
-file("src/ducky.gleam", 157).
?DOC(
" Executes operations with automatic connection cleanup.\n"
"\n"
" Connection closes automatically on success or error.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" use conn <- with_connection(\":memory:\")\n"
" query(conn, \"SELECT 42\")\n"
" ```\n"
).
-spec with_connection(
binary(),
fun((connection()) -> {ok, DRZ} | {error, error()})
) -> {ok, DRZ} | {error, error()}.
with_connection(Db_path, Callback) ->
gleam@result:'try'(
connect(Db_path),
fun(Conn) ->
Res = Callback(Conn),
_ = close(Conn),
Res
end
).
-file("src/ducky.gleam", 179).
?DOC(
" Executes operations within a transaction.\n"
"\n"
" Commits on success, rolls back on error.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" transaction(conn, fn(conn) {\n"
" use _ <- result.try(query(conn, \"UPDATE accounts ...\"))\n"
" query(conn, \"SELECT * FROM accounts\")\n"
" })\n"
" ```\n"
).
-spec transaction(
connection(),
fun((connection()) -> {ok, DSE} | {error, error()})
) -> {ok, DSE} | {error, error()}.
transaction(Conn, Callback) ->
gleam@result:'try'(
begin
_pipe = ducky@internal@connection:execute_raw(
erlang:element(2, Conn),
<<"BEGIN TRANSACTION"/utf8>>
),
gleam@result:map_error(_pipe, fun decode_nif_error/1)
end,
fun(_) -> case Callback(Conn) of
{ok, Value} ->
gleam@result:'try'(
begin
_pipe@1 = ducky@internal@connection:execute_raw(
erlang:element(2, Conn),
<<"COMMIT"/utf8>>
),
gleam@result:map_error(
_pipe@1,
fun decode_nif_error/1
)
end,
fun(_) -> {ok, Value} end
);
{error, Err} ->
_ = ducky@internal@connection:execute_raw(
erlang:element(2, Conn),
<<"ROLLBACK"/utf8>>
),
{error, Err}
end end
).
-file("src/ducky.gleam", 385).
?DOC(" Decodes a list with recursive element decoding.\n").
-spec decode_list(gleam@dynamic:dynamic_()) -> value().
decode_list(Dyn) ->
Decoder = gleam@dynamic@decode:list(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
case gleam@dynamic@decode:run(Dyn, Decoder) of
{ok, Elements} ->
Decoded_elements = gleam@list:map(Elements, fun decode_value/1),
{list, Decoded_elements};
{error, _} ->
null
end.
-file("src/ducky.gleam", 362).
?DOC(" Decodes a dynamic value from the NIF into a typed Value.\n").
-spec decode_value(gleam@dynamic:dynamic_()) -> value().
decode_value(Dyn) ->
Classification = gleam_stdlib:classify_dynamic(Dyn),
case Classification of
<<"Atom"/utf8>> ->
null;
<<"Dict"/utf8>> ->
decode_struct(Dyn);
<<"List"/utf8>> ->
decode_list(Dyn);
<<"Array"/utf8>> ->
decode_tagged_tuple(Dyn);
_ ->
Value_decoder = gleam@dynamic@decode:one_of(
begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_bool/1},
gleam@dynamic@decode:map(
_pipe,
fun(Field@0) -> {boolean, Field@0} end
)
end,
[begin
_pipe@1 = {decoder,
fun gleam@dynamic@decode:decode_int/1},
gleam@dynamic@decode:map(
_pipe@1,
fun(Field@0) -> {integer, Field@0} end
)
end,
begin
_pipe@2 = {decoder,
fun gleam@dynamic@decode:decode_float/1},
gleam@dynamic@decode:map(
_pipe@2,
fun(Field@0) -> {double, Field@0} end
)
end,
begin
_pipe@3 = {decoder,
fun gleam@dynamic@decode:decode_string/1},
gleam@dynamic@decode:map(
_pipe@3,
fun(Field@0) -> {text, Field@0} end
)
end,
begin
_pipe@4 = {decoder,
fun gleam@dynamic@decode:decode_bit_array/1},
gleam@dynamic@decode:map(
_pipe@4,
fun(Field@0) -> {blob, Field@0} end
)
end]
),
_pipe@5 = gleam@dynamic@decode:run(Dyn, Value_decoder),
gleam@result:unwrap(_pipe@5, null)
end.
-file("src/ducky.gleam", 348).
?DOC(" Decodes raw NIF result into a DataFrame.\n").
-spec decode_dataframe({list(binary()), list(list(gleam@dynamic:dynamic_()))}) -> data_frame().
decode_dataframe(Raw) ->
{Columns, Rows} = Raw,
Decoded_rows = begin
_pipe = Rows,
gleam@list:map(
_pipe,
fun(Row) ->
Values = gleam@list:map(Row, fun decode_value/1),
{row, Values}
end
)
end,
{data_frame, Columns, Decoded_rows}.
-file("src/ducky.gleam", 218).
?DOC(
" Executes a SQL query and returns structured results.\n"
"\n"
" The query runs on a dirty scheduler to avoid blocking the BEAM.\n"
" Results are loaded into memory. For large datasets, use LIMIT/OFFSET\n"
" or filter in SQL to reduce memory usage.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" query(conn, \"SELECT id, name FROM users WHERE active = true\")\n"
" // => Ok(DataFrame(columns: [\"id\", \"name\"], rows: [...]))\n"
"\n"
" // For large datasets, paginate:\n"
" query(conn, \"SELECT * FROM users LIMIT 1000 OFFSET 0\")\n"
" ```\n"
).
-spec 'query'(connection(), binary()) -> {ok, data_frame()} | {error, error()}.
'query'(Conn, Sql) ->
_pipe = ducky_nif:execute_query(
ducky@internal@connection:native(erlang:element(2, Conn)),
Sql,
[]
),
_pipe@1 = gleam@result:map(_pipe, fun decode_dataframe/1),
gleam@result:map_error(_pipe@1, fun decode_nif_error/1).
-file("src/ducky.gleam", 246).
?DOC(
" Executes a parameterized SQL query with bound parameters to prevent SQL injection.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" query_params(conn, \"SELECT * FROM users WHERE id = ? AND age > ?\", [\n"
" Integer(42),\n"
" Integer(18),\n"
" ])\n"
" // => Ok(DataFrame(...))\n"
" ```\n"
"\n"
" ## Security\n"
"\n"
" Always use this function when including user input in queries:\n"
" ```gleam\n"
" // UNSAFE - SQL injection risk\n"
" query(conn, \"SELECT * FROM users WHERE name = '\" <> user_input <> \"'\")\n"
"\n"
" // SAFE - parameters are properly escaped\n"
" query_params(conn, \"SELECT * FROM users WHERE name = ?\", [Text(user_input)])\n"
" ```\n"
).
-spec query_params(connection(), binary(), list(value())) -> {ok, data_frame()} |
{error, error()}.
query_params(Conn, Sql, Params) ->
gleam@result:'try'(
gleam@list:try_map(Params, fun value_to_dynamic/1),
fun(Dynamic_params) ->
_pipe = ducky_nif:execute_query(
ducky@internal@connection:native(erlang:element(2, Conn)),
Sql,
Dynamic_params
),
_pipe@1 = gleam@result:map(_pipe, fun decode_dataframe/1),
gleam@result:map_error(_pipe@1, fun decode_nif_error/1)
end
).
-file("src/ducky.gleam", 398).
?DOC(" Decodes an Erlang map into a Struct with recursive value decoding.\n").
-spec decode_struct(gleam@dynamic:dynamic_()) -> value().
decode_struct(Dyn) ->
Decoder = begin
_pipe = gleam@dynamic@decode:dict(
{decoder, fun gleam@dynamic@decode:decode_string/1},
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
gleam@dynamic@decode:map(
_pipe,
fun(Fields) ->
Decoded_fields = begin
_pipe@1 = Fields,
_pipe@2 = maps:to_list(_pipe@1),
_pipe@3 = gleam@list:map(
_pipe@2,
fun(Pair) ->
{Key, Val} = Pair,
{Key, decode_value(Val)}
end
),
maps:from_list(_pipe@3)
end,
{struct, Decoded_fields}
end
)
end,
_pipe@4 = gleam@dynamic@decode:run(Dyn, Decoder),
gleam@result:unwrap(_pipe@4, null).
-file("src/ducky.gleam", 458).
?DOC(" Decodes an array tagged tuple {array, [elements]}.\n").
-spec decode_array_value(gleam@dynamic:dynamic_()) -> value().
decode_array_value(Dyn) ->
Decoder = begin
gleam@dynamic@decode:subfield(
[1],
gleam@dynamic@decode:list(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
fun(Elements) -> gleam@dynamic@decode:success(Elements) end
)
end,
case gleam@dynamic@decode:run(Dyn, Decoder) of
{ok, Elements@1} ->
Decoded_elements = gleam@list:map(Elements@1, fun decode_value/1),
{array, Decoded_elements};
{error, _} ->
null
end.
-file("src/ducky.gleam", 473).
?DOC(" Decodes a map tagged tuple {map, %{key => value}}.\n").
-spec decode_map_value(gleam@dynamic:dynamic_()) -> value().
decode_map_value(Dyn) ->
Decoder = begin
gleam@dynamic@decode:subfield(
[1],
gleam@dynamic@decode:dict(
{decoder, fun gleam@dynamic@decode:decode_string/1},
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
fun(Entries) -> gleam@dynamic@decode:success(Entries) end
)
end,
case gleam@dynamic@decode:run(Dyn, Decoder) of
{ok, Entries@1} ->
Decoded_entries = begin
_pipe = Entries@1,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Pair) ->
{Key, Val} = Pair,
{Key, decode_value(Val)}
end
),
maps:from_list(_pipe@2)
end,
{map, Decoded_entries};
{error, _} ->
null
end.
-file("src/ducky.gleam", 419).
?DOC(" Decodes tagged tuples sent as Erlang arrays for various types.\n").
-spec decode_tagged_tuple(gleam@dynamic:dynamic_()) -> value().
decode_tagged_tuple(Dyn) ->
Tag_decoder = begin
gleam@dynamic@decode:subfield(
[0],
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Tag_dynamic) -> gleam@dynamic@decode:success(Tag_dynamic) end
)
end,
case gleam@dynamic@decode:run(Dyn, Tag_decoder) of
{ok, Tag_dynamic@1} ->
Tag = case gleam_stdlib:classify_dynamic(Tag_dynamic@1) of
<<"Atom"/utf8>> ->
erlang:atom_to_binary(Tag_dynamic@1);
_ ->
<<""/utf8>>
end,
case Tag of
<<"decimal"/utf8>> ->
decode_decimal_value(Dyn);
<<"array"/utf8>> ->
decode_array_value(Dyn);
<<"map"/utf8>> ->
decode_map_value(Dyn);
<<"timestamp"/utf8>> ->
decode_temporal_tuple(Dyn);
<<"date"/utf8>> ->
decode_temporal_tuple(Dyn);
<<"time"/utf8>> ->
decode_temporal_tuple(Dyn);
<<"interval"/utf8>> ->
decode_interval_tuple(Dyn);
_ ->
null
end;
{error, _} ->
null
end.