elasticgraph-graphql 0.19.3.0 → 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/lib/elastic_graph/graphql/aggregation/query_adapter.rb +6 -38
  3. data/lib/elastic_graph/graphql/aggregation/resolvers/node.rb +2 -2
  4. data/lib/elastic_graph/graphql/aggregation/resolvers/relay_connection_builder.rb +8 -7
  5. data/lib/elastic_graph/graphql/aggregation/resolvers/sub_aggregations.rb +3 -3
  6. data/lib/elastic_graph/graphql/config.rb +0 -15
  7. data/lib/elastic_graph/graphql/datastore_query.rb +53 -13
  8. data/lib/elastic_graph/graphql/datastore_response/document.rb +4 -0
  9. data/lib/elastic_graph/graphql/filtering/boolean_query.rb +23 -6
  10. data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +62 -22
  11. data/lib/elastic_graph/graphql/query_adapter/filters.rb +10 -11
  12. data/lib/elastic_graph/graphql/query_adapter/requested_fields.rb +21 -21
  13. data/lib/elastic_graph/graphql/resolvers/get_record_field_value.rb +1 -2
  14. data/lib/elastic_graph/graphql/resolvers/graphql_adapter_builder.rb +5 -9
  15. data/lib/elastic_graph/graphql/resolvers/nested_relationships.rb +1 -3
  16. data/lib/elastic_graph/graphql/resolvers/nested_relationships_source.rb +7 -74
  17. data/lib/elastic_graph/graphql/resolvers/relay_connection/array_adapter.rb +17 -16
  18. data/lib/elastic_graph/graphql/resolvers/relay_connection/generic_adapter.rb +6 -4
  19. data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb +66 -2
  20. data/lib/elastic_graph/graphql/resolvers/relay_connection.rb +3 -3
  21. data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +3 -3
  22. data/lib/elastic_graph/graphql/schema/field.rb +1 -1
  23. data/lib/elastic_graph/graphql/schema/type.rb +4 -0
  24. data/lib/elastic_graph/graphql.rb +1 -7
  25. metadata +25 -25
@@ -41,22 +41,21 @@ module ElasticGraph
41
41
  end
42
42
 
43
43
  def query_attributes_for(field:, lookahead:)
44
- index_field_paths =
45
- field.type
46
- .unwrap_fully
47
- .search_index_definitions
48
- .flat_map { |index_def| index_def.fields_by_path.keys }
49
- .to_set
50
-
51
44
  attributes =
52
45
  if field.type.relay_connection?
46
+ highlights = lookahead
47
+ .selection(@schema.element_names.edges)
48
+ .selection(@schema.element_names.highlights)
49
+
53
50
  {
54
51
  individual_docs_needed: pagination_fields_need_individual_docs?(lookahead),
55
- requested_fields: requested_fields_under(relay_connection_node_from(lookahead), index_field_paths)
52
+ requested_fields: requested_fields_under(relay_connection_node_from(lookahead)),
53
+ request_all_highlights: requesting_all_highlights?(lookahead),
54
+ requested_highlights: requested_fields_under(highlights)
56
55
  }
57
56
  else
58
57
  {
59
- requested_fields: requested_fields_under(lookahead, index_field_paths)
58
+ requested_fields: requested_fields_under(lookahead)
60
59
  }
61
60
  end
62
61
 
@@ -75,9 +74,9 @@ module ElasticGraph
75
74
  # can ignore its foreign key; but when we are determining requested fields for a parent type,
76
75
  # we need to identify the foreign key to request from the datastore, without recursing into
77
76
  # its children.
78
- def requested_fields_under(node, index_field_paths, path_prefix: "")
77
+ def requested_fields_under(node, path_prefix: "")
79
78
  fields = node.selections.flat_map do |child|
80
- requested_fields_for(child, index_field_paths, path_prefix: path_prefix)
79
+ requested_fields_for(child, path_prefix: path_prefix)
81
80
  end
82
81
 
83
82
  fields << "#{path_prefix}__typename" if field_for(node.field)&.type&.abstract?
@@ -86,22 +85,14 @@ module ElasticGraph
86
85
 
