Packages
cake
4.1.0
4.1.0
4.0.0
3.0.0
2.2.2
2.2.1
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.0
retired
0.15.0
retired
0.14.0
retired
0.13.0
retired
0.12.0
retired
0.11.0
retired
0.10.1
retired
0.10.0
retired
0.9.2
retired
0.9.1
retired
0.9.0
retired
0.8.0
retired
0.7.0
retired
0.6.0
retired
0.5.0
retired
0.4.0
retired
0.3.0
retired
0.2.0
retired
0.1.0
retired
0.0.1
retired
🎂 An SQL query builder for Gleam for SQL dialects 🐘PostgreSQL, 🪶SQLite, 🦭MariaDB, and 🐬MySQL
Current section
Files
Jump to
Current section
Files
src/cake@fragment.erl
-module(cake@fragment).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/cake/fragment.gleam").
-export([int/1, prepared/2, literal/1, bool/1, true/0, false/0, float/1, string/1, null/0, date/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.
?MODULEDOC(
" Fragments are low level building blocks of queries which allow direct\n"
" manipulation of the query string.\n"
"\n"
" If you want to insert parameters, you are required to use prepared\n"
" fragments, which will be validated against the number of parameters given\n"
" and the parameters are automatically escaped by the RDBMS to prevent SQL\n"
" injections.\n"
"\n"
" Low-level building blocks for injecting raw SQL into queries while keeping\n"
" parameter binding safe.\n"
"\n"
" ## Aliases\n"
"\n"
" ```gleam\n"
" import cake/fragment as f\n"
" ```\n"
"\n"
" ---\n"
"\n"
" ## When to use fragments\n"
"\n"
" Use fragments when Cake's typed builder functions do not cover your use case:\n"
"\n"
" - Database-specific functions (`NOW()`, `ARRAY_AGG(...)`, `JSON_BUILD_OBJECT(...)`)\n"
" - Type casts (`$1::uuid`, `? AS UNSIGNED`)\n"
" - Any SQL expression not expressible through the standard API\n"
"\n"
" > ⛔ **Never** pass uncontrolled user input through `f.literal()`.\n"
" > Always use `f.prepared()` with typed params to bind user data safely.\n"
"\n"
" ---\n"
"\n"
" ## Fragment constructors\n"
"\n"
" ### `prepared(string, params) -> Fragment`\n"
"\n"
" Creates a fragment where `?` placeholders (the value of `f.placeholder`) are\n"
" replaced with safely-bound parameters at query execution time.\n"
"\n"
" ```gleam\n"
" f.prepared(\"?::uuid\", [f.string(\"0000-0000-4000-a000-a00000000000\")])\n"
" ```\n"
"\n"
" The placeholder character is exported as the constant `f.placeholder` (the `?`\n"
" grapheme). Use it when constructing fragment strings dynamically.\n"
"\n"
" > ⛔ If the number of placeholders does not match the number of params, an\n"
" > error is printed to stderr and the fragment is created with best-effort\n"
" > fallback behaviour:\n"
" >\n"
" > - Too many placeholders → last param is repeated.\n"
" > - Too many params → excess params are ignored.\n"
"\n"
" ### `literal(string) -> Fragment`\n"
"\n"
" Creates a fragment from a **static, developer-controlled** SQL string.\n"
" No parameter substitution occurs.\n"
"\n"
" ```gleam\n"
" f.literal(\"NOW()\")\n"
" f.literal(\"CURRENT_TIMESTAMP\")\n"
" f.literal(\"COUNT(*)\")\n"
" ```\n"
"\n"
" > ⛔ **YOU ARE FORBIDDEN TO INSERT UNCONTROLLED USER INPUT THIS WAY.**\n"
"\n"
" ---\n"
"\n"
" ## Placeholder\n"
"\n"
" ```gleam\n"
" f.placeholder // the \"?\" grapheme used inside prepared fragment strings\n"
" ```\n"
"\n"
" ---\n"
"\n"
" ## Param constructors\n"
"\n"
" Params are the typed values passed to `f.prepared()`. They are escaped by the\n"
" database driver, preventing SQL injection.\n"
"\n"
" | Function | Gleam type | SQL type |\n"
" | ---------- | ---------- | ------------- |\n"
" | `bool(value)` | `Bool` | Boolean |\n"
" | `true()` | — | `TRUE` |\n"
" | `false()` | — | `FALSE` |\n"
" | `float(value)` | `Float` | Float |\n"
" | `int(value)` | `Int` | Integer |\n"
" | `string(value)` | `String` | String / Text |\n"
" | `null()` | — | `NULL` |\n"
" | `date(value)` | `calendar.Date` | Date |\n"
"\n"
" ---\n"
"\n"
" ## Using fragments in queries\n"
"\n"
" Fragments can appear in several positions depending on context:\n"
"\n"
" ```mermaid\n"
" flowchart TD\n"
" A[Fragment] --> B[SelectValue\\ns.fragment]\n"
" A --> C[InsertValue\\ni.fragment]\n"
" A --> D[UpdateSet\\nu.set_fragment]\n"
" A --> E[WhereValue\\nw.fragment_value]\n"
" A --> F[Where condition\\nw.fragment]\n"
" ```\n"
"\n"
" ### In SELECT projections\n"
"\n"
" ```gleam\n"
" import cake/select as s\n"
" import cake/fragment as f\n"
"\n"
" s.new()\n"
" |> s.from_table(\"orders\")\n"
" |> s.select(s.fragment(f.literal(\"SUM(amount)\")) |> s.alias(\"total\"))\n"
" ```\n"
"\n"
" ### In INSERT values\n"
"\n"
" ```gleam\n"
" import cake/insert as i\n"
" import cake/fragment as f\n"
"\n"
" i.from_values(\"users\", [\"id\", \"name\"], [\n"
" i.row([\n"
" i.fragment(f.prepared(\"?::uuid\", [f.string(\"abc-123\")])),\n"
" i.string(\"Alice\"),\n"
" ]),\n"
" ])\n"
" ```\n"
"\n"
" ### In UPDATE SET\n"
"\n"
" ```gleam\n"
" import cake/update as u\n"
" import cake/fragment as f\n"
"\n"
" u.new()\n"
" |> u.table(\"sessions\")\n"
" |> u.set(u.set_fragment(\"expires_at\", f.literal(\"NOW() + INTERVAL '1 hour'\")))\n"
" ```\n"
"\n"
" ### In WHERE — as a value operand\n"
"\n"
" ```gleam\n"
" import cake/where as w\n"
" import cake/fragment as f\n"
"\n"
" w.eq(\n"
" w.col(\"tags\"),\n"
" w.fragment_value(f.prepared(\"?::jsonb\", [f.string(\"[\\\"gleam\\\"]\")])),\n"
" )\n"
" ```\n"
"\n"
" ### In WHERE — as a full condition\n"
"\n"
" ```gleam\n"
" w.fragment(f.prepared(\"? @> ?::jsonb\", [\n"
" f.string(\"tags\"),\n"
" f.string(\"[\\\"gleam\\\"]\"),\n"
" ]))\n"
" ```\n"
"\n"
" ---\n"
"\n"
" ## Safety reference\n"
"\n"
" | | `f.prepared` | `f.literal` |\n"
" | ------ | ---------- | ----------- |\n"
" | User input safe | ✅ | ❌ |\n"
" | Param substitution | ✅ | ❌ |\n"
" | Static SQL only | ❌ | ✅ |\n"
"\n"
" ---\n"
"\n"
" ## Full Example\n"
"\n"
" ```gleam\n"
" import cake/select as s\n"
" import cake/where as w\n"
" import cake/fragment as f\n"
"\n"
" // Select users whose tags JSON array contains \"gleam\"\n"
" s.new()\n"
" |> s.from_table(\"users\")\n"
" |> s.select_cols([\"id\", \"name\"])\n"
" |> s.select(s.fragment(f.literal(\"tags::text\")) |> s.alias(\"tags_text\"))\n"
" |> s.where(w.fragment(\n"
" f.prepared(\"tags @> ?::jsonb\", [f.string(\"[\\\"gleam\\\"]\")]),\n"
" ))\n"
" |> s.order_by_asc(\"name\")\n"
" |> s.to_query\n"
" ```\n"
"\n"
"\n"
" <!-- html assets for docs gen -->\n"
" <style>\n"
" .page {\n"
" display: block;\n"
" }\n"
" .content {\n"
" width: auto;\n"
" max-width: none;\n"
" }\n"
" </style>\n"
" <!--<script src=\"https://cdn.jsdelivr.net/npm/@mermaid-js/tiny@11/dist/mermaid.tiny.js\"></script>-->\n"
" <script\n"
" src=\"https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js\"\n"
" integrity=\"sha256-cBN+d7snO7LvlyuG6LBADMqL5TyyW/xFkRoYbcmGZd4=\"\n"
" crossorigin=\"anonymous\"\n"
" ></script>\n"
" <script>\n"
" (callback => document.readyState !== 'loading' ? callback() : document.addEventListener('DOMContentLoaded', callback, { once: true }))(() => {\n"
" mermaid.initialize({ startOnLoad: false })\n"
" mermaid.run({\n"
" querySelector: \".language-mermaid\",\n"
" })\n"
" })\n"
" </script>\n"
"\n"
).
-file("src/cake/fragment.gleam", 360).
?DOC(" Create a new `Param` with an `Int` value.\n").
-spec int(integer()) -> cake@param:param().
int(Value) ->
_pipe = Value,
{int_param, _pipe}.
-file("src/cake/fragment.gleam", 264).
?DOC(
" Create a new fragment from a string and a list of parameters.\n"
"\n"
" ⛔ ⛔ ⛔\n"
"\n"
" If you mismatch the number of placeholders with the number of\n"
" parameters, an error will be printed to stderr and the fragment will be\n"
" created with the given parameters:\n"
"\n"
" - If there are too many placeholders, the fragment will be created with the\n"
" given parameters and the last parameter will be repeated for the remaining\n"
" placeholders.\n"
" - If there are too many parameters, the fragment will be created with the\n"
" given parameters and the excess parameters will be ignored.\n"
"\n"
" ⛔ ⛔ ⛔\n"
).
-spec prepared(binary(), list(cake@param:param())) -> cake@internal@read_query:fragment().
prepared(String, Params) ->
Placeholder_count = begin
_pipe = String,
_pipe@1 = cake@internal@read_query:fragment_prepared_split_string(_pipe),
cake@internal@read_query:fragment_count_placeholders(_pipe@1)
end,
Param_count = begin
_pipe@2 = Params,
erlang:length(_pipe@2)
end,
case {Placeholder_count,
Param_count,
begin
_pipe@3 = Placeholder_count,
gleam@int:compare(_pipe@3, Param_count)
end} of
{0, 0, eq} ->
_pipe@4 = String,
{fragment_literal, _pipe@4};
{_, _, eq} ->
_pipe@5 = String,
{fragment_prepared, _pipe@5, Params};
{0, _, _} ->
gleam_stdlib:println_error(
<<<<<<<<"Fragment had 0 "/utf8, (<<"$"/utf8>>)/binary>>/binary,
"-placeholders, but "/utf8>>/binary,
(begin
_pipe@6 = Param_count,
erlang:integer_to_binary(_pipe@6)
end)/binary>>/binary,
" params given!"/utf8>>
),
_pipe@7 = String,
{fragment_literal, _pipe@7};
{_, 0, _} ->
gleam_stdlib:println_error(
<<<<<<<<"Fragment had "/utf8,
(begin
_pipe@8 = Placeholder_count,
erlang:integer_to_binary(_pipe@8)
end)/binary>>/binary,
" "/utf8>>/binary,
(<<"$"/utf8>>)/binary>>/binary,
"-placeholders, but 0 params given!"/utf8>>
),
_pipe@9 = String,
{fragment_literal, _pipe@9};
{_, _, _} ->
gleam_stdlib:println_error(
<<<<<<<<<<<<"Fragment had "/utf8,
(begin
_pipe@10 = Placeholder_count,
erlang:integer_to_binary(_pipe@10)
end)/binary>>/binary,
" "/utf8>>/binary,
(<<"$"/utf8>>)/binary>>/binary,
"-placeholders, but "/utf8>>/binary,
(begin
_pipe@11 = Param_count,
erlang:integer_to_binary(_pipe@11)
end)/binary>>/binary,
" params given!"/utf8>>
),
_pipe@12 = String,
{fragment_prepared, _pipe@12, Params}
end.
-file("src/cake/fragment.gleam", 326).
?DOC(
" Create a new fragment from a literal string.\n"
"\n"
" ⛔ ⛔ ⛔\n"
"\n"
" WARNING: YOU ARE FORBIDDEN TO INSERT UNCONTROLLED USER INPUT THIS WAY!\n"
"\n"
" ⛔ ⛔ ⛔\n"
).
-spec literal(binary()) -> cake@internal@read_query:fragment().
literal(String) ->
_pipe = String,
{fragment_literal, _pipe}.
-file("src/cake/fragment.gleam", 336).
?DOC(" Create a new `Param` with a `Bool` value.\n").
-spec bool(boolean()) -> cake@param:param().
bool(Value) ->
_pipe = Value,
{bool_param, _pipe}.
-file("src/cake/fragment.gleam", 342).
?DOC(" Create a new `Param` with a `True` value.\n").
-spec true() -> cake@param:param().
true() ->
_pipe = true,
{bool_param, _pipe}.
-file("src/cake/fragment.gleam", 348).
?DOC(" Create a new `Param` with a `False` value.\n").
-spec false() -> cake@param:param().
false() ->
_pipe = false,
{bool_param, _pipe}.
-file("src/cake/fragment.gleam", 354).
?DOC(" Create a new `Param` with a `Float` value.\n").
-spec float(float()) -> cake@param:param().
float(Value) ->
_pipe = Value,
{float_param, _pipe}.
-file("src/cake/fragment.gleam", 366).
?DOC(" Create a new `Param` with a `String` value.\n").
-spec string(binary()) -> cake@param:param().
string(Value) ->
_pipe = Value,
{string_param, _pipe}.
-file("src/cake/fragment.gleam", 372).
?DOC(" Create a new `Param` with an SQL `NULL` value.\n").
-spec null() -> cake@param:param().
null() ->
null_param.
-file("src/cake/fragment.gleam", 378).
?DOC(" Create a new `Param` with a `calendar.Date` value.\n").
-spec date(gleam@time@calendar:date()) -> cake@param:param().
date(Value) ->
_pipe = Value,
{date_param, _pipe}.