-module(sqlode@runtime). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sqlode/runtime.gleam"). -export([null/0, string/1, int/1, float/1, bool/1, bytes/1, array/1, nullable/2, param_marker/1, slice_marker/1, expand_slice_placeholders/4, prepare/2]). -export_type([query_command/0, value/0, query_info/0, placeholder_style/0, raw_query/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type query_command() :: query_one | query_many | query_exec | query_exec_result | query_exec_rows | query_exec_last_id | query_batch_one | query_batch_many | query_batch_exec | query_copy_from. -type value() :: sql_null | {sql_string, binary()} | {sql_int, integer()} | {sql_float, float()} | {sql_bool, boolean()} | {sql_bytes, bitstring()} | {sql_array, list(value())}. -type query_info() :: {query_info, binary(), binary(), query_command(), integer()}. -type placeholder_style() :: dollar_numbered | question_numbered | question_positional. -type raw_query(HBG) :: {raw_query, binary(), binary(), query_command(), integer(), placeholder_style(), fun((HBG) -> list(value())), fun((HBG) -> list({integer(), integer()}))}. -file("src/sqlode/runtime.gleam", 85). -spec null() -> value(). null() -> sql_null. -file("src/sqlode/runtime.gleam", 89). -spec string(binary()) -> value(). string(Value) -> {sql_string, Value}. -file("src/sqlode/runtime.gleam", 93). -spec int(integer()) -> value(). int(Value) -> {sql_int, Value}. -file("src/sqlode/runtime.gleam", 97). -spec float(float()) -> value(). float(Value) -> {sql_float, Value}. -file("src/sqlode/runtime.gleam", 101). -spec bool(boolean()) -> value(). bool(Value) -> {sql_bool, Value}. -file("src/sqlode/runtime.gleam", 105). -spec bytes(bitstring()) -> value(). bytes(Value) -> {sql_bytes, Value}. -file("src/sqlode/runtime.gleam", 109). -spec array(list(value())) -> value(). array(Values) -> {sql_array, Values}. -file("src/sqlode/runtime.gleam", 113). -spec nullable(gleam@option:option(HBL), fun((HBL) -> value())) -> value(). nullable(Value, Encode) -> case Value of {some, V} -> Encode(V); none -> sql_null end. -file("src/sqlode/runtime.gleam", 123). ?DOC( " Marker prefix emitted by the generator for a regular `sqlode.arg` /\n" " `sqlode.narg` / `@name` parameter at the given 1-based index.\n" " Rendered into the final placeholder at runtime by `expand_slice_placeholders`.\n" ). -spec param_marker(integer()) -> binary(). param_marker(Index) -> <<<<"__sqlode_param_"/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, "__"/utf8>>. -file("src/sqlode/runtime.gleam", 130). ?DOC( " Marker prefix emitted by the generator for a `sqlode.slice` parameter at\n" " the given 1-based index. Rendered into the expanded placeholder list at\n" " runtime by `expand_slice_placeholders`.\n" ). -spec slice_marker(integer()) -> binary(). slice_marker(Index) -> <<<<"__sqlode_slice_"/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, "__"/utf8>>. -file("src/sqlode/runtime.gleam", 201). -spec render_placeholder(placeholder_style(), integer()) -> binary(). render_placeholder(Style, Index) -> case Style of dollar_numbered -> <<"$"/utf8, (erlang:integer_to_binary(Index))/binary>>; question_numbered -> <<"?"/utf8, (erlang:integer_to_binary(Index))/binary>>; question_positional -> <<"?"/utf8>> end. -file("src/sqlode/runtime.gleam", 148). ?DOC( " Render a parameter marker into the final engine-specific placeholder.\n" "\n" " The generator emits `__sqlode_param_N__` / `__sqlode_slice_N__` in the SQL\n" " template regardless of the target engine. At runtime this function\n" " replaces each marker with the correct placeholder string (for example\n" " `$3` for PostgreSQL, `?3` for SQLite, `?` for MySQL) and expands slice\n" " markers to a comma-separated list sized by the caller-provided\n" " `slices`. Non-slice markers are renumbered sequentially across the\n" " whole SQL text so that slices that precede them shift their index.\n" "\n" " Using markers instead of rewriting `prefix<>index` directly means\n" " placeholder-like text inside string literals or comments is never\n" " touched, and MySQL (which uses bare `?` rather than `?N`) works\n" " without special-casing the placeholder format.\n" ). -spec expand_slice_placeholders( binary(), list({integer(), integer()}), integer(), placeholder_style() ) -> binary(). expand_slice_placeholders(Sql, Slices, Total_params, Style) -> {_, Mapping} = gleam@int:range( 1, Total_params + 1, {1, []}, fun(Acc, Orig_idx) -> {Next_new_idx, Map} = Acc, case gleam@list:find( Slices, fun(S) -> erlang:element(1, S) =:= Orig_idx end ) of {ok, {_, Len}} -> Marker = slice_marker(Orig_idx), case Len of 0 -> {Next_new_idx, [{Marker, <<"NULL"/utf8>>} | Map]}; _ -> Expanded = begin _pipe = gleam@int:range( Next_new_idx, Next_new_idx + Len, [], fun(Items, I) -> [render_placeholder(Style, I) | Items] end ), _pipe@1 = lists:reverse(_pipe), gleam@string:join(_pipe@1, <<", "/utf8>>) end, {Next_new_idx + Len, [{Marker, Expanded} | Map]} end; {error, _} -> Marker@1 = param_marker(Orig_idx), {Next_new_idx + 1, [{Marker@1, render_placeholder(Style, Next_new_idx)} | Map]} end end ), _pipe@2 = Mapping, _pipe@3 = lists:reverse(_pipe@2), gleam@list:fold( _pipe@3, Sql, fun(S@1, Entry) -> {Marker@2, Replacement} = Entry, gleam@string:replace(S@1, Marker@2, Replacement) end ). -file("src/sqlode/runtime.gleam", 72). ?DOC( " Prepare a raw query for execution by encoding parameters and expanding\n" " the engine-agnostic placeholder markers that the generator emits. The\n" " target placeholder dialect is read from `query.placeholder_style`, so\n" " callers no longer need to pass a separate style argument.\n" " Returns the final SQL string and the flattened parameter values, ready\n" " to be passed to a database driver.\n" ). -spec prepare(raw_query(HBH), HBH) -> {binary(), list(value())}. prepare(Query, Params) -> Values = (erlang:element(7, Query))(Params), Slices = (erlang:element(8, Query))(Params), Sql = expand_slice_placeholders( erlang:element(3, Query), Slices, erlang:element(5, Query), erlang:element(6, Query) ), {Sql, Values}.