87
86
  # Identifies the fields we need to fetch from the datastore for the given node,
88
87
  # and recursing into the fields under it as needed.
89
- def requested_fields_for(node, index_field_paths, path_prefix:)
88
+ def requested_fields_for(node, path_prefix:)
90
89
  return [] if graphql_dynamic_field?(node)
91
90
 
92
91
  # @type var field: Schema::Field
93
92
  field = _ = field_for(node.field)
94
93
 
95
94
  if field.type.embedded_object?
96
- field_path = "#{path_prefix}#{field.name_in_index}"
97
- if index_field_paths.include?(field_path)
98
- # If we've reached a path to a scalar field, we should just return it instead of recursing.
99
- # This allows an object field to use a `name_in_index` of a scalar field which can be
100
- # necessary for some custom resolvers.
101
- [field_path]
102
- else
103
- requested_fields_under(node, index_field_paths, path_prefix: "#{field_path}.")
104
- end
95
+ requested_fields_under(node, path_prefix: "#{path_prefix}#{field.name_in_index}.")
105
96
  else
106
97
  field.index_field_names_for_resolution.map do |name|
107
98
  "#{path_prefix}#{name}"
@@ -142,6 +133,15 @@ module ElasticGraph
142
133
  lookahead.selects?(@schema.element_names.total_edge_count)
143
134
  end
144
135
 
136
+ def requesting_all_highlights?(lookahead)
137
+ all_highlights =
138
+ lookahead
139
+ .selection(@schema.element_names.edges)
140
+ .selection(@schema.element_names.all_highlights)
141
+
142
+ all_highlights.selects?(@schema.element_names.path) || all_highlights.selects?(@schema.element_names.snippets)
143
+ end
144
+
145
145
  def graphql_dynamic_field?(node)
146
146
  # As per https://spec.graphql.org/October2021/#sec-Objects,
147
147
  # > All fields defined within an Object type must not have a name which begins with "__"
@@ -16,7 +16,6 @@ module ElasticGraph
16
16
  # Responsible for fetching a single field value from a document.
17
17
  class GetRecordFieldValue
18
18
  def initialize(elasticgraph_graphql:, config:)
19
- @schema_element_names = elasticgraph_graphql.runtime_metadata.schema_element_names
20
19
  end
21
20
 
22
21
  def resolve(field:, object:, args:, context:)
@@ -32,7 +31,7 @@ module ElasticGraph
32
31
  value = [] if value.nil? && field.type.list?
33
32
 
34
33
  if field.type.relay_connection?
35
- RelayConnection::ArrayAdapter.build(value, args, @schema_element_names, context)
34
+ RelayConnection::ArrayAdapter.build(value, args, context)
36
35
  else
37
36
  value
38
37
  end
@@ -17,11 +17,7 @@ module ElasticGraph
17
17
  class GraphQLAdapterBuilder
18
18
  def initialize(runtime_metadata:, named_resolvers:, query_adapter:)
19
19
  @runtime_metadata = runtime_metadata
20
- @resolvers_by_name_and_field_config = named_resolvers.transform_values do |resolver_constructor|
21
- ::Hash.new do |hash, field_config|
22
- hash[field_config] = resolver_constructor.call(field_config)
23
- end
24
- end
20
+ @named_resolvers = named_resolvers
25
21
  @query_adapter = query_adapter
26
22
  end
27
23
 
@@ -46,10 +42,10 @@ module ElasticGraph
46
42
  def object_type_hash
47
43
  @runtime_metadata.object_types_by_name.filter_map do |type_name, type|
48
44
  fields_hash = type.graphql_fields_by_name.filter_map do |field_name, field|
49
- if (configured_resolver = field.resolver)
50
- resolver = @resolvers_by_name_and_field_config.fetch(configured_resolver.name) do
51
- raise Errors::SchemaError, "Resolver `#{configured_resolver.name}` (for `#{type_name}.#{field_name}`) cannot be found."
52
- end[configured_resolver.config]
45
+ if (resolver_name = field.resolver)
46
+ resolver = @named_resolvers.fetch(resolver_name) do
47
+ raise Errors::SchemaError, "Resolver `#{resolver_name}` (for `#{type_name}.#{field_name}`) cannot be found."
48
+ end
53
49
 
