Skip to content

Commit 0951213

Browse files
José Valimjosevalim
authored andcommitted
Introduce an AST for preprocessing + postprocessing
This reduces the complexity in the adapters, reduces the caching cost per entry and speeds up deserialization.
1 parent 82aeb45 commit 0951213

File tree

15 files changed

+510
-486
lines changed

15 files changed

+510
-486
lines changed

lib/ecto/adapter.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ defmodule Ecto.Adapter do
77
@type t :: module
88

99
@typedoc "Ecto.Query metadata fields (stored in cache)"
10-
@type query_meta :: %{prefix: binary | nil, sources: tuple, assocs: term,
11-
preloads: term, select: term, fields: [term]}
10+
@type query_meta :: %{prefix: binary | nil, sources: tuple, preloads: term, select: map}
1211

1312
@typedoc "Ecto.Schema metadata fields"
1413
@type schema_meta :: %{source: source, schema: atom, context: term, autogenerate_id: {atom, :id | :binary_id}}
@@ -20,7 +19,7 @@ defmodule Ecto.Adapter do
2019
@type returning :: [atom]
2120
@type prepared :: term
2221
@type cached :: term
23-
@type process :: (field :: Macro.t, value :: term, context :: term -> term)
22+
@type process :: (term -> term)
2423
@type autogenerate_id :: {field :: atom, type :: :id | :binary_id, value :: term} | nil
2524
@type on_conflict :: {:raise, list(), []} |
2625
{:nothing, list(), [atom]} |

lib/ecto/adapters/mysql/connection.ex

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,10 @@ if Code.ensure_loaded?(Mariaex) do
338338
[name, ?. | quote_name(field)]
339339
end
340340

341-
defp expr({:&, _, [idx, fields, _counter]}, sources, query) do
342-
{source, name, schema} = elem(sources, idx)
343-
if is_nil(schema) and is_nil(fields) do
344-
error!(query, "MySQL does not support selecting all fields from #{source} without a schema. " <>
345-
"Please specify a schema or specify exactly which fields you want to select")
346-
end
347-
intersperse_map(fields, ", ", &[name, ?. | quote_name(&1)])
341+
defp expr({:&, _, [idx]}, sources, query) do
342+
{source, _name, _schema} = elem(sources, idx)
343+
error!(query, "MySQL does not support selecting all fields from #{source} without a schema. " <>
344+
"Please specify a schema or specify exactly which fields you want to select")
348345
end
349346

350347
defp expr({:in, _, [_left, []]}, _sources, _query) do
@@ -377,8 +374,8 @@ if Code.ensure_loaded?(Mariaex) do
377374
["NOT (", expr(expr, sources, query), ?)]
378375
end
379376

380-
defp expr(%Ecto.SubQuery{query: query, fields: fields}, _sources, _query) do
381-
query.select.fields |> put_in(fields) |> all()
377+
defp expr(%Ecto.SubQuery{query: query}, _sources, _query) do
378+
all(query)
382379
end
383380

384381
defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do

lib/ecto/adapters/postgres/connection.ex

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -418,13 +418,10 @@ if Code.ensure_loaded?(Postgrex) do
418418
quote_qualified_name(field, sources, idx)
419419
end
420420

421-
defp expr({:&, _, [idx, fields, _counter]}, sources, query) do
422-
{source, name, schema} = elem(sources, idx)
423-
if is_nil(schema) and is_nil(fields) do
424-
error!(query, "PostgreSQL does not support selecting all fields from #{source} without a schema. " <>
425-
"Please specify a schema or specify exactly which fields you want to select")
426-
end
427-
intersperse_map(fields, ", ", &[name, ?. | quote_name(&1)])
421+
defp expr({:&, _, [idx]}, sources, query) do
422+
{source, _name, _schema} = elem(sources, idx)
423+
error!(query, "PostgreSQL does not support selecting all fields from #{source} without a schema. " <>
424+
"Please specify a schema or specify exactly which fields you want to select")
428425
end
429426

430427
defp expr({:in, _, [_left, []]}, _sources, _query) do
@@ -452,8 +449,8 @@ if Code.ensure_loaded?(Postgrex) do
452449
["NOT (", expr(expr, sources, query), ?)]
453450
end
454451

455-
defp expr(%Ecto.SubQuery{query: query, fields: fields}, _sources, _query) do
456-
query.select.fields |> put_in(fields) |> all()
452+
defp expr(%Ecto.SubQuery{query: query}, _sources, _query) do
453+
all(query)
457454
end
458455

459456
defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do

lib/ecto/adapters/sql.ex

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -388,32 +388,15 @@ defmodule Ecto.Adapters.SQL do
388388
do_execute(repo, meta, prepared, params, mapper, put_source(opts, meta))
389389
end
390390

391-
defp do_execute(repo, _meta, {:cache, update, {id, prepared}}, params, nil, opts) do
392-
execute_and_cache(repo, id, update, prepared, params, nil, opts)
393-
end
394-
395-
defp do_execute(repo, %{fields: fields, sources: sources}, {:cache, update, {id, prepared}}, params, process, opts) do
396-
mapper = &process_row(&1, process, fields, sources)
391+
defp do_execute(repo, _meta, {:cache, update, {id, prepared}}, params, mapper, opts) do
397392
execute_and_cache(repo, id, update, prepared, params, mapper, opts)
398393
end
399394

