Current section
Files
Jump to
Current section
Files
src/ducky@query.erl
-module(ducky@query).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ducky/query.gleam").
-export(['query'/2, query_params/3]).
-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(" Query execution and result handling.\n").
-file("src/ducky/query.gleam", 65).
?DOC(" Decodes a dynamic value from the NIF into a typed Value.\n").
-spec decode_value(gleam@dynamic:dynamic_()) -> ducky@types:value().
decode_value(Dyn) ->
case gleam_stdlib:classify_dynamic(Dyn) of
<<"Atom"/utf8>> ->
null;
_ ->
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/query.gleam", 86).
?DOC(" Converts a Value to a Dynamic for passing to the NIF.\n").
-spec value_to_dynamic(ducky@types:value()) -> gleam@dynamic:dynamic_().
value_to_dynamic(Value) ->
case Value of
null ->
gleam@dynamic:nil();
{boolean, B} ->
gleam_stdlib:identity(B);
{tiny_int, I} ->
gleam_stdlib:identity(I);
{small_int, I@1} ->
gleam_stdlib:identity(I@1);
{integer, I@2} ->
gleam_stdlib:identity(I@2);
{big_int, I@3} ->
gleam_stdlib:identity(I@3);
{float, F} ->
gleam_stdlib:identity(F);
{double, F@1} ->
gleam_stdlib:identity(F@1);
{text, S} ->
gleam_stdlib:identity(S);
{blob, Bits} ->
gleam_stdlib:identity(Bits);
{timestamp, _} ->
gleam@dynamic:nil();
{date, _} ->
gleam@dynamic:nil();
{time, _} ->
gleam@dynamic:nil();
{interval, _} ->
gleam@dynamic:nil();
{list, _} ->
gleam@dynamic:nil()
end.
-file("src/ducky/query.gleam", 108).
?DOC(" Decodes raw NIF result into a DataFrame.\n").
-spec decode_dataframe({list(binary()), list(list(gleam@dynamic:dynamic_()))}) -> ducky@types:data_frame().
decode_dataframe(Result) ->
{Columns, Rows} = Result,
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/query.gleam", 24).
?DOC(
" Executes a SQL query and returns structured results.\n"
"\n"
" The query runs on a dirty scheduler to avoid blocking the BEAM.\n"
" Large result sets are streamed to prevent memory exhaustion.\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"
).
-spec 'query'(ducky@connection:connection(), binary()) -> {ok,
ducky@types:data_frame()} |
{error, ducky@error:error()}.
'query'(Conn, Sql) ->
_pipe = ducky_nif:execute_query(ducky@connection:native(Conn), Sql, []),
_pipe@1 = gleam@result:map(_pipe, fun decode_dataframe/1),
gleam@result:map_error(
_pipe@1,
fun ducky@internal@error_decoder:decode_nif_error/1
).
-file("src/ducky/query.gleam", 52).
?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"
" types.Integer(42),\n"
" types.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 = ?\", [types.Text(user_input)])\n"
" ```\n"
).
-spec query_params(
ducky@connection:connection(),
binary(),
list(ducky@types:value())
) -> {ok, ducky@types:data_frame()} | {error, ducky@error:error()}.
query_params(Conn, Sql, Params) ->
Dynamic_params = gleam@list:map(Params, fun value_to_dynamic/1),
_pipe = ducky_nif:execute_query(
ducky@connection:native(Conn),
Sql,
Dynamic_params
),
_pipe@1 = gleam@result:map(_pipe, fun decode_dataframe/1),
gleam@result:map_error(
_pipe@1,
fun ducky@internal@error_decoder:decode_nif_error/1
).