54
50
  resolver_lambda =
55
51
  if resolver.method(:resolve).parameters.include?([:keyreq, :lookahead])
@@ -23,7 +23,6 @@ module ElasticGraph
23
23
  @schema_element_names = elasticgraph_graphql.runtime_metadata.schema_element_names
24
24
  @logger = elasticgraph_graphql.logger
25
25
  @monotonic_clock = elasticgraph_graphql.monotonic_clock
26
- @resolver_mode = elasticgraph_graphql.config.nested_relationship_resolver_mode
27
26
  end
28
27
 
29
28
  def resolve(field:, object:, args:, context:, lookahead:)
@@ -41,8 +40,7 @@ module ElasticGraph
41
40
  NestedRelationshipsSource.execute_one(
42
41
  Array(id_or_ids).to_set,
43
42
  query:, join:, context:,
44
- monotonic_clock: @monotonic_clock,
45
- mode: @resolver_mode
43
+ monotonic_clock: @monotonic_clock
46
44
  )
47
45
 
48
46
  join.normalize_documents(initial_response) do |problem|
@@ -32,7 +32,7 @@ module ElasticGraph
32
32
  #
33
33
  # This optimization, when we can apply it, results in much less load on the datastore. In addition, it also helps to reduce
34
34
  # the amount of overhead imposed by ElasticGraph. Profiling has shown that significant overhead is incurred when we repeatedly
35
- # merge filters into a query (e.g. `query.merge_with(filters: [{id: {equal_to_any_of: [part.manufacturer_id]}}])` 10 times to
35
+ # merge filters into a query (e.g. `query.merge_with(internal_filters: [{id: {equal_to_any_of: [part.manufacturer_id]}}])` 10 times to
36
36
  # produce 10 different queries). This optimization also avoids that overhead.
37
37
  #
38
38
  # Note: while the comments discuss the examples in terms of _parent objects_, in the implementation, we deal with id sets.
@@ -60,7 +60,7 @@ module ElasticGraph
60
60
  # isn't particularly expensive, compared to needing to re-run an extra query.
61
61
  EXTRA_SIZE_MULTIPLIER = 4
62
62
 
63
- def initialize(query:, join:, context:, monotonic_clock:, mode:)
63
+ def initialize(query:, join:, context:, monotonic_clock:)
64
64
  @query = query
65
65
  @join = join
66
66
  @filter_id_field_name_path = @join.filter_id_field_name.split(".")
@@ -69,24 +69,15 @@ module ElasticGraph
69
69
  @schema_element_names = elastic_graph_schema.element_names
70
70
  @logger = elastic_graph_schema.logger
71
71
  @monotonic_clock = monotonic_clock
72
- @mode = mode
73
72
  end
74
73
 
75
74
  def fetch(id_sets)
76
75
  return fetch_original(id_sets) unless can_merge_filters?
77
-
78
- case @mode
79
- when :original
80
- fetch_original(id_sets)
81
- when :comparison
82
- fetch_comparison(id_sets)
83
- else
84
- fetch_optimized(id_sets)
85
- end
76
+ fetch_optimized(id_sets)
86
77
  end
87
78
 
88
- def self.execute_one(ids, query:, join:, context:, monotonic_clock:, mode:)
89
- context.dataloader.with(self, query:, join:, context:, monotonic_clock:, mode:).load(ids)
79
+ def self.execute_one(ids, query:, join:, context:, monotonic_clock:)
80
+ context.dataloader.with(self, query:, join:, context:, monotonic_clock:).load(ids)
90
81
  end
91
82
 
92
83
  private
@@ -116,52 +107,6 @@ module ElasticGraph
116
107
  fetch_via_separate_queries(id_sets, requested_fields: requested_fields)
117
108
  end
118
109
 
