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