Skip to content

Commit

Permalink
Allow passing sigils/attributes to fragment
Browse files Browse the repository at this point in the history
  • Loading branch information
michalmuskala committed Nov 24, 2016
1 parent 3421fd9 commit 497cba2
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 12 deletions.
32 changes: 20 additions & 12 deletions lib/ecto/query/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ defmodule Ecto.Query.Builder do
{{:{}, [], [:fragment, [], [expr]]}, params}
end

def escape({:fragment, _, [query|frags]}, _type, params, vars, env) when is_binary(query) do
pieces = split_binary(query)
def escape({:fragment, _, [query|frags]}, _type, params, vars, env) do
pieces = split_binary(query, env)

if length(pieces) != length(frags) + 1 do
error! "fragment(...) expects extra arguments in the same amount of question marks in string"
Expand All @@ -81,11 +81,6 @@ defmodule Ecto.Query.Builder do
{{:{}, [], [:fragment, [], merge_fragments(pieces, frags)]}, params}
end

def escape({:fragment, _, [query | _]}, _type, _params, _vars, _env) do
error! "fragment(...) expects the first argument to be a string for SQL fragments, " <>
"a keyword list, or an interpolated value, got: `#{Macro.to_string(query)}`"
end

# interval

def escape({:from_now, meta, [count, interval]}, type, params, vars, env) do
Expand Down Expand Up @@ -255,11 +250,24 @@ defmodule Ecto.Query.Builder do
params
end

defp split_binary(query), do: split_binary(query, "")
defp split_binary(<<>>, consumed), do: [consumed]
defp split_binary(<<??, rest :: binary >>, consumed), do: [consumed | split_binary(rest, "")]
defp split_binary(<<?\\, ??, rest :: binary >>, consumed), do: split_binary(rest, consumed <> <<??>>)
defp split_binary(<<first :: utf8, rest :: binary>>, consumed), do: split_binary(rest, consumed <> <<first>>)
defp split_binary(query, env) do
case Macro.expand(query, env) do
binary when is_binary(binary) ->
do_split_binary(binary, "")
_ ->
error! "fragment(...) expects the first argument to be a string for SQL fragments, " <>
"a keyword list, or an interpolated value, got: `#{Macro.to_string(query)}`"
end
end

defp do_split_binary(<<>>, consumed),
do: [consumed]
defp do_split_binary(<<??, rest :: binary >>, consumed),
do: [consumed | do_split_binary(rest, "")]
defp do_split_binary(<<?\\, ??, rest :: binary >>, consumed),
do: do_split_binary(rest, consumed <> <<??>>)
defp do_split_binary(<<first :: utf8, rest :: binary>>, consumed),
do: do_split_binary(rest, consumed <> <<first>>)

defp escape_call({name, _, args}, type, params, vars, env) do
{args, params} = Enum.map_reduce(args, params, &escape(&1, type, &2, vars, env))
Expand Down
3 changes: 3 additions & 0 deletions test/ecto/query/builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ defmodule Ecto.Query.BuilderTest do
{:raw, ", "}, {:expr, ^0}, {:raw, ")"}) end), %{0 => {0, :any}}} ==
escape(quote do fragment("date_add(?, ?)", p.created_at, ^0) end, [p: 0], __ENV__)

assert {Macro.escape(quote do fragment({:raw, ""}, {:expr, ^0}, {:raw, "::text"}) end), %{0 => {0, :any}}} ==
escape(quote do fragment(~S"?::text", ^0) end, [p: 0], __ENV__)

assert {Macro.escape(quote do fragment({:raw, "query?("}, {:expr, &0.created_at},
{:raw, ")"}) end), %{}} ==
escape(quote do fragment("query\\?(?)", p.created_at) end, [p: 0], __ENV__)
Expand Down

0 comments on commit 497cba2

Please sign in to comment.