zf

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

select.ex (16770B)


      1 import Kernel, except: [apply: 2]
      2 
      3 defmodule Ecto.Query.Builder.Select do
      4   @moduledoc false
      5 
      6   alias Ecto.Query.Builder
      7 
      8   @doc """
      9   Escapes a select.
     10 
     11   It allows tuples, lists and variables at the top level. Inside the
     12   tuples and lists query expressions are allowed.
     13 
     14   ## Examples
     15 
     16       iex> escape({1, 2}, [], __ENV__)
     17       {{:{}, [], [:{}, [], [1, 2]]}, {[], %{take: %{}, subqueries: [], aliases: %{}}}}
     18 
     19       iex> escape([1, 2], [], __ENV__)
     20       {[1, 2], {[], %{take: %{}, subqueries: [], aliases: %{}}}}
     21 
     22       iex> escape(quote(do: x), [x: 0], __ENV__)
     23       {{:{}, [], [:&, [], [0]]}, {[], %{take: %{}, subqueries: [], aliases: %{}}}}
     24 
     25   """
     26   @spec escape(Macro.t, Keyword.t, Macro.Env.t) :: {Macro.t, {list, %{take: map, subqueries: list}}}
     27   def escape(atom, _vars, _env)
     28       when is_atom(atom) and not is_boolean(atom) and atom != nil do
     29     Builder.error! """
     30     #{inspect(atom)} is not a valid query expression, :select expects a query expression or a list of fields
     31     """
     32   end
     33 
     34   def escape(other, vars, env) do
     35     cond do
     36       take?(other) ->
     37         {
     38           {:{}, [], [:&, [], [0]]},
     39           {[], %{take: %{0 => {:any, Macro.expand(other, env)}}, subqueries: [], aliases: %{}}}
     40         }
     41 
     42       maybe_take?(other) ->
     43         Builder.error! """
     44         Cannot mix fields with interpolations, such as: `select: [:foo, ^:bar, :baz]`. \
     45         Instead interpolate all fields at once, such as: `select: ^[:foo, :bar, :baz]`. \
     46         Got: #{Macro.to_string(other)}.
     47         """
     48 
     49       true ->
     50         {expr, {params, acc}} = escape(other, {[], %{take: %{}, subqueries: [], aliases: %{}}}, vars, env)
     51         acc = %{acc | subqueries: Enum.reverse(acc.subqueries)}
     52         {expr, {params, acc}}
     53     end
     54   end
     55 
     56   # Tuple
     57   defp escape({left, right}, params_acc, vars, env) do
     58     escape({:{}, [], [left, right]}, params_acc, vars, env)
     59   end
     60 
     61   # Tuple
     62   defp escape({:{}, _, list}, params_acc, vars, env) do
     63     {list, params_acc} = Enum.map_reduce(list, params_acc, &escape(&1, &2, vars, env))
     64     expr = {:{}, [], [:{}, [], list]}
     65     {expr, params_acc}
     66   end
     67 
     68   # Struct
     69   defp escape({:%, _, [name, map]}, params_acc, vars, env) do
     70     name = Macro.expand(name, env)
     71     {escaped_map, params_acc} = escape(map, params_acc, vars, env)
     72     {{:{}, [], [:%, [], [name, escaped_map]]}, params_acc}
     73   end
     74 
     75   # Map
     76   defp escape({:%{}, _, [{:|, _, [data, pairs]}]}, params_acc, vars, env) do
     77     {data, params_acc} = escape(data, params_acc, vars, env)
     78     {pairs, params_acc} = escape_pairs(pairs, params_acc, vars, env)
     79     {{:{}, [], [:%{}, [], [{:{}, [], [:|, [], [data, pairs]]}]]}, params_acc}
     80   end
     81 
     82   # Merge
     83   defp escape({:merge, _, [left, {kind, _, _} = right]}, params_acc, vars, env)
     84        when kind in [:%{}, :map] do
     85     {left, params_acc} = escape(left, params_acc, vars, env)
     86     {right, params_acc} = escape(right, params_acc, vars, env)
     87     {{:{}, [], [:merge, [], [left, right]]}, params_acc}
     88   end
     89 
     90   defp escape({:merge, _, [_left, right]}, _params_acc, _vars, _env) do
     91     Builder.error! "expected the second argument of merge/2 in select to be a map, got: `#{Macro.to_string(right)}`"
     92   end
     93 
     94   # Map
     95   defp escape({:%{}, _, pairs}, params_acc, vars, env) do
     96     {pairs, params_acc} = escape_pairs(pairs, params_acc, vars, env)
     97     {{:{}, [], [:%{}, [], pairs]}, params_acc}
     98   end
     99 
    100   # List
    101   defp escape(list, params_acc, vars, env) when is_list(list) do
    102     Enum.map_reduce(list, params_acc, &escape(&1, &2, vars, env))
    103   end
    104 
    105   # map/struct(var, [:foo, :bar])
    106   defp escape({tag, _, [{var, _, context}, fields]}, {params, acc}, vars, env)
    107        when tag in [:map, :struct] and is_atom(var) and is_atom(context) do
    108     taken = escape_fields(fields, tag, env)
    109     expr = Builder.escape_var!(var, vars)
    110     acc = add_take(acc, Builder.find_var!(var, vars), {tag, taken})
    111     {expr, {params, acc}}
    112   end
    113 
    114   # aliased values
    115   defp escape({:selected_as, _, [expr, name]}, {params, acc}, vars, env) when is_atom(name) do
    116     {escaped, {params, acc}} = Builder.escape(expr, :any, {params, acc}, vars, env)
    117     expr = {:{}, [], [:selected_as, [], [escaped, name]]}
    118     aliases = Builder.add_select_alias(acc.aliases, name)
    119     {expr, {params, %{acc | aliases: aliases}}}
    120   end
    121 
    122   defp escape({:selected_as, _, [_expr, name]}, {_params, _acc}, _vars, _env) do
    123     Builder.error! "selected_as/2 expects `name` to be an atom, got `#{inspect(name)}`"
    124   end
    125 
    126   defp escape(expr, params_acc, vars, env) do
    127     Builder.escape(expr, :any, params_acc, vars, {env, &escape_expansion/5})
    128   end
    129 
    130   defp escape_expansion(expr, _type, params_acc, vars, env) do
    131     escape(expr, params_acc, vars, env)
    132   end
    133 
    134   defp escape_pairs(pairs, params_acc, vars, env) do
    135     Enum.map_reduce(pairs, params_acc, fn {k, v}, acc ->
    136       {k, acc} = escape_key(k, acc, vars, env)
    137       {v, acc} = escape(v, acc, vars, env)
    138       {{k, v}, acc}
    139     end)
    140   end
    141 
    142   defp escape_key(k, params_acc, _vars, _env) when is_atom(k) do
    143     {k, params_acc}
    144   end
    145 
    146   defp escape_key(k, params_acc, vars, env) do
    147     escape(k, params_acc, vars, env)
    148   end
    149 
    150   defp escape_fields({:^, _, [interpolated]}, tag, _env) do
    151     quote do
    152       Ecto.Query.Builder.Select.fields!(unquote(tag), unquote(interpolated))
    153     end
    154   end
    155   defp escape_fields(expr, tag, env) do
    156     case Macro.expand(expr, env) do
    157       fields when is_list(fields) ->
    158         fields
    159       _ ->
    160         Builder.error!(
    161           "`#{tag}/2` in `select` expects either a literal or " <>
    162             "an interpolated (1) list of atom fields, (2) dynamic, or " <>
    163             "(3) map with dynamic values"
    164         )
    165     end
    166   end
    167 
    168   @doc """
    169   Called at runtime to verify a field.
    170   """
    171   def fields!(tag, fields) do
    172     if take?(fields) do
    173       fields
    174     else
    175       raise ArgumentError,
    176         "expected a list of fields in `#{tag}/2` inside `select`, got: `#{inspect fields}`"
    177     end
    178   end
    179 
    180   # atom list sigils
    181   defp take?({name, _, [_, modifiers]}) when name in ~w(sigil_w sigil_W)a do
    182     ?a in modifiers
    183   end
    184 
    185   defp take?(fields) do
    186     is_list(fields) and Enum.all?(fields, fn
    187       {k, v} when is_atom(k) -> take?(List.wrap(v))
    188       k when is_atom(k) -> true
    189       _ -> false
    190     end)
    191   end
    192 
    193   defp maybe_take?(fields) do
    194     is_list(fields) and Enum.any?(fields, fn
    195       {k, v} when is_atom(k) -> maybe_take?(List.wrap(v))
    196       k when is_atom(k) -> true
    197       _ -> false
    198     end)
    199   end
    200 
    201   @doc """
    202   Called at runtime for interpolated/dynamic selects.
    203   """
    204   def select!(kind, query, fields, file, line) when is_map(fields) do
    205     {expr, {params, subqueries, aliases, _count}} = expand_nested(fields, {[], [], %{}, 0}, query)
    206 
    207     %Ecto.Query.SelectExpr{
    208       expr: expr,
    209       params: Enum.reverse(params),
    210       subqueries: Enum.reverse(subqueries),
    211       aliases: aliases,
    212       file: file,
    213       line: line
    214     }
    215     |> apply_or_merge(kind, query)
    216   end
    217 
    218   def select!(kind, query, fields, file, line) do
    219     take = %{0 => {:any, fields!(:select, fields)}}
    220 
    221     %Ecto.Query.SelectExpr{expr: {:&, [], [0]}, take: take, file: file, line: line}
    222     |> apply_or_merge(kind, query)
    223   end
    224 
    225   defp apply_or_merge(select, kind, query) do
    226     if kind == :select do
    227       apply(query, select)
    228     else
    229       merge(query, select)
    230     end
    231   end
    232 
    233   defp expand_nested(%Ecto.Query.DynamicExpr{} = dynamic, {params, subqueries, aliases, count}, query) do
    234     {expr, params, subqueries, aliases, count} =
    235       Ecto.Query.Builder.Dynamic.partially_expand(query, dynamic, params, subqueries, aliases, count)
    236 
    237     {expr, {params, subqueries, aliases, count}}
    238   end
    239 
    240   defp expand_nested(%Ecto.SubQuery{} = subquery, {params, subqueries, aliases, count}, _query) do
    241     index = length(subqueries)
    242     # used both in ast and in parameters, as a placeholder.
    243     expr = {:subquery, index}
    244     params = [expr | params]
    245     subqueries = [subquery | subqueries]
    246     count = count + 1
    247 
    248     {expr, {params, subqueries, aliases, count}}
    249   end
    250 
    251   defp expand_nested(%type{} = fields, acc, query) do
    252     {fields, acc} = fields |> Map.from_struct() |> expand_nested(acc, query)
    253     {{:%, [], [type, fields]}, acc}
    254   end
    255 
    256   defp expand_nested(fields, acc, query) when is_map(fields) do
    257     {fields, acc} = fields |> Enum.map_reduce(acc, &expand_nested_pair(&1, &2, query))
    258     {{:%{}, [], fields}, acc}
    259   end
    260 
    261   defp expand_nested(invalid, _acc, query) when is_list(invalid) or is_tuple(invalid) do
    262     raise Ecto.QueryError,
    263       query: query,
    264       message:
    265         "Interpolated map values in :select can only be " <>
    266           "maps, structs, dynamics, subqueries and literals. Got #{inspect(invalid)}"
    267   end
    268 
    269   defp expand_nested(other, acc, _query) do
    270     {other, acc}
    271   end
    272 
    273   defp expand_nested_pair({key, val}, acc, query) do
    274     {val, acc} = expand_nested(val, acc, query)
    275     {{key, val}, acc}
    276   end
    277 
    278   @doc """
    279   Builds a quoted expression.
    280 
    281   The quoted expression should evaluate to a query at runtime.
    282   If possible, it does all calculations at compile time to avoid
    283   runtime work.
    284   """
    285   @spec build(:select | :merge, Macro.t, [Macro.t], Macro.t, Macro.Env.t) :: Macro.t
    286 
    287   def build(kind, query, _binding, {:^, _, [var]}, env) do
    288     quote do
    289       Ecto.Query.Builder.Select.select!(unquote(kind), unquote(query), unquote(var),
    290                                         unquote(env.file), unquote(env.line))
    291     end
    292   end
    293 
    294   def build(kind, query, binding, expr, env) do
    295     {query, binding} = Builder.escape_binding(query, binding, env)
    296     {expr, {params, acc}} = escape(expr, binding, env)
    297     params = Builder.escape_params(params)
    298     take = {:%{}, [], Map.to_list(acc.take)}
    299     aliases = {:%{}, [], Map.to_list(acc.aliases)}
    300 
    301     select = quote do: %Ecto.Query.SelectExpr{
    302                          expr: unquote(expr),
    303                          params: unquote(params),
    304                          file: unquote(env.file),
    305                          line: unquote(env.line),
    306                          take: unquote(take),
    307                          subqueries: unquote(acc.subqueries),
    308                          aliases: unquote(aliases)}
    309 
    310     if kind == :select do
    311       Builder.apply_query(query, __MODULE__, [select], env)
    312     else
    313       quote do
    314         query = unquote(query)
    315         Builder.Select.merge(query, unquote(select))
    316       end
    317     end
    318   end
    319 
    320   @doc """
    321   The callback applied by `build/5` to build the query.
    322   """
    323   @spec apply(Ecto.Queryable.t, term) :: Ecto.Query.t
    324   def apply(%Ecto.Query{select: nil} = query, expr) do
    325     %{query | select: expr}
    326   end
    327   def apply(%Ecto.Query{}, _expr) do
    328     Builder.error! "only one select expression is allowed in query"
    329   end
    330   def apply(query, expr) do
    331     apply(Ecto.Queryable.to_query(query), expr)
    332   end
    333 
    334   @doc """
    335   The callback applied by `build/5` when merging.
    336   """
    337   def merge(%Ecto.Query{select: nil} = query, new_select) do
    338     merge(query, new_select, {:&, [], [0]}, [], [], %{}, %{}, new_select)
    339   end
    340   def merge(%Ecto.Query{select: old_select} = query, new_select) do
    341     %{expr: old_expr, params: old_params, subqueries: old_subqueries, take: old_take, aliases: old_aliases} = old_select
    342     merge(query, old_select, old_expr, old_params, old_subqueries, old_take, old_aliases, new_select)
    343   end
    344   def merge(query, expr) do
    345     merge(Ecto.Queryable.to_query(query), expr)
    346   end
    347 
    348   defp merge(query, select, old_expr, old_params, old_subqueries, old_take, old_aliases, new_select) do
    349     %{expr: new_expr, params: new_params, subqueries: new_subqueries, take: new_take, aliases: new_aliases} = new_select
    350 
    351     new_expr =
    352       new_expr
    353       |> Ecto.Query.Builder.bump_interpolations(old_params)
    354       |> Ecto.Query.Builder.bump_subqueries(old_subqueries)
    355 
    356     expr =
    357       case {classify_merge(old_expr, old_take), classify_merge(new_expr, new_take)} do
    358         {_, _} when old_expr == new_expr ->
    359           new_expr
    360 
    361         {{:source, meta, ix}, {:source, _, ix}} ->
    362           {:&, meta, [ix]}
    363 
    364         {{:struct, meta, name, old_fields}, {:map, _, new_fields}} when old_params == [] ->
    365           cond do
    366             new_fields == [] ->
    367               old_expr
    368 
    369             Keyword.keyword?(old_fields) and Keyword.keyword?(new_fields) ->
    370               {:%, meta, [name, {:%{}, meta, Keyword.merge(old_fields, new_fields)}]}
    371 
    372             true ->
    373               {:merge, [], [old_expr, new_expr]}
    374           end
    375 
    376         {{:map, meta, old_fields}, {:map, _, new_fields}} when old_params == [] ->
    377           cond do
    378             old_fields == [] ->
    379               new_expr
    380 
    381             new_fields == [] ->
    382               old_expr
    383 
    384             Keyword.keyword?(old_fields) and Keyword.keyword?(new_fields) ->
    385               {:%{}, meta, Keyword.merge(old_fields, new_fields)}
    386 
    387             true ->
    388               {:merge, [], [old_expr, new_expr]}
    389           end
    390 
    391         {_, {:map, _, _}} ->
    392           {:merge, [], [old_expr, new_expr]}
    393 
    394         {_, _} ->
    395           message = """
    396           cannot select_merge #{merge_argument_to_error(new_expr, query)} into \
    397           #{merge_argument_to_error(old_expr, query)}, those select expressions \
    398           are incompatible. You can only select_merge:
    399 
    400             * a source (such as post) with another source (of the same type)
    401             * a source (such as post) with a map
    402             * a struct with a map
    403             * a map with a map
    404 
    405           Incompatible merge found
    406           """
    407 
    408           raise Ecto.QueryError, query: query, message: message
    409       end
    410 
    411     select = %{
    412       select | expr: expr,
    413                params: old_params ++ bump_subquery_params(new_params, old_subqueries),
    414                subqueries: old_subqueries ++ new_subqueries,
    415                take: merge_take(query.from.source, old_expr, old_take, new_take),
    416                aliases: merge_aliases(old_aliases, new_aliases)
    417     }
    418 
    419     %{query | select: select}
    420   end
    421 
    422   defp classify_merge({:&, meta, [ix]}, take) when is_integer(ix) do
    423     case take do
    424       %{^ix => {:map, _}} -> {:map, meta, :runtime}
    425       _ -> {:source, meta, ix}
    426     end
    427   end
    428 
    429   defp classify_merge({:%, meta, [name, {:%{}, _, fields}]}, _take)
    430        when fields == [] or tuple_size(hd(fields)) == 2 do
    431     {:struct, meta, name, fields}
    432   end
    433 
    434   defp classify_merge({:%{}, meta, fields}, _take)
    435        when fields == [] or tuple_size(hd(fields)) == 2 do
    436     {:map, meta, fields}
    437   end
    438 
    439   defp classify_merge({:%{}, meta, _}, _take) do
    440     {:map, meta, :runtime}
    441   end
    442 
    443   defp classify_merge(_, _take) do
    444     :error
    445   end
    446 
    447   defp merge_argument_to_error({:&, _, [0]}, %{from: %{source: {source, alias}}}) do
    448     "source #{inspect(source || alias)}"
    449   end
    450 
    451   defp merge_argument_to_error({:&, _, [ix]}, _query) do
    452     "join (at position #{ix})"
    453   end
    454 
    455   defp merge_argument_to_error(other, _query) do
    456     Macro.to_string(other)
    457   end
    458 
    459   defp add_take(acc, key, value) do
    460     take = Map.update(acc.take, key, value, &merge_take_kind_and_fields(key, &1, value))
    461     %{acc | take: take}
    462   end
    463 
    464   defp bump_subquery_params(new_params, old_subqueries) do
    465     len = length(old_subqueries)
    466 
    467     Enum.map(new_params, fn
    468       {:subquery, counter} -> {:subquery, len + counter}
    469       other -> other
    470     end)
    471   end
    472 
    473   defp merge_take(source, old_expr, %{} = old_take, %{} = new_take) do
    474     Enum.reduce(new_take, old_take, fn {binding, {new_kind, new_fields} = new_value}, acc ->
    475       case acc do
    476         %{^binding => old_value} ->
    477           Map.put(acc, binding, merge_take_kind_and_fields(binding, old_value, new_value))
    478 
    479         %{} ->
    480           # If merging with a schema, add the schema's query fields. This comes in handy if the user
    481           # is merging fields with load_in_query = false.
    482           # If merging with a schemaless source, do nothing so the planner can take all the fields.
    483           case {old_expr, source} do
    484             {{:&, _, [^binding]}, {_source, schema}} when not is_nil(schema) ->
    485               Map.put(acc, binding, {new_kind, Enum.uniq(new_fields ++ schema.__schema__(:query_fields))})
    486 
    487             {{:&, _, [^binding]}, _} ->
    488                 acc
    489 
    490             _ ->
    491               Map.put(acc, binding, new_value)
    492           end
    493       end
    494     end)
    495   end
    496 
    497   defp merge_take_kind_and_fields(binding, {old_kind, old_fields}, {new_kind, new_fields}) do
    498     {merge_take_kind(binding, old_kind, new_kind), Enum.uniq(old_fields ++ new_fields)}
    499   end
    500 
    501   defp merge_take_kind(_, kind, kind), do: kind
    502   defp merge_take_kind(_, :any, kind), do: kind
    503   defp merge_take_kind(_, kind, :any), do: kind
    504   defp merge_take_kind(binding, old, new) do
    505     Builder.error! "cannot select_merge because the binding at position #{binding} " <>
    506                    "was previously specified as a `#{old}` and later as `#{new}`"
    507   end
    508 
    509   defp merge_aliases(old_aliases, new_aliases) do
    510     Enum.reduce(new_aliases, old_aliases, fn {alias, _}, aliases ->
    511       Builder.add_select_alias(aliases, alias)
    512     end)
    513   end
    514 end