400-
defp do_execute(repo, _meta, {:cached, reset, {id, cached}}, params, nil, opts) do
401-
execute_or_reset(repo, id, reset, cached, params, nil, opts)
402-
end
403-
404-
defp do_execute(repo, %{fields: fields, sources: sources}, {:cached, reset, {id, cached}}, params, process, opts) do
405-
mapper = &process_row(&1, process, fields, sources)
395+
defp do_execute(repo, _meta, {:cached, reset, {id, cached}}, params, mapper, opts) do
406396
execute_or_reset(repo, id, reset, cached, params, mapper, opts)
407397
end
408398

409-
defp do_execute(repo, _meta, {:nocache, {_id, prepared}}, params, nil, opts) do
410-
%{rows: rows, num_rows: num} =
411-
sql_call!(repo, :execute, [prepared], params, nil, opts)
412-
{num, rows}
413-
end
414-
415-
defp do_execute(repo, %{fields: fields, sources: sources}, {:nocache, {_id, prepared}}, params, process, opts) do
416-
mapper = &process_row(&1, process, fields, sources)
399+
defp do_execute(repo, _meta, {:nocache, {_id, prepared}}, params, mapper, opts) do
417400
%{rows: rows, num_rows: num} =
418401
sql_call!(repo, :execute, [prepared], params, mapper, opts)
419402
{num, rows}
@@ -491,30 +474,15 @@ defmodule Ecto.Adapters.SQL do
491474
do_stream(repo, meta, prepared, params, mapper, put_source(opts, meta))
492475
end
493476

494-
def do_stream(repo, _meta, {:cache, _, {_, prepared}}, params, nil, opts) do
495-
prepare_stream(repo, prepared, params, nil, opts)
496-
end
497-
498-
def do_stream(repo, %{fields: fields, sources: sources}, {:cache, _, {_, prepared}}, params, process, opts) do
499-
mapper = &process_row(&1, process, fields, sources)
477+
def do_stream(repo, _meta, {:cache, _, {_, prepared}}, params, mapper, opts) do
500478
prepare_stream(repo, prepared, params, mapper, opts)
501479
end
502480

503-
def do_stream(repo, _, {:cached, _, {_, cached}}, params, nil, opts) do
504-
prepare_stream(repo, String.Chars.to_string(cached), params, nil, opts)
505-
end
506-
507-
def do_stream(repo, %{fields: fields, sources: sources}, {:cached, _, {_, cached}}, params, process, opts) do
508-
mapper = &process_row(&1, process, fields, sources)
481+
def do_stream(repo, _, {:cached, _, {_, cached}}, params, mapper, opts) do
509482
prepare_stream(repo, String.Chars.to_string(cached), params, mapper, opts)
510483
end
511484

512-
def do_stream(repo, _meta, {:nocache, {_id, prepared}}, params, nil, opts) do
513-
prepare_stream(repo, prepared, params, nil, opts)
514-
end
515-
516-
def do_stream(repo, %{fields: fields, sources: sources}, {:nocache, {_id, prepared}}, params, process, opts) do
517-
mapper = &process_row(&1, process, fields, sources)
485+
def do_stream(repo, _meta, {:nocache, {_id, prepared}}, params, mapper, opts) do
518486
prepare_stream(repo, prepared, params, mapper, opts)
519487
end
520488

@@ -570,33 +538,6 @@ defmodule Ecto.Adapters.SQL do
570538
end
571539
end
572540

573-
defp process_row(row, process, fields, sources) do
574-
num_sources = tuple_size(sources)
575-
Enum.map_reduce(fields, row, fn
576-
{:&, _, [_, _, counter]} = field, acc when num_sources == 1 ->
577-
{val, rest} = Enum.split(acc, counter)
578-
{process.(field, val, nil), rest}
579-
{:&, _, [_, _, counter]} = field, acc ->
580-
case split_and_not_nil(acc, counter, true, []) do
581-
{nil, rest} -> {nil, rest}
582-
{val, rest} -> {process.(field, val, nil), rest}
583-
end
584-
field, [h|t] ->
585-
{process.(field, h, nil), t}
586-
end) |> elem(0)
587-
end
588-
589-
defp split_and_not_nil(rest, 0, true, _acc), do: {nil, rest}
590-
defp split_and_not_nil(rest, 0, false, acc), do: {:lists.reverse(acc), rest}
591-
592-
defp split_and_not_nil([nil|t], count, all_nil?, acc) do
593-
split_and_not_nil(t, count - 1, all_nil?, [nil|acc])
594-
end
595-
596-
defp split_and_not_nil([h|t], count, _all_nil?, acc) do
597-
split_and_not_nil(t, count - 1, false, [h|acc])
598-
end
599-
600541
## Transactions
601542

602543
@doc false

lib/ecto/query.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Ecto.SubQuery do
22
@moduledoc """
33
Stores subquery information.
44
"""
5-
defstruct [:query, :params, :fields, :meta, :cache]
5+
defstruct [:query, :params, :select, :cache]
66
end
77

88
defmodule Ecto.Query do

0 commit comments

Comments
 (0)