zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

update.ex (6530B)


      1 import Kernel, except: [apply: 2]
      2 
      3 defmodule Ecto.Query.Builder.Update do
      4   @moduledoc false
      5 
      6   @keys [:set, :inc, :push, :pull]
      7   alias Ecto.Query.Builder
      8 
      9   @doc """
     10   Escapes a list of quoted expressions.
     11 
     12       iex> escape([], [], __ENV__)
     13       {[], [], []}
     14 
     15       iex> escape([set: []], [], __ENV__)
     16       {[], [], []}
     17 
     18       iex> escape(quote(do: ^[set: []]), [], __ENV__)
     19       {[], [set: []], []}
     20 
     21       iex> escape(quote(do: [set: ^[foo: 1]]), [], __ENV__)
     22       {[], [set: [foo: 1]], []}
     23 
     24       iex> escape(quote(do: [set: [foo: ^1]]), [], __ENV__)
     25       {[], [set: [foo: 1]], []}
     26 
     27   """
     28   @spec escape(Macro.t, Keyword.t, Macro.Env.t) :: {Macro.t, Macro.t, list}
     29   def escape(expr, vars, env) when is_list(expr) do
     30     escape_op(expr, [], [], [], vars, env)
     31   end
     32 
     33   def escape({:^, _, [v]}, _vars, _env) do
     34     {[], v, []}
     35   end
     36 
     37   def escape(expr, _vars, _env) do
     38     compile_error!(expr)
     39   end
     40 
     41   defp escape_op([{k, v}|t], compile, runtime, params, vars, env) when is_atom(k) and is_list(v) do
     42     validate_op!(k)
     43     {compile_values, runtime_values, params} = escape_kw(k, v, params, vars, env)
     44     compile =
     45       if compile_values == [], do: compile, else: [{k, Enum.reverse(compile_values)} | compile]
     46     runtime =
     47       if runtime_values == [], do: runtime, else: [{k, Enum.reverse(runtime_values)} | runtime]
     48     escape_op(t, compile, runtime, params, vars, env)
     49   end
     50 
     51   defp escape_op([{k, {:^, _, [v]}}|t], compile, runtime, params, vars, env) when is_atom(k) do
     52     validate_op!(k)
     53     escape_op(t, compile, [{k, v}|runtime], params, vars, env)
     54   end
     55 
     56   defp escape_op([], compile, runtime, params, _vars, _env) do
     57     {Enum.reverse(compile), Enum.reverse(runtime), params}
     58   end
     59 
     60   defp escape_op(expr, _compile, _runtime, _params, _vars, _env) do
     61     compile_error!(expr)
     62   end
     63 
     64   defp escape_kw(op, kw, params, vars, env) do
     65     Enum.reduce kw, {[], [], params}, fn
     66       {k, {:^, _, [v]}}, {compile, runtime, params} when is_atom(k) ->
     67         {compile, [{k, v} | runtime], params}
     68       {k, v}, {compile, runtime, params} ->
     69         k = escape_field!(k)
     70         {v, {params, _acc}} = Builder.escape(v, type_for_key(op, {0, k}), {params, %{}}, vars, env)
     71         {[{k, v} | compile], runtime, params}
     72       _, _acc ->
     73         Builder.error! "malformed #{inspect op} in update `#{Macro.to_string(kw)}`, " <>
     74                        "expected a keyword list"
     75     end
     76   end
     77 
     78   defp escape_field!({:^, _, [k]}), do: quote(do: Ecto.Query.Builder.Update.field!(unquote(k)))
     79   defp escape_field!(k) when is_atom(k), do: k
     80 
     81   defp escape_field!(k) do
     82     Builder.error!(
     83       "expected an atom field or an interpolated field in `update`, got `#{inspect(k)}`"
     84     )
     85   end
     86 
     87   def field!(field) when is_atom(field), do: field
     88 
     89   def field!(other) do
     90     raise ArgumentError, "expected a field as an atom in `update`, got: `#{inspect other}`"
     91   end
     92 
     93   defp compile_error!(expr) do
     94     Builder.error! "malformed update `#{Macro.to_string(expr)}` in query expression, " <>
     95                    "expected a keyword list with set/push/pop as keys with field-value " <>
     96                    "pairs as values"
     97   end
     98 
     99   @doc """
    100   Builds a quoted expression.
    101 
    102   The quoted expression should evaluate to a query at runtime.
    103   If possible, it does all calculations at compile time to avoid
    104   runtime work.
    105   """
    106   @spec build(Macro.t, [Macro.t], Macro.t, Macro.Env.t) :: Macro.t
    107   def build(query, binding, expr, env) do
    108     {query, binding} = Builder.escape_binding(query, binding, env)
    109     {compile, runtime, params} = escape(expr, binding, env)
    110 
    111     query =
    112       if compile == [] do
    113         query
    114       else
    115         params = Builder.escape_params(params)
    116 
    117         update = quote do
    118           %Ecto.Query.QueryExpr{expr: unquote(compile), params: unquote(params),
    119                                 file: unquote(env.file), line: unquote(env.line)}
    120         end
    121 
    122         Builder.apply_query(query, __MODULE__, [update], env)
    123       end
    124 
    125     if runtime == [] do
    126       query
    127     else
    128       quote do
    129         Ecto.Query.Builder.Update.update!(unquote(query), unquote(runtime),
    130                                           unquote(env.file), unquote(env.line))
    131       end
    132     end
    133   end
    134 
    135   @doc """
    136   The callback applied by `build/4` to build the query.
    137   """
    138   @spec apply(Ecto.Queryable.t, term) :: Ecto.Query.t
    139   def apply(%Ecto.Query{updates: updates} = query, expr) do
    140     %{query | updates: updates ++ [expr]}
    141   end
    142   def apply(query, expr) do
    143     apply(Ecto.Queryable.to_query(query), expr)
    144   end
    145 
    146   @doc """
    147   If there are interpolated updates at compile time,
    148   we need to handle them at runtime. We do such in
    149   this callback.
    150   """
    151   def update!(query, runtime, file, line) when is_list(runtime) do
    152     {runtime, {params, _count}} =
    153       Enum.map_reduce runtime, {[], 0}, fn
    154         {k, v}, acc when is_atom(k) and is_list(v) ->
    155           validate_op!(k)
    156           {v, params} = runtime_field!(query, k, v, acc)
    157           {{k, v}, params}
    158         _, _ ->
    159           runtime_error!(runtime)
    160       end
    161 
    162     expr = %Ecto.Query.QueryExpr{expr: runtime, params: Enum.reverse(params),
    163                                  file: file, line: line}
    164 
    165     apply(query, expr)
    166   end
    167 
    168   def update!(_query, runtime, _file, _line) do
    169     runtime_error!(runtime)
    170   end
    171 
    172   defp runtime_field!(query, key, kw, acc) do
    173     Enum.map_reduce kw, acc, fn
    174       {k, %Ecto.Query.DynamicExpr{} = v}, {params, count} when is_atom(k) ->
    175         {v, params, count} = Ecto.Query.Builder.Dynamic.partially_expand(:update, query, v, params, count)
    176         {{k, v}, {params, count}}
    177       {k, v}, {params, count} when is_atom(k) ->
    178         params = [{v, type_for_key(key, {0, k})} | params]
    179         {{k, {:^, [], [count]}}, {params, count + 1}}
    180       _, _acc ->
    181         raise ArgumentError, "malformed #{inspect key} in update `#{inspect(kw)}`, " <>
    182                              "expected a keyword list"
    183     end
    184   end
    185 
    186   defp runtime_error!(value) do
    187     raise ArgumentError,
    188           "malformed update `#{inspect(value)}` in query expression, " <>
    189           "expected a keyword list with set/push/pop as keys with field-value pairs as values"
    190   end
    191 
    192   defp validate_op!(key) when key in @keys, do: :ok
    193   defp validate_op!(key), do: Builder.error! "unknown key `#{inspect(key)}` in update"
    194 
    195   # Out means the given type must be taken out of an array
    196   # It is the opposite of "left in right" in the query API.
    197   defp type_for_key(:push, type), do: {:out, type}
    198   defp type_for_key(:pull, type), do: {:out, type}
    199   defp type_for_key(_, type),     do: type
    200 end