119
- def fetch_comparison(id_sets)
120
- # Note: we'd ideally run both versions of the logic in parallel, but our attempts to do that resulted in errors
121
- # because of the fiber context in which dataloaders run.
122
- original_duration_ms, original_results = time_duration do
123
- # In the `fetch_optimized` implementation, we request this extra field. We don't need it for
124
- # the original implementation (so `fetch_original` doesn't also request that field...) but for
125
- # the purposes of comparison we need to request it so that the document payloads will have the
126
- # same fields.
127
- #
128
- # Note: we don't add the requested field if we have only a single id set, in order to align with
129
- # the short-circuiting logic in `fetch_via_single_query_with_merged_filters`. Otherwise, any time
130
- # we have a single id set we always get reported differences which are not actually real!
131
- requested_fields = (id_sets.size > 1) ? [@join.filter_id_field_name] : [] # : ::Array[::String]
132
- fetch_original(id_sets, requested_fields: requested_fields)
133
- end
134
-
135
- optimized_duration_ms, optimized_results = time_duration do
136
- fetch_optimized(id_sets)
137
- end
138
-
139
- # To see if we got the same results we only look at the documents, because we expect differences outside
140
- # of the documents--for example, the `SearchResponse#metadata` will report different `took` values.
141
- got_same_results = original_results.map(&:documents) == optimized_results.map(&:documents)
142
- message = {
143
- "message_type" => "NestedRelationshipsComparisonResults",
144
- "field" => @join.field.description,
145
- "original_duration_ms" => original_duration_ms,
146
- "optimized_duration_ms" => optimized_duration_ms,
147
- "optimized_faster" => (optimized_duration_ms < original_duration_ms),
148
- "id_set_count" => id_sets.size,
149
- "total_id_count" => id_sets.reduce(:union).size,
150
- "got_same_results" => got_same_results
151
- }
152
-
153
- if got_same_results
154
- @logger.info(message)
155
- else
156
- @logger.error(message.merge({
157
- "original_documents" => loggable_results(original_results),
158
- "optimized_documents" => loggable_results(optimized_results)
159
- }))
160
- end
161
-
162
- original_results
163
- end
164
-
165
110
  # For "simple", document-based queries, we can safely merge filters. However, this cannot be done safely when the response
166
111
  # cannot safely be "pulled part" into the bits that apply to a particular set of ids for a parent object. Specifically:
167
112
  #
@@ -227,7 +172,7 @@ module ElasticGraph
227
172
 
228
173
  # First, we build a combined query with filters that account for all ids we are filtering on from all `id_sets`.
