Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

Commit 4d4cc5e

Browse files
committed
Use ArrayMaps to represent lists in intermediate result representation
1 parent fd18a53 commit 4d4cc5e

3 files changed

Lines changed: 76 additions & 5 deletions

File tree

lib/graphql/execution/executor.ex

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ defmodule GraphQL.Execution.Executor do
1212
alias GraphQL.Type.CompositeType
1313
alias GraphQL.Type.AbstractType
1414
alias GraphQL.Lang.AST.Nodes
15+
alias GraphQL.Util.ArrayMap
1516

1617
@type result_data :: {:ok, Map}
1718

@@ -45,17 +46,33 @@ defmodule GraphQL.Execution.Executor do
4546
case operation.operation do
4647
:query ->
4748
{context, result} = execute_fields(context, type, root_value, fields)
48-
{:ok, result, context.errors}
49+
{:ok, expand_array_maps(result), context.errors}
4950
:mutation ->
5051
{context, result} = execute_fields_serially(context, type, root_value, fields)
51-
{:ok, result, context.errors}
52+
{:ok, expand_array_maps(result), context.errors}
5253
:subscription ->
5354
{:error, "Subscriptions not currently supported"}
5455
_ ->
5556
{:error, "Can only execute queries, mutations and subscriptions"}
5657
end
5758
end
5859

60+
defp expand_array_maps(result) when is_list(result) do
61+
Enum.map(result, &expand_array_maps/1)
62+
end
63+
defp expand_array_maps(%ArrayMap{} = result) do
64+
Enum.reduce(Enum.sort(Map.keys(result.map)), [], fn(index, acc) ->
65+
[expand_array_maps(Map.get(result.map, index))] ++ acc
66+
end) |> Enum.reverse
67+
end
68+
defp expand_array_maps(result) when is_map(result) do
69+
Enum.reduce(result, %{}, fn({k, v}, acc) ->
70+
Map.put(acc, expand_array_maps(k), expand_array_maps(v))
71+
end)
72+
end
73+
defp expand_array_maps(result), do: result
74+
75+
5976
defp collect_selections(context, runtime_type, selection_set, field_fragment_map \\ %{fields: %{}, fragments: %{}}) do
6077
Enum.reduce selection_set[:selections], {context, field_fragment_map}, fn(selection, {context, field_fragment_map}) ->
6178
collect_selection(context, runtime_type, selection, field_fragment_map)
@@ -193,11 +210,11 @@ defmodule GraphQL.Execution.Executor do
193210

194211
@spec complete_value(%List{}, ExecutionContext.t, GraphQL.Document.t, any, any) :: map
195212
defp complete_value(%List{ofType: list_type}, context, field_asts, info, result) do
196-
{context, result} = Enum.reduce result, {context, []}, fn(item, {context, acc}) ->
213+
{context, value, _} = Enum.reduce result, {context, %ArrayMap{}, 0}, fn(item, {context, acc, count}) ->
197214
{context, value} = complete_value(unwrap_type(list_type), context, field_asts, info, item)
198-
{context, [value] ++ acc}
215+
{context, ArrayMap.put(acc, count, value), count + 1}
199216
end
200-
{context, Enum.reverse(result)}
217+
{context, value}
201218
end
202219

203220
defp complete_value(return_type, context, field_asts, info, result) when is_atom(return_type) do

lib/graphql/util/array_map.ex

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
# ArrayMap is used for representing lists in intermediate results.
3+
# This means the entire intermediate Executor result representation can
4+
# be manipulated with the Access protocol which will allow for patching
5+
# the entire structure in an ad-hoc manner. This is key to implementing
6+
# deferred resolvers.
7+
defmodule GraphQL.Util.ArrayMap do
8+
9+
@behaviour Access
10+
11+
defstruct map: %{}
12+
13+
def put(array_map, index, value) when is_integer(index) do
14+
%__MODULE__{ map: Map.put(array_map.map, index, value) }
15+
end
16+
17+
# Access behaviour
18+
def fetch(array_map, key) do
19+
case Access.fetch(array_map.map, key) do
20+
{:ok, value} -> %__MODULE__{map: value}
21+
:error -> :error
22+
end
23+
end
24+
25+
def get_and_update(array_map, key, list) do
26+
{value, map} = Access.get_and_update(array_map.map, key, list)
27+
{value, %__MODULE__{map: map}}
28+
end
29+
30+
def get(array_map, key, value) do
31+
map = Access.get(array_map.map, key, value)
32+
%__MODULE__{map: value}
33+
end
34+
35+
def pop(array_map, key) do
36+
{value, map} = Access.get(array_map.map, key)
37+
{value, %__MODULE__{map: map}}
38+
end
39+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
defmodule GraphQL.Util.ArrayMapTest do
3+
use ExUnit.Case, async: true
4+
5+
alias GraphQL.Util.ArrayMap
6+
7+
test "implements Access" do
8+
version_1 = %ArrayMap{map: %{0 => :zero, 1 => :one, 2 => :two}}
9+
version_2 = %ArrayMap{map: %{0 => :zero, 1 => :ONE, 2 => :two}}
10+
11+
version_1_updated = put_in(version_1, [1], :ONE)
12+
13+
assert version_2 == version_1_updated
14+
end
15+
end

0 commit comments

Comments
 (0)