-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" " \n" " \n" " \n" " \n" " \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}.