-module(sqlode@runtime). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sqlode/runtime.gleam"). -export([raw_query_for_test/7, null/0, string/1, int/1, float/1, bool/1, bytes/1, array/1, nullable/2, param_marker/1, slice_marker/1, param_marker_checked/1, slice_marker_checked/1, expand_slice_placeholders/4, prepare/2, expand_slice_placeholders_checked/4]). -export_type([query_command/0, value/0, query_info/0, placeholder_style/0, raw_query/1, marker_error/0, expand_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. -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(HCF) :: {raw_query, binary(), binary(), query_command(), integer(), placeholder_style(), fun((HCF) -> list(value())), fun((HCF) -> list({integer(), integer()}))}. -type marker_error() :: {marker_index_non_positive, integer()}. -type expand_error() :: {slice_length_negative, integer(), integer()} | {slice_index_out_of_range, integer(), integer()}. -file("src/sqlode/runtime.gleam", 84). ?DOC( " Construct a `RawQuery` directly. **Test-only**: production callers\n" " should always go through codegen-emitted `RawQuery` values, which\n" " `sqlode generate` produces from declarative SQL fixtures.\n" "\n" " Use this helper when:\n" "\n" " - writing a custom adapter (in-memory test database, SQLite WASM\n" " shim, query-log middleware, ...) that needs to exercise\n" " `prepare(query, params)` against a hand-rolled `RawQuery`\n" " without running the codegen pipeline; or\n" " - writing property / regression tests for the runtime surface\n" " (`prepare`, `expand_slice_placeholders`) without regenerating\n" " fixtures every time.\n" "\n" " Behaviour is identical to invoking the `RawQuery(...)` constructor\n" " directly; the named helper exists so the intent (\\\"this is a\n" " hand-rolled RawQuery, not codegen output\\\") is obvious at the call\n" " site and discoverable via the docs.\n" ). -spec raw_query_for_test( binary(), binary(), query_command(), integer(), placeholder_style(), fun((HCG) -> list(value())), fun((HCG) -> list({integer(), integer()})) ) -> raw_query(HCG). raw_query_for_test( Name, Sql, Command, Param_count, Placeholder_style, Encode, Slice_info ) -> {raw_query, Name, Sql, Command, Param_count, Placeholder_style, Encode, Slice_info}. -file("src/sqlode/runtime.gleam", 123). -spec null() -> value(). null() -> sql_null. -file("src/sqlode/runtime.gleam", 127). -spec string(binary()) -> value(). string(Value) -> {sql_string, Value}. -file("src/sqlode/runtime.gleam", 131). -spec int(integer()) -> value(). int(Value) -> {sql_int, Value}. -file("src/sqlode/runtime.gleam", 135). -spec float(float()) -> value(). float(Value) -> {sql_float, Value}. -file("src/sqlode/runtime.gleam", 139). -spec bool(boolean()) -> value(). bool(Value) -> {sql_bool, Value}. -file("src/sqlode/runtime.gleam", 143). -spec bytes(bitstring()) -> value(). bytes(Value) -> {sql_bytes, Value}. -file("src/sqlode/runtime.gleam", 147). -spec array(list(value())) -> value(). array(Values) -> {sql_array, Values}. -file("src/sqlode/runtime.gleam", 151). -spec nullable(gleam@option:option(HCO), fun((HCO) -> value())) -> value(). nullable(Value, Encode) -> case Value of {some, V} -> Encode(V); none -> sql_null end. -file("src/sqlode/runtime.gleam", 171). ?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\n" " `expand_slice_placeholders`.\n" "\n" " Panics with\n" " `\"sqlode.runtime.param_marker: index must be >= 1 (1-based) (got )\"`\n" " when `index < 1`. The runtime expand step is keyed by 1-based positions,\n" " so a `0` or negative index would either round-trip to a syntactically\n" " broken SQL string or silently match an unrelated marker — both\n" " constitute SQL-shape bugs. Use `param_marker_checked/1` instead when\n" " the caller wants to surface the precondition as a `Result` (for\n" " example, in a custom adapter that accepts user-supplied indices).\n" ). -spec param_marker(integer()) -> binary(). param_marker(Index) -> case Index < 1 of true -> erlang:error(#{gleam_error => panic, message => (<<<<"sqlode.runtime.param_marker: index must be >= 1 (1-based) (got "/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, ")"/utf8>>), file => <>, module => <<"sqlode/runtime"/utf8>>, function => <<"param_marker"/utf8>>, line => 174}); false -> <<<<"__sqlode_param_"/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, "__"/utf8>> end. -file("src/sqlode/runtime.gleam", 192). ?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\n" " at runtime by `expand_slice_placeholders`.\n" "\n" " Panics with\n" " `\"sqlode.runtime.slice_marker: index must be >= 1 (1-based) (got )\"`\n" " when `index < 1`. Same reasoning as `param_marker/1`. Use\n" " `slice_marker_checked/1` instead when the caller wants to surface the\n" " precondition as a `Result`.\n" ). -spec slice_marker(integer()) -> binary(). slice_marker(Index) -> case Index < 1 of true -> erlang:error(#{gleam_error => panic, message => (<<<<"sqlode.runtime.slice_marker: index must be >= 1 (1-based) (got "/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, ")"/utf8>>), file => <>, module => <<"sqlode/runtime"/utf8>>, function => <<"slice_marker"/utf8>>, line => 195}); false -> <<<<"__sqlode_slice_"/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, "__"/utf8>> end. -file("src/sqlode/runtime.gleam", 223). ?DOC( " Like `param_marker/1`, but returns the precondition failure as a\n" " `Result` instead of panicking. Use this when `index` comes from a\n" " custom adapter or hand-rolled `RawQuery` and the caller wants to\n" " surface bookkeeping mistakes without crashing the process. Same\n" " reasoning as `expand_slice_placeholders_checked` (#546).\n" "\n" " On success the returned string is identical to `param_marker(index)`.\n" ). -spec param_marker_checked(integer()) -> {ok, binary()} | {error, marker_error()}. param_marker_checked(Index) -> case Index < 1 of true -> {error, {marker_index_non_positive, Index}}; false -> {ok, <<<<"__sqlode_param_"/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, "__"/utf8>>} end. -file("src/sqlode/runtime.gleam", 233). ?DOC( " Like `slice_marker/1`, but returns the precondition failure as a\n" " `Result` instead of panicking. Same shape and reasoning as\n" " `param_marker_checked/1`.\n" ). -spec slice_marker_checked(integer()) -> {ok, binary()} | {error, marker_error()}. slice_marker_checked(Index) -> case Index < 1 of true -> {error, {marker_index_non_positive, Index}}; false -> {ok, <<<<"__sqlode_slice_"/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, "__"/utf8>>} end. -file("src/sqlode/runtime.gleam", 328). -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", 371). -spec validate_slices(list({integer(), integer()}), integer()) -> {ok, nil} | {error, expand_error()}. validate_slices(Slices, Total_params) -> case Slices of [] -> {ok, nil}; [{Idx, Len} | Rest] -> case Len < 0 of true -> {error, {slice_length_negative, Idx, Len}}; false -> case (Idx < 1) orelse (Idx > Total_params) of true -> {error, {slice_index_out_of_range, Idx, Total_params}}; false -> validate_slices(Rest, Total_params) end end end. -file("src/sqlode/runtime.gleam", 254). ?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) -> case validate_slices(Slices, Total_params) of {ok, _} -> nil; {error, {slice_length_negative, Index, Length}} -> erlang:error(#{gleam_error => panic, message => (<<<<<<<<"sqlode.expand_slice_placeholders: slice length must be >= 0 (got index="/utf8, (erlang:integer_to_binary(Index))/binary>>/binary, ", length="/utf8>>/binary, (erlang:integer_to_binary(Length))/binary>>/binary, ")"/utf8>>), file => <>, module => <<"sqlode/runtime"/utf8>>, function => <<"expand_slice_placeholders"/utf8>>, line => 263}); {error, {slice_index_out_of_range, Index@1, Total}} -> erlang:error(#{gleam_error => panic, message => (<<<<<<<<<<<<"sqlode.expand_slice_placeholders: slice index must be in 1.."/utf8, (erlang:integer_to_binary(Total))/binary>>/binary, " (got index="/utf8>>/binary, (erlang:integer_to_binary(Index@1))/binary>>/binary, ", total_params="/utf8>>/binary, (erlang:integer_to_binary(Total))/binary>>/binary, ")"/utf8>>), file => <>, module => <<"sqlode/runtime"/utf8>>, function => <<"expand_slice_placeholders"/utf8>>, line => 271}) end, {_, 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", 110). ?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(HCK), HCK) -> {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}. -file("src/sqlode/runtime.gleam", 359). ?DOC( " Like `expand_slice_placeholders`, but returns the validation\n" " failure as a `Result` instead of panicking. Use this when `slices`\n" " or `total_params` come from a custom adapter / hand-rolled\n" " `RawQuery` and the caller wants to surface bookkeeping mistakes\n" " without crashing the process.\n" "\n" " On success the returned string is identical to\n" " `expand_slice_placeholders(sql, slices, total_params, style)`.\n" ). -spec expand_slice_placeholders_checked( binary(), list({integer(), integer()}), integer(), placeholder_style() ) -> {ok, binary()} | {error, expand_error()}. expand_slice_placeholders_checked(Sql, Slices, Total_params, Style) -> case validate_slices(Slices, Total_params) of {error, E} -> {error, E}; {ok, _} -> {ok, expand_slice_placeholders(Sql, Slices, Total_params, Style)} end.