229
174
  filtered_query = @query.merge_with(
230
- filters: filters_for(id_sets.reduce(:union)),
175
+ internal_filters: filters_for(id_sets.reduce(:union)),
231
176
  requested_fields: [@join.filter_id_field_name],
232
177
  # We need to request a larger size than `@query` originally had. If the original size was `10` and we have
233
178
  # 5 sets of ids, then, at a minimum, we need to request 50 results (10 results for each id set).
@@ -271,7 +216,7 @@ module ElasticGraph
271
216
 
272
217
  def fetch_via_separate_queries(id_sets, requested_fields: [])
273
218
  queries = id_sets.map do |ids|
274
- @query.merge_with(filters: filters_for(ids), requested_fields: requested_fields)
219
+ @query.merge_with(internal_filters: filters_for(ids), requested_fields: requested_fields)
275
220
  end
276
221
 
277
222
  results = QuerySource.execute_many(queries, for_context: @context)
@@ -307,18 +252,6 @@ module ElasticGraph
307
252
  stop_time = @monotonic_clock.now_in_ms
308
253
  [stop_time - start_time, result]
309
254
  end
310
-
311
- # Converts the given list of responses into a format we can safely log when we are logging
312
- # response differences. We include the `id` (to identify the document) and the `hash` (so
313
- # we can tell if the payload of a document differed, without logging the contents of that
314
- # payload).
315
- def loggable_results(responses)
316
- responses.map do |response|
317
- response.documents.map do |doc|
318
- "#{doc.id} (hash: #{doc.hash})"
319
- end
320
- end
321
- end
322
255
  end
323
256
  end
324
257
  end
@@ -15,32 +15,33 @@ module ElasticGraph
15
15
  module RelayConnection
16
16
  # Relay connection adapter for an array. Implemented primarily by `GraphQL::Relay::ArrayConnection`;
17
17
  # here we just adapt it to the ElasticGraph internal resolver interface.
18
- class ArrayAdapter < ResolvableValue.new(:graphql_impl, :total_edge_count)
18
+ class ArrayAdapter < ResolvableValue.new(:graphql_impl)
19
19
  # `ResolvableValue.new` provides the following methods:
20
- # @dynamic initialize, graphql_impl, total_edge_count, schema_element_names
20
+ # @dynamic initialize, graphql_impl, schema
21
21
 
22
22
  # `def_delegators` provides the following methods:
23
23
  # @dynamic start_cursor, end_cursor, has_next_page, has_previous_page
24
24
  extend Forwardable
25
25
  def_delegators :graphql_impl, :start_cursor, :end_cursor, :has_next_page, :has_previous_page
26
26
 
27
- def self.build(nodes, args, schema_element_names, context)
28
- nodes ||= [] # : ::Array[untyped]
27
+ def self.build(nodes, args, context)
28
+ schema = context.fetch(:elastic_graph_schema)
29
+ schema_element_names = schema.element_names
30
+
29
31
  # ElasticGraph supports any schema elements (like a `first` argument) being renamed,
30
32
  # but `GraphQL::Relay::ArrayConnection` would not understand a renamed argument.
31
33
  # Here we map the args back to the canonical relay args so `ArrayConnection` can
32
34
  # understand them.
33
- #
34
- # `after` and `before` are encoded to convert them to the string form required by `ArrayConnection`.
35
- relay_args = {
36
- first: args[schema_element_names.first],
37
- after: args[schema_element_names.after]&.encode,
38
- last: args[schema_element_names.last],
39
- before: args[schema_element_names.before]&.encode
40
- }.compact
35
+ relay_args = [:first, :after, :last, :before].to_h do |arg_name|
36
+ [arg_name, args[schema_element_names.public_send(arg_name)]]
37
+ end.compact
38
+
39
+ graphql_impl = ::GraphQL::Pagination::ArrayConnection.new(nodes || [], context: context, **relay_args)
40
+ new(schema, graphql_impl)
41
+ end
41
42
 
42
- graphql_impl = ::GraphQL::Pagination::ArrayConnection.new(nodes, context: context, **relay_args)
43
- new(schema_element_names, graphql_impl, nodes.size)
43
+ def total_edge_count
44
+ graphql_impl.nodes.size
44
45
  end
45
46
 
46
47
  def page_info
@@ -49,7 +50,7 @@ module ElasticGraph
49
50
 
50
51
  def edges
51
52
  @edges ||= graphql_impl.nodes.map do |node|
52
- Edge.new(schema_element_names, graphql_impl, node)
53
+ Edge.new(schema, graphql_impl, node)
53
54
  end
54
55
  end
55
56
 
@@ -60,7 +61,7 @@ module ElasticGraph
60
61
  # Simple edge implementation for a node object.
61
62
  class Edge < ResolvableValue.new(:graphql_impl, :node)
62
63
  # `ResolvableValue.new` provides the following methods:
63
- # @dynamic initialize, graphql_impl, schema_element_names, node
64
+ # @dynamic initialize, graphql_impl, schema, node
64
65
 
65
66
  def cursor
66
67
  graphql_impl.cursor_for(node)
@@ -21,13 +21,15 @@ module ElasticGraph
21
21
  # Lambda that is used to convert a node to a sort value during truncation.
22
22
  :to_sort_value,
23
23
  # Gets an optional count of total edges.
24
- :get_total_edge_count
24
+ :get_total_edge_count,
25
+ # The class used for edges
26
+ :edge_class
25
27
  )
26
- # @dynamic initialize, with, schema_element_names, raw_nodes, paginator, to_sort_value, get_total_edge_count
28
+ # @dynamic initialize, with, schema, raw_nodes, paginator, to_sort_value, get_total_edge_count, edge_class
27
29
 
28
30
  def page_info
