Skip to content

Commit

Permalink
Allow numbers in literal/1 (#4562)
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-rychlewski authored Jan 8, 2025
1 parent 4990abd commit 3ab8279
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 15 deletions.
15 changes: 9 additions & 6 deletions lib/ecto/query/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -480,17 +480,20 @@ defmodule Ecto.Query.API do
def fragment(fragments), do: doc!([fragments])

@doc """
Allows a literal identifier to be injected into a fragment:
Allows a literal identifier or number to be injected into a fragment:
collation = "es_ES"
fragment("? COLLATE ?", ^name, literal(^collation))
The example above will inject `collation` into the query as
a literal identifier instead of a query parameter. Note that
each different value of `collation` will emit a different query,
which will be independently prepared and cached.
limit = 10
limit(query, fragment("?", literal(^limit)))
The example above will inject `collation` and `limit` into the queries as
literals instead of query parameters. Note that each different value passed
to `literal/1` will emit a different query, which will be independently prepared
and cached.
"""
def literal(binary), do: doc!([binary])
def literal(literal), do: doc!([literal])

@doc """
Allows a list argument to be spliced into a fragment.
Expand Down
11 changes: 5 additions & 6 deletions lib/ecto/query/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1256,13 +1256,12 @@ defmodule Ecto.Query.Builder do
@doc """
Called by escaper at runtime to verify literal in fragments.
"""
def literal!(literal) when is_binary(literal), do: literal
def literal!(literal) when is_number(literal), do: literal

def literal!(literal) do
if is_binary(literal) do
literal
else
raise ArgumentError,
"literal(^value) expects `value` to be a string, got `#{inspect(literal)}`"
end
raise ArgumentError,
"literal(^value) expects `value` to be a string or a number, got `#{inspect(literal)}`"
end

@doc """
Expand Down
17 changes: 14 additions & 3 deletions test/ecto/query_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -988,9 +988,20 @@ defmodule Ecto.QueryTest do
raw: ""
] = parts

assert_raise ArgumentError, "literal(^value) expects `value` to be a string, got `123`", fn ->
from p in "posts", select: fragment("? COLLATE ?", p.name, literal(^123))
end
query = from p in "posts", limit: fragment("?", literal(^1))
assert {:fragment, _, parts} = query.limit.expr

assert [
raw: "",
expr: {:literal, _, [1]},
raw: ""
] = parts

assert_raise ArgumentError,
"literal(^value) expects `value` to be a string or a number, got `%{}`",
fn ->
from p in "posts", select: fragment("? COLLATE ?", p.name, literal(^%{}))
end
end

test "supports list splicing" do
Expand Down

0 comments on commit 3ab8279

Please sign in to comment.