29
31
  @page_info ||= PageInfo.new(
30
- schema_element_names: schema_element_names,
32
+ schema: schema,
31
33
  before_truncation_nodes: before_truncation_nodes,
32
34
  edges: edges,
33
35
  paginator: paginator
@@ -39,7 +41,7 @@ module ElasticGraph
39
41
  end
40
42
 
41
43
  def edges
42
- @edges ||= nodes.map { |node| Edge.new(schema_element_names, node) }
44
+ @edges ||= nodes.map { |node| edge_class.new(schema, node) }
43
45
  end
44
46
 
45
47
  def nodes
@@ -14,14 +14,15 @@ module ElasticGraph
14
14
  module RelayConnection
15
15
  # Adapts an `DatastoreResponse::SearchResponse` to what the graphql gem expects for a relay connection.
16
16
  class SearchResponseAdapterBuilder
17
- def self.build_from(schema_element_names:, search_response:, query:)
17
+ def self.build_from(schema:, search_response:, query:)
18
18
  document_paginator = query.document_paginator
19
19
 
20
20
  GenericAdapter.new(
21
- schema_element_names: schema_element_names,
21
+ schema: schema,
22
22
  raw_nodes: search_response.to_a,
23
23
  paginator: document_paginator.paginator,
24
24
  get_total_edge_count: -> { search_response.total_document_count },
25
+ edge_class: DocumentEdge,
25
26
  to_sort_value: ->(document, decoded_cursor) do
26
27
  (_ = document).sort.zip(decoded_cursor.sort_values.values, document_paginator.sort).map do |from_document, from_cursor, sort_clause|
27
28
  DatastoreQuery::Paginator::SortValue.new(
@@ -34,6 +35,69 @@ module ElasticGraph
34
35
  )
35
36
  end
36
37
  end
38
+
39
+ class DocumentEdge < GenericAdapter::Edge
40
+ def highlights
41
+ @highlights ||= node.highlights.each_with_object({}) do |(path_string, snippets), highlights| # $::Hash[::String, untyped]
42
+ *object_fields, leaf_field = path_string.split(".")
43
+ leaf_hash = object_fields.reduce(highlights) { |accum, field| accum[field] ||= {} }
44
+ leaf_hash[leaf_field.to_s] = snippets
45
+ end
46
+ end
47
+
48
+ def all_highlights
49
+ @all_highlights ||= begin
50
+ document_type = schema.document_type_stored_in(node.index_definition_name)
51
+
52
+ node.highlights.filter_map do |path_string, snippets|
53
+ if (path = path_from(path_string, document_type))
54
+ SearchHighlight.new(schema, path, snippets)
55
+ end
56
+ end.sort_by(&:path)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def path_from(path_string, document_type)
63
+ type = document_type
64
+
65
+ # The GraphQL field name and `name_in_index` can be different. The datastore returns path segments using
66
+ # the `name_in_index` but we want to return the GraphQL field name, so here we translate.
67
+ path_string.split(".").map do |name_in_index|
68
+ fields = type.fields_by_name_in_index[name_in_index] || []
69
+
70
+ # It's possible (but pretty rare) for a single `name_in_index` to map to multiple GraphQL fields.
71
+ # We don't really have a basis for preferring one over another so we just use the first one here.
72
+ field = fields.first
73
+
74
+ # It's possible (but should be *very* rare) that `name_in_index` does not map to any GraphQL fields.
75
+ # Here's a situation where that could happen:
76
+ #
77
+ # * The schema has an `indexing_only: true` field.
78
+ # * A custom query interceptor (used via `elasticgraph-query_interceptor`) merges some `client_filters` into an intercepted
79
+ # query which filters on the indexing-only field.
80
+ #
81
+ # It would be more correct for the query interceptor to use `internal_filters` for that case, but in case we've
82
+ # run into this situation, logging a warning and hiding the highlight is the best we can do.
83
+ unless field
84
+ schema.logger.warn(
85
+ "Skipping SearchHighlight for #{document_type.name} #{node.id} which contains a path (#{path_string}) " \
86
+ "that does not map to any GraphQL field path."
87
+ )
88
+
89
+ return nil
90
+ end
91
+
92
+ type = field.type
93
+ field.name
94
+ end
95
+ end
96
+ end
97
+
98
+ class SearchHighlight < ResolvableValue.new(:path, :snippets)
99
+ # @dynamic initialize, path, snippets
100
+ end
37
101
  end
38
102
  end
39
103
  end
@@ -20,11 +20,11 @@ module ElasticGraph
20
20
  def self.maybe_wrap(search_response, field:, context:, lookahead:, query:)
21
21
  return search_response unless field.type.relay_connection?
22
22
 
23
- schema_element_names = context.fetch(:elastic_graph_schema).element_names
23
+ schema = context.fetch(:elastic_graph_schema)
24
24
 
25
25
  unless field.type.unwrap_fully.indexed_aggregation?
26
26
  return SearchResponseAdapterBuilder.build_from(
27
- schema_element_names: schema_element_names,
27
+ schema: schema,
28
28
  search_response: search_response,
29
29
  query: query
30
30
  )
@@ -32,7 +32,7 @@ module ElasticGraph
32
32
 
33
33
  agg_name = lookahead.ast_nodes.first&.alias || lookahead.name
34
34
  Aggregation::Resolvers::RelayConnectionBuilder.build_from_search_response(
35
- schema_element_names: schema_element_names,
35
+ schema: schema,
36
36
  search_response: search_response,
37
37
  query: Support::HashUtil.verbose_fetch(query.aggregations, agg_name)
38
38
  )
@@ -17,10 +17,10 @@ module ElasticGraph
17
17
  # and also has a corresponding method definition.
18
18
  module ResolvableValue
19
19
  # `MemoizableData.define` provides the following methods:
20
- # @dynamic schema_element_names
20
+ # @dynamic schema
21
21
 
22
22
  def self.new(*fields, &block)
23
- Support::MemoizableData.define(:schema_element_names, *fields) do
23
+ Support::MemoizableData.define(:schema, *fields) do
24
24
  # @implements ResolvableValueClass
25
25
  include ResolvableValue
26
26
  class_exec(&block) if block
@@ -41,7 +41,7 @@ module ElasticGraph
41
41
  end
42
42
 
43
43
  def canonical_name_for(name, element_type)
44
- schema_element_names.canonical_name_for(name) ||
44
+ schema.element_names.canonical_name_for(name) ||
45
45
  raise(Errors::SchemaError, "#{element_type} `#{name}` is not a defined schema element")
46
46
  end
47
47
  end
@@ -29,7 +29,7 @@ module ElasticGraph
29
29
  @computation_detail = runtime_metadata&.computation_detail
30
30
  @resolver = runtime_metadata&.resolver
31
31
  @name_in_index = runtime_metadata&.name_in_index || name
32
- @graphql_field.extras([:lookahead]) if resolvers_needing_lookahead.include?(@resolver&.name)
32
+ @graphql_field.extras([:lookahead]) if resolvers_needing_lookahead.include?(@resolver)
33
33
  end
34
34
 
35
35
  def type
@@ -126,6 +126,10 @@ module ElasticGraph
126
126
  raise Errors::NotFoundError, msg
127
127
  end
128
128
 
129
+ def fields_by_name_in_index
130
+ @fields_by_name_in_index ||= @fields_by_name.values.group_by(&:name_in_index)
131
+ end
132
+
129
133
  def enum_value_named(enum_value_name)
130
134
  @enum_values_by_name[enum_value_name]
131
135
  end
@@ -183,13 +183,7 @@ module ElasticGraph
183
183
  def named_graphql_resolvers
184
184
  @named_graphql_resolvers ||= runtime_metadata.graphql_resolvers_by_name.transform_values do |resolver|
185
185
  ext = resolver.load_resolver
186
-
187
- ->(field_config) do
188
- (_ = ext.extension_class).new(
189
- elasticgraph_graphql: self,
190
- config: ext.config.merge(field_config)
191
- )
192
- end
186
+ (_ = ext.extension_class).new(elasticgraph_graphql: self, config: ext.config)
193
187
  end
194
188
  end
195
189