elasticgraph-graphql 0.18.0.5 → 0.19.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.
- checksums.yaml +4 -4
- data/elasticgraph-graphql.gemspec +1 -1
- data/lib/elastic_graph/graphql/aggregation/non_composite_grouping_adapter.rb +11 -2
- data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +3 -1
- data/lib/elastic_graph/graphql/datastore_search_router.rb +18 -1
- data/lib/elastic_graph/graphql/filtering/boolean_query.rb +1 -6
- data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +58 -30
- data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +17 -7
- data/lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb +9 -3
- data/lib/elastic_graph/graphql/http_endpoint.rb +4 -0
- data/lib/elastic_graph/graphql/query_adapter/filters.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/query_source.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +0 -1
- data/lib/elastic_graph/graphql/schema.rb +15 -18
- data/lib/elastic_graph/graphql.rb +10 -1
- metadata +35 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01af70cea7a26c2897ca64c179e0ed5a9a482a9229a46304b2e59b2f249148c3
|
4
|
+
data.tar.gz: 9a6aca68a2663012e54bf022a7153ec4443b3bc59846c63b91e6084b07b61574
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ead565225393723335e8dcc0dea15a6858dd005e5f96531ad60aa21b2cf0166760cb5c25e788b0dbb5d367c50ddb1335f9fd5129a2ee173df0d04c436e5ed15
|
7
|
+
data.tar.gz: fc360a136de5620ac53375046dce9e93a90b19f4ea3eb2331f8ca2dbda2f2701b55156e04ac60f71ab4bf45c5eca4e45a2efcf2236cef25f78503ef1beca5cda
|
@@ -13,7 +13,7 @@ ElasticGraphGemspecHelper.define_elasticgraph_gem(gemspec_file: __FILE__, catego
|
|
13
13
|
|
14
14
|
spec.add_dependency "elasticgraph-datastore_core", eg_version
|
15
15
|
spec.add_dependency "elasticgraph-schema_artifacts", eg_version
|
16
|
-
spec.add_dependency "graphql", "~> 2.
|
16
|
+
spec.add_dependency "graphql", "~> 2.4.5"
|
17
17
|
|
18
18
|
spec.add_development_dependency "elasticgraph-admin", eg_version
|
19
19
|
spec.add_development_dependency "elasticgraph-elasticsearch", eg_version
|
@@ -114,8 +114,17 @@ module ElasticGraph
|
|
114
114
|
# (by doc count descending, then by the key values ascending), and then take only first `size`.
|
115
115
|
def sort_and_truncate_buckets(buckets, size)
|
116
116
|
buckets
|
117
|
-
.sort_by
|
118
|
-
|
117
|
+
.sort_by do |b|
|
118
|
+
# We convert key values to strings to ensure they are comparable. Otherwise, we can get an error like:
|
119
|
+
#
|
120
|
+
# > ArgumentError: comparison of Array with Array failed
|
121
|
+
#
|
122
|
+
# Note that this turns it into a lexicographical sort rather than a more type-aware sort
|
123
|
+
# (10 will sort before 2, for example), but that's fine. We only sort by `key_values` as
|
124
|
+
# a time breaker to ensure deterministic results, but don't particularly care which buckets
|
125
|
+
# come first.
|
126
|
+
[-b.fetch("doc_count"), b.fetch("key_values").map(&:to_s)]
|
127
|
+
end.first(size)
|
119
128
|
end
|
120
129
|
|
121
130
|
def missing_bucket_path_from(buckets_path)
|
@@ -15,7 +15,7 @@ module ElasticGraph
|
|
15
15
|
# Responsible for building a search index expression for a specific query based on the filters.
|
16
16
|
class IndexExpressionBuilder
|
17
17
|
def initialize(schema_names:)
|
18
|
-
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(schema_names, Support::TimeSet::ALL) do |operator, filter_value|
|
18
|
+
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(schema_names, Support::TimeSet::ALL, Support::TimeSet::EMPTY) do |operator, filter_value|
|
19
19
|
case operator
|
20
20
|
when :gt, :gte, :lt, :lte
|
21
21
|
if date_string?(filter_value)
|
@@ -16,8 +16,9 @@ module ElasticGraph
|
|
16
16
|
def initialize(schema_names:)
|
17
17
|
# @type var all_values_set: _RoutingValueSet
|
18
18
|
all_values_set = RoutingValueSet::ALL
|
19
|
+
empty_set = RoutingValueSet::EMPTY
|
19
20
|
|
20
|
-
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(schema_names, all_values_set) do |operator, filter_value|
|
21
|
+
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(schema_names, all_values_set, empty_set) do |operator, filter_value|
|
21
22
|
if operator == :equal_to_any_of
|
22
23
|
# This calls `.compact` to remove `nil` filter_value values
|
23
24
|
RoutingValueSet.of(filter_value.compact)
|
@@ -70,6 +71,7 @@ module ElasticGraph
|
|
70
71
|
end
|
71
72
|
|
72
73
|
ALL = of_all_except([])
|
74
|
+
EMPTY = of([])
|
73
75
|
|
74
76
|
def intersection(other_set)
|
75
77
|
# Here we return `self` to preserve the commutative property of `intersection`. Returning `self`
|
@@ -61,7 +61,7 @@ module ElasticGraph
|
|
61
61
|
# Unfortunately, the Elasticsearch/OpenSearch clients don't support setting a per-request client-side timeout,
|
62
62
|
# even though Faraday (the underlying HTTP client) does. To work around this, we pass our desired
|
63
63
|
# timeout in a specific header that the `SupportTimeouts` Faraday middleware will use.
|
64
|
-
headers = {TIMEOUT_MS_HEADER => msearch_request_timeout_from(queries)}.compact
|
64
|
+
headers = {TIMEOUT_MS_HEADER => msearch_request_timeout_from(queries)&.to_s}.compact
|
65
65
|
|
66
66
|
queries_and_header_body_tuples_by_datastore_client = header_body_tuples_by_query.group_by do |(query, header_body_tuples)|
|
67
67
|
@datastore_clients_by_name.fetch(query.cluster_name)
|
@@ -118,6 +118,8 @@ module ElasticGraph
|
|
118
118
|
return if failures.empty?
|
119
119
|
|
120
120
|
formatted_failures = failures.map do |(query, response), index|
|
121
|
+
raise_execution_exception_for_known_public_error(response)
|
122
|
+
|
121
123
|
# Note: we intentionally omit the body of the request here, because it could contain PII
|
122
124
|
# or other sensitive values that we don't want logged.
|
123
125
|
<<~ERROR
|
@@ -130,6 +132,21 @@ module ElasticGraph
|
|
130
132
|
raise Errors::SearchFailedError, "Got #{failures.size} search failure(s):\n\n#{formatted_failures}"
|
131
133
|
end
|
132
134
|
|
135
|
+
# In general, when we receive a datastore response indicating a search failed, we raise
|
136
|
+
# `Errors::SearchFailedError` which translates into a `500 Internal Server Error`. That's
|
137
|
+
# appropriate for transient errors (particularly when there's nothing the client can do about
|
138
|
+
# it) but for non-transient errors that the client can do something about, we'd like to provide
|
139
|
+
# a friendlier error. This method handles those cases.
|
140
|
+
#
|
141
|
+
# GraphQL::ExecutionError is automatically translated into a nice error response.
|
142
|
+
def raise_execution_exception_for_known_public_error(response)
|
143
|
+
if response.dig("error", "caused_by", "type") == "too_many_buckets_exception"
|
144
|
+
max_buckets = response.dig("error", "caused_by", "max_buckets")
|
145
|
+
raise ::GraphQL::ExecutionError, "Aggregation query produces too many groupings. " \
|
146
|
+
"Reduce the grouping cardinality to less than #{max_buckets} and try again."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
133
150
|
# Examine successful query responses and log any shard failure they encounter
|
134
151
|
def log_shard_failure_if_necessary(responses_by_query)
|
135
152
|
shard_failures = responses_by_query.each_with_index.select do |(query, response), query_numeric_index|
|
@@ -33,12 +33,7 @@ module ElasticGraph
|
|
33
33
|
bool_node[occurrence].concat(clauses)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
# I haven't found any sort of literal `false` we can pass in the compound expression
|
38
|
-
# or even a literal `1 = 0` as is sometimes used in SQL. Instead, we use this for that
|
39
|
-
# case.
|
40
|
-
empty_array = [] # : ::Array[untyped]
|
41
|
-
ALWAYS_FALSE_FILTER = filter({ids: {values: empty_array}})
|
36
|
+
ALWAYS_FALSE_FILTER = filter({match_none: {}})
|
42
37
|
end
|
43
38
|
end
|
44
39
|
end
|
@@ -59,14 +59,9 @@ module ElasticGraph
|
|
59
59
|
|
60
60
|
def process_filter_hash(bool_node, filter_hash, field_path)
|
61
61
|
filter_hash.each do |field_or_op, expression|
|
62
|
-
# `nil` filter predicates should be ignored, so we can safely `compact` them out.
|
63
|
-
# It also is simpler to handle them once here instead of the different branches
|
64
|
-
# below having to be aware of possible `nil` predicates.
|
65
|
-
expression = expression.compact if expression.is_a?(::Hash)
|
66
|
-
|
67
62
|
case filter_node_interpreter.identify_node_type(field_or_op, expression)
|
68
63
|
when :empty
|
69
|
-
# This is an "empty" filter predicate and
|
64
|
+
# This is an "empty" filter predicate and can be treated as `true`.
|
70
65
|
when :not
|
71
66
|
process_not_expression(bool_node, expression, field_path)
|
72
67
|
when :list_any_filter
|
@@ -109,10 +104,14 @@ module ElasticGraph
|
|
109
104
|
|
110
105
|
def process_not_expression(bool_node, expression, field_path)
|
111
106
|
sub_filter = build_bool_hash do |inner_node|
|
112
|
-
process_filter_hash(inner_node, expression, field_path)
|
107
|
+
process_filter_hash(inner_node, expression || {}, field_path)
|
113
108
|
end
|
114
109
|
|
115
|
-
|
110
|
+
unless sub_filter
|
111
|
+
# Since an empty expression is treated as `true`, convert to `false` when negating.
|
112
|
+
BooleanQuery::ALWAYS_FALSE_FILTER.merge_into(bool_node)
|
113
|
+
return
|
114
|
+
end
|
116
115
|
|
117
116
|
# Prevent any negated filters from being unnecessarily double-negated by
|
118
117
|
# converting them to a positive filter (i.e., !!A == A).
|
@@ -138,6 +137,8 @@ module ElasticGraph
|
|
138
137
|
# this because we do not generate `any_satisfy` filters on `object` list fields (instead,
|
139
138
|
# they get generated on their leaf fields).
|
140
139
|
def process_list_any_filter_expression(bool_node, filter, field_path)
|
140
|
+
return if filter.nil? || filter == {}
|
141
|
+
|
141
142
|
if filters_on_sub_fields?(filter)
|
142
143
|
process_any_satisfy_filter_expression_on_nested_object_list(bool_node, filter, field_path)
|
143
144
|
else
|
@@ -188,22 +189,37 @@ module ElasticGraph
|
|
188
189
|
end
|
189
190
|
end
|
190
191
|
|
192
|
+
# We want to provide the following semantics for `any_of`:
|
193
|
+
#
|
194
|
+
# * `filter: {anyOf: []}` -> return no results
|
195
|
+
# * `filter: {anyOf: [{field: null}]}` -> return all results
|
196
|
+
# * `filter: {anyOf: [{field: null}, {field: ...}]}` -> return all results
|
191
197
|
def process_any_of_expression(bool_node, expressions, field_path)
|
198
|
+
return if expressions.nil? || expressions == {}
|
199
|
+
|
200
|
+
if expressions.empty?
|
201
|
+
# When our `expressions` array is empty, we want to match no documents. However, that's
|
202
|
+
# not the behavior the datastore will give us if we have an empty array in the query under
|
203
|
+
# `should`. To get the behavior we want, we need to pass the datastore some filter criteria
|
204
|
+
# that will evaluate to false for every document.
|
205
|
+
BooleanQuery::ALWAYS_FALSE_FILTER.merge_into(bool_node)
|
206
|
+
return
|
207
|
+
end
|
208
|
+
|
192
209
|
shoulds = expressions.filter_map do |expression|
|
193
210
|
build_bool_hash do |inner_bool_node|
|
194
211
|
process_filter_hash(inner_bool_node, expression, field_path)
|
195
212
|
end
|
196
213
|
end
|
197
214
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
# criteria that will evaluate to false for every document.
|
202
|
-
bool_query = shoulds.empty? ? BooleanQuery::ALWAYS_FALSE_FILTER : BooleanQuery.should(*shoulds)
|
203
|
-
bool_query.merge_into(bool_node)
|
215
|
+
return if shoulds.size < expressions.size
|
216
|
+
|
217
|
+
BooleanQuery.should(*shoulds).merge_into(bool_node)
|
204
218
|
end
|
205
219
|
|
206
220
|
def process_all_of_expression(bool_node, expressions, field_path)
|
221
|
+
return if expressions.nil? || expressions == {}
|
222
|
+
|
207
223
|
# `all_of` represents an AND. AND is the default way that `process_filter_hash` combines
|
208
224
|
# filters so we just have to call it for each sub-expression.
|
209
225
|
expressions.each do |sub_expression|
|
@@ -212,6 +228,8 @@ module ElasticGraph
|
|
212
228
|
end
|
213
229
|
|
214
230
|
def process_operator_expression(bool_node, operator, expression, field_path)
|
231
|
+
return if expression.nil? || expression == {}
|
232
|
+
|
215
233
|
# `operator` is a filtering operator, and `expression` is the value the filtering
|
216
234
|
# operator should be applied to. The `op_applicator` lambda, when called, will
|
217
235
|
# return a Clause instance (defined in this module).
|
@@ -299,6 +317,8 @@ module ElasticGraph
|
|
299
317
|
end
|
300
318
|
|
301
319
|
def process_list_count_expression(bool_node, expression, field_path)
|
320
|
+
return if expression.nil? || expression == {}
|
321
|
+
|
302
322
|
# Normally, we don't have to do anything special for list count expressions.
|
303
323
|
# That's the case, for example, for an expression like:
|
304
324
|
#
|
@@ -313,7 +333,7 @@ module ElasticGraph
|
|
313
333
|
# While we index an explicit count of 0, the count field will be missing from documents indexed before
|
314
334
|
# the list field was defined on the ElasticGraph schema. To properly match those documents, we need to
|
315
335
|
# convert this into an OR (using `any_of`) to also match documents that lack the field entirely.
|
316
|
-
|
336
|
+
if filters_to_range_including_zero?(expression)
|
317
337
|
expression = {schema_names.any_of => [
|
318
338
|
expression,
|
319
339
|
{schema_names.equal_to_any_of => [nil]}
|
@@ -326,7 +346,7 @@ module ElasticGraph
|
|
326
346
|
def build_bool_hash(&block)
|
327
347
|
bool_node = Hash.new { |h, k| h[k] = [] }.tap(&block)
|
328
348
|
|
329
|
-
# To
|
349
|
+
# To treat "empty" filter predicates as `true` we need to return `nil` here.
|
330
350
|
return nil if bool_node.empty?
|
331
351
|
|
332
352
|
# According to https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html#bool-min-should-match,
|
@@ -337,20 +357,28 @@ module ElasticGraph
|
|
337
357
|
{bool: bool_node}
|
338
358
|
end
|
339
359
|
|
340
|
-
# Determines if the given
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
360
|
+
# Determines if the given expression filters to a range that includes `0`.
|
361
|
+
# If it does not do any filtering (e.g. an empty expression) it will return `false`.
|
362
|
+
def filters_to_range_including_zero?(expression)
|
363
|
+
expression = expression.compact
|
364
|
+
|
365
|
+
expression.size > 0 && expression.none? do |operator, operand|
|
366
|
+
operator_excludes_zero?(operator, operand)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# Determines if the given operator and operand exclude 0 as a matched value.
|
371
|
+
def operator_excludes_zero?(operator, operand)
|
372
|
+
case operator
|
373
|
+
when schema_names.equal_to_any_of then !operand.include?(0)
|
374
|
+
when schema_names.lt then operand <= 0
|
375
|
+
when schema_names.lte then operand < 0
|
376
|
+
when schema_names.gt then operand >= 0
|
377
|
+
when schema_names.gte then operand > 0
|
378
|
+
else
|
379
|
+
# :nocov: -- all operators are covered above. But simplecov complains about an implicit `else` branch being uncovered, so here we've defined it to wrap it with `:nocov:`.
|
380
|
+
false
|
381
|
+
# :nocov:
|
354
382
|
end
|
355
383
|
end
|
356
384
|
|
@@ -25,22 +25,32 @@ module ElasticGraph
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def identify_node_type(field_or_op, sub_expression)
|
28
|
-
|
28
|
+
identify_by_field_or_op(field_or_op) || identify_by_sub_expr(sub_expression) || :unknown
|
29
|
+
end
|
30
|
+
|
31
|
+
def filter_operators
|
32
|
+
@filter_operators ||= build_filter_operators(runtime_metadata)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def identify_by_field_or_op(field_or_op)
|
29
38
|
return :not if field_or_op == schema_names.not
|
30
39
|
return :list_any_filter if field_or_op == schema_names.any_satisfy
|
31
40
|
return :all_of if field_or_op == schema_names.all_of
|
32
41
|
return :any_of if field_or_op == schema_names.any_of
|
33
42
|
return :operator if filter_operators.key?(field_or_op)
|
34
43
|
return :list_count if field_or_op == LIST_COUNTS_FIELD
|
35
|
-
return :sub_field if sub_expression.is_a?(::Hash)
|
36
|
-
:unknown
|
37
|
-
end
|
38
44
|
|
39
|
-
|
40
|
-
@filter_operators ||= build_filter_operators(runtime_metadata)
|
45
|
+
nil
|
41
46
|
end
|
42
47
|
|
43
|
-
|
48
|
+
def identify_by_sub_expr(sub_expression)
|
49
|
+
return :empty if sub_expression.nil? || sub_expression == {}
|
50
|
+
return :sub_field if sub_expression.is_a?(::Hash)
|
51
|
+
|
52
|
+
nil
|
53
|
+
end
|
44
54
|
|
45
55
|
def build_filter_operators(runtime_metadata)
|
46
56
|
filter_by_time_of_day_script_id = runtime_metadata
|
@@ -12,9 +12,10 @@ module ElasticGraph
|
|
12
12
|
# Responsible for extracting a set of values from query filters, based on a using a custom
|
13
13
|
# set type that is able to efficiently model the "all values" case.
|
14
14
|
class FilterValueSetExtractor
|
15
|
-
def initialize(schema_names, all_values_set, &build_set_for_filter)
|
15
|
+
def initialize(schema_names, all_values_set, empty_set, &build_set_for_filter)
|
16
16
|
@schema_names = schema_names
|
17
17
|
@all_values_set = all_values_set
|
18
|
+
@empty_set = empty_set
|
18
19
|
@build_set_for_filter = build_set_for_filter
|
19
20
|
end
|
20
21
|
|
@@ -55,7 +56,7 @@ module ElasticGraph
|
|
55
56
|
# outside the `map_reduce_sets` block below so we only do it once instead of N times.
|
56
57
|
target_field_path_parts = target_field_path.split(".")
|
57
58
|
|
58
|
-
# Here we intersect the filter value
|
59
|
+
# Here we intersect the filter value set, because when we have multiple `filter_hashes`,
|
59
60
|
# the filters are ANDed together. Only documents that match ALL the filters will be
|
60
61
|
# returned. Therefore, we want the intersection of filter value sets.
|
61
62
|
map_reduce_sets(filter_hashes, :intersection, negate: false) do |filter_hash|
|
@@ -83,7 +84,7 @@ module ElasticGraph
|
|
83
84
|
# to a particular field.
|
84
85
|
def filter_value_set_for_filter_hash_entry(field_or_op, filter_value, target_field_path_parts, traversed_field_path_parts, negate:)
|
85
86
|
if filter_value.nil?
|
86
|
-
# Any filter with a `nil` value is effectively
|
87
|
+
# Any filter with a `nil` value is effectively treated as `true` by our filtering logic, so we need
|
87
88
|
# to return our `@all_values_set` to indicate this filter matches all documents.
|
88
89
|
@all_values_set
|
89
90
|
elsif field_or_op == @schema_names.not
|
@@ -105,6 +106,11 @@ module ElasticGraph
|
|
105
106
|
|
106
107
|
# Determines the set of filter values for an `any_of` clause, which is used for ORing multiple filters together.
|
107
108
|
def filter_value_set_for_any_of(filter_hashes, target_field_path_parts, traversed_field_path_parts, negate:)
|
109
|
+
# Here we treat `any_of: []` as matching no values.
|
110
|
+
if filter_hashes.empty?
|
111
|
+
return negate ? @all_values_set : @empty_set
|
112
|
+
end
|
113
|
+
|
108
114
|
# Here we union the filter value sets because `any_of` represents an OR. If we can determine specific
|
109
115
|
# filter values for all `any_of` clauses, we will OR them together. Alternately, if we cannot
|
110
116
|
# determine specific filter values for any clauses, we will union `@all_values_set`,
|
@@ -130,6 +130,10 @@ module ElasticGraph
|
|
130
130
|
# Ignore an empty string operationName.
|
131
131
|
params = params.merge("operationName" => nil) if params["operationName"] && params["operationName"].empty?
|
132
132
|
|
133
|
+
if (variables = params["variables"]) && !variables.is_a?(::Hash)
|
134
|
+
return HTTPResponse.error(400, "`variables` must be a JSON object but was not.")
|
135
|
+
end
|
136
|
+
|
133
137
|
yield params
|
134
138
|
end
|
135
139
|
|
@@ -101,7 +101,7 @@ module ElasticGraph
|
|
101
101
|
|
102
102
|
def filter_value_set_extractor
|
103
103
|
@filter_value_set_extractor ||=
|
104
|
-
Filtering::FilterValueSetExtractor.new(schema_element_names, IncludesNilSet) do |operator, filter_value|
|
104
|
+
Filtering::FilterValueSetExtractor.new(schema_element_names, IncludesNilSet, ExcludesNilSet) do |operator, filter_value|
|
105
105
|
if operator == :equal_to_any_of && filter_value.include?(nil)
|
106
106
|
IncludesNilSet
|
107
107
|
else
|
@@ -29,7 +29,7 @@ module ElasticGraph
|
|
29
29
|
scalar_types.to_set.union(introspection_types)
|
30
30
|
)
|
31
31
|
|
32
|
-
attr_reader :element_names, :
|
32
|
+
attr_reader :element_names, :config, :graphql_schema, :runtime_metadata
|
33
33
|
|
34
34
|
def initialize(
|
35
35
|
graphql_schema_string:,
|
@@ -53,7 +53,6 @@ module ElasticGraph
|
|
53
53
|
)
|
54
54
|
end
|
55
55
|
|
56
|
-
@types_by_name = Hash.new { |hash, key| hash[key] = lookup_type_by_name(key) }
|
57
56
|
@build_resolver = build_resolver
|
58
57
|
|
59
58
|
# Note: as part of loading the schema, the GraphQL gem may use the resolver (such
|
@@ -68,7 +67,7 @@ module ElasticGraph
|
|
68
67
|
|
69
68
|
# Pre-load all defined types so that all field extras can get configured as part
|
70
69
|
# of loading the schema, before we execute the first query.
|
71
|
-
@
|
70
|
+
@types_by_name = build_types_by_name
|
72
71
|
end
|
73
72
|
|
74
73
|
def type_from(graphql_type)
|
@@ -80,7 +79,11 @@ module ElasticGraph
|
|
80
79
|
# get type objects for wrapped types, but you need to get it from a field object of that
|
81
80
|
# type.
|
82
81
|
def type_named(type_name)
|
83
|
-
@types_by_name
|
82
|
+
@types_by_name.fetch(type_name.to_s)
|
83
|
+
rescue KeyError => e
|
84
|
+
msg = "No type named #{type_name} could be found"
|
85
|
+
msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
|
86
|
+
raise Errors::NotFoundError, msg
|
84
87
|
end
|
85
88
|
|
86
89
|
def document_type_stored_in(index_definition_name)
|
@@ -106,6 +109,10 @@ module ElasticGraph
|
|
106
109
|
@indexed_document_types ||= defined_types.select(&:indexed_document?)
|
107
110
|
end
|
108
111
|
|
112
|
+
def defined_types
|
113
|
+
@defined_types ||= @types_by_name.except(*BUILT_IN_TYPE_NAMES).values
|
114
|
+
end
|
115
|
+
|
109
116
|
def to_s
|
110
117
|
"#<#{self.class.name} 0x#{__id__.to_s(16)} indexed_document_types=#{indexed_document_types.map(&:name).sort.to_s.delete(":")}>"
|
111
118
|
end
|
@@ -128,24 +135,14 @@ module ElasticGraph
|
|
128
135
|
def_delegators :resolver, :call, :resolve_type, :coerce_input, :coerce_result
|
129
136
|
end
|
130
137
|
|
131
|
-
def lookup_type_by_name(type_name)
|
132
|
-
type_from(@graphql_schema.types.fetch(type_name))
|
133
|
-
rescue KeyError => e
|
134
|
-
msg = "No type named #{type_name} could be found"
|
135
|
-
msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
|
136
|
-
raise Errors::NotFoundError, msg
|
137
|
-
end
|
138
|
-
|
139
138
|
def resolver
|
140
139
|
@resolver ||= @build_resolver.call(self)
|
141
140
|
end
|
142
141
|
|
143
|
-
def
|
144
|
-
graphql_schema
|
145
|
-
|
146
|
-
|
147
|
-
.reject { |t| BUILT_IN_TYPE_NAMES.include?(t.graphql_name) }
|
148
|
-
.map { |t| type_named(t.graphql_name) }
|
142
|
+
def build_types_by_name
|
143
|
+
graphql_schema.types.transform_values do |graphql_type|
|
144
|
+
@types_by_graphql_type[graphql_type]
|
145
|
+
end
|
149
146
|
end
|
150
147
|
|
151
148
|
def indexed_document_types_by_index_definition_name
|
@@ -142,7 +142,13 @@ module ElasticGraph
|
|
142
142
|
def graphql_gem_plugins
|
143
143
|
@graphql_gem_plugins ||= begin
|
144
144
|
require "graphql"
|
145
|
-
{
|
145
|
+
{
|
146
|
+
# We depend on this to avoid N+1 calls to the datastore.
|
147
|
+
::GraphQL::Dataloader => {},
|
148
|
+
# This is new in the graphql-ruby 2.4 release, and will be required in the future.
|
149
|
+
# We pass `preload: true` because the way we handle the schema depends on it being preloaded.
|
150
|
+
::GraphQL::Schema::Visibility => {preload: true}
|
151
|
+
}
|
146
152
|
end
|
147
153
|
end
|
148
154
|
|
@@ -244,6 +250,9 @@ module ElasticGraph
|
|
244
250
|
# at boot time instead of deferring dependency loading until we handle the first query. In other environments (such as tests),
|
245
251
|
# it's nice to load dependencies when needed.
|
246
252
|
def load_dependencies_eagerly
|
253
|
+
require "graphql"
|
254
|
+
::GraphQL.eager_load!
|
255
|
+
|
247
256
|
# run a simple GraphQL query to force load any dependencies needed to handle GraphQL queries
|
248
257
|
graphql_query_executor.execute(EAGER_LOAD_QUERY, client: Client::ELASTICGRAPH_INTERNAL)
|
249
258
|
graphql_http_endpoint # force load this too.
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elasticgraph-graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.19.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Myron Marston
|
8
8
|
- Ben VandenBos
|
9
|
-
-
|
9
|
+
- Block Engineering
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-12-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rubocop-factory_bot
|
@@ -46,42 +46,42 @@ dependencies:
|
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '3.
|
49
|
+
version: '3.1'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: '3.
|
56
|
+
version: '3.1'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: standard
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: 1.
|
63
|
+
version: 1.41.0
|
64
64
|
type: :development
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
68
|
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: 1.
|
70
|
+
version: 1.41.0
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: steep
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
75
|
- - "~>"
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version: '1.
|
77
|
+
version: '1.8'
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
82
|
- - "~>"
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version: '1.
|
84
|
+
version: '1.8'
|
85
85
|
- !ruby/object:Gem::Dependency
|
86
86
|
name: coderay
|
87
87
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,14 +136,14 @@ dependencies:
|
|
136
136
|
requirements:
|
137
137
|
- - "~>"
|
138
138
|
- !ruby/object:Gem::Version
|
139
|
-
version: '0.
|
139
|
+
version: '0.13'
|
140
140
|
type: :development
|
141
141
|
prerelease: false
|
142
142
|
version_requirements: !ruby/object:Gem::Requirement
|
143
143
|
requirements:
|
144
144
|
- - "~>"
|
145
145
|
- !ruby/object:Gem::Version
|
146
|
-
version: '0.
|
146
|
+
version: '0.13'
|
147
147
|
- !ruby/object:Gem::Dependency
|
148
148
|
name: simplecov
|
149
149
|
requirement: !ruby/object:Gem::Requirement
|
@@ -254,126 +254,126 @@ dependencies:
|
|
254
254
|
requirements:
|
255
255
|
- - "~>"
|
256
256
|
- !ruby/object:Gem::Version
|
257
|
-
version: '3.
|
257
|
+
version: '3.5'
|
258
258
|
type: :development
|
259
259
|
prerelease: false
|
260
260
|
version_requirements: !ruby/object:Gem::Requirement
|
261
261
|
requirements:
|
262
262
|
- - "~>"
|
263
263
|
- !ruby/object:Gem::Version
|
264
|
-
version: '3.
|
264
|
+
version: '3.5'
|
265
265
|
- !ruby/object:Gem::Dependency
|
266
266
|
name: elasticgraph-datastore_core
|
267
267
|
requirement: !ruby/object:Gem::Requirement
|
268
268
|
requirements:
|
269
269
|
- - '='
|
270
270
|
- !ruby/object:Gem::Version
|
271
|
-
version: 0.
|
271
|
+
version: 0.19.0.0.rc1
|
272
272
|
type: :runtime
|
273
273
|
prerelease: false
|
274
274
|
version_requirements: !ruby/object:Gem::Requirement
|
275
275
|
requirements:
|
276
276
|
- - '='
|
277
277
|
- !ruby/object:Gem::Version
|
278
|
-
version: 0.
|
278
|
+
version: 0.19.0.0.rc1
|
279
279
|
- !ruby/object:Gem::Dependency
|
280
280
|
name: elasticgraph-schema_artifacts
|
281
281
|
requirement: !ruby/object:Gem::Requirement
|
282
282
|
requirements:
|
283
283
|
- - '='
|
284
284
|
- !ruby/object:Gem::Version
|
285
|
-
version: 0.
|
285
|
+
version: 0.19.0.0.rc1
|
286
286
|
type: :runtime
|
287
287
|
prerelease: false
|
288
288
|
version_requirements: !ruby/object:Gem::Requirement
|
289
289
|
requirements:
|
290
290
|
- - '='
|
291
291
|
- !ruby/object:Gem::Version
|
292
|
-
version: 0.
|
292
|
+
version: 0.19.0.0.rc1
|
293
293
|
- !ruby/object:Gem::Dependency
|
294
294
|
name: graphql
|
295
295
|
requirement: !ruby/object:Gem::Requirement
|
296
296
|
requirements:
|
297
297
|
- - "~>"
|
298
298
|
- !ruby/object:Gem::Version
|
299
|
-
version: 2.
|
299
|
+
version: 2.4.5
|
300
300
|
type: :runtime
|
301
301
|
prerelease: false
|
302
302
|
version_requirements: !ruby/object:Gem::Requirement
|
303
303
|
requirements:
|
304
304
|
- - "~>"
|
305
305
|
- !ruby/object:Gem::Version
|
306
|
-
version: 2.
|
306
|
+
version: 2.4.5
|
307
307
|
- !ruby/object:Gem::Dependency
|
308
308
|
name: elasticgraph-admin
|
309
309
|
requirement: !ruby/object:Gem::Requirement
|
310
310
|
requirements:
|
311
311
|
- - '='
|
312
312
|
- !ruby/object:Gem::Version
|
313
|
-
version: 0.
|
313
|
+
version: 0.19.0.0.rc1
|
314
314
|
type: :development
|
315
315
|
prerelease: false
|
316
316
|
version_requirements: !ruby/object:Gem::Requirement
|
317
317
|
requirements:
|
318
318
|
- - '='
|
319
319
|
- !ruby/object:Gem::Version
|
320
|
-
version: 0.
|
320
|
+
version: 0.19.0.0.rc1
|
321
321
|
- !ruby/object:Gem::Dependency
|
322
322
|
name: elasticgraph-elasticsearch
|
323
323
|
requirement: !ruby/object:Gem::Requirement
|
324
324
|
requirements:
|
325
325
|
- - '='
|
326
326
|
- !ruby/object:Gem::Version
|
327
|
-
version: 0.
|
327
|
+
version: 0.19.0.0.rc1
|
328
328
|
type: :development
|
329
329
|
prerelease: false
|
330
330
|
version_requirements: !ruby/object:Gem::Requirement
|
331
331
|
requirements:
|
332
332
|
- - '='
|
333
333
|
- !ruby/object:Gem::Version
|
334
|
-
version: 0.
|
334
|
+
version: 0.19.0.0.rc1
|
335
335
|
- !ruby/object:Gem::Dependency
|
336
336
|
name: elasticgraph-opensearch
|
337
337
|
requirement: !ruby/object:Gem::Requirement
|
338
338
|
requirements:
|
339
339
|
- - '='
|
340
340
|
- !ruby/object:Gem::Version
|
341
|
-
version: 0.
|
341
|
+
version: 0.19.0.0.rc1
|
342
342
|
type: :development
|
343
343
|
prerelease: false
|
344
344
|
version_requirements: !ruby/object:Gem::Requirement
|
345
345
|
requirements:
|
346
346
|
- - '='
|
347
347
|
- !ruby/object:Gem::Version
|
348
|
-
version: 0.
|
348
|
+
version: 0.19.0.0.rc1
|
349
349
|
- !ruby/object:Gem::Dependency
|
350
350
|
name: elasticgraph-indexer
|
351
351
|
requirement: !ruby/object:Gem::Requirement
|
352
352
|
requirements:
|
353
353
|
- - '='
|
354
354
|
- !ruby/object:Gem::Version
|
355
|
-
version: 0.
|
355
|
+
version: 0.19.0.0.rc1
|
356
356
|
type: :development
|
357
357
|
prerelease: false
|
358
358
|
version_requirements: !ruby/object:Gem::Requirement
|
359
359
|
requirements:
|
360
360
|
- - '='
|
361
361
|
- !ruby/object:Gem::Version
|
362
|
-
version: 0.
|
362
|
+
version: 0.19.0.0.rc1
|
363
363
|
- !ruby/object:Gem::Dependency
|
364
364
|
name: elasticgraph-schema_definition
|
365
365
|
requirement: !ruby/object:Gem::Requirement
|
366
366
|
requirements:
|
367
367
|
- - '='
|
368
368
|
- !ruby/object:Gem::Version
|
369
|
-
version: 0.
|
369
|
+
version: 0.19.0.0.rc1
|
370
370
|
type: :development
|
371
371
|
prerelease: false
|
372
372
|
version_requirements: !ruby/object:Gem::Requirement
|
373
373
|
requirements:
|
374
374
|
- - '='
|
375
375
|
- !ruby/object:Gem::Version
|
376
|
-
version: 0.
|
376
|
+
version: 0.19.0.0.rc1
|
377
377
|
description:
|
378
378
|
email:
|
379
379
|
- myron@squareup.com
|
@@ -461,10 +461,15 @@ files:
|
|
461
461
|
- lib/elastic_graph/graphql/schema/type.rb
|
462
462
|
- script/dump_time_zones
|
463
463
|
- script/dump_time_zones.java
|
464
|
-
homepage:
|
464
|
+
homepage: https://block.github.io/elasticgraph/
|
465
465
|
licenses:
|
466
466
|
- MIT
|
467
467
|
metadata:
|
468
|
+
bug_tracker_uri: https://github.com/block/elasticgraph/issues
|
469
|
+
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v0.19.0.0.rc1
|
470
|
+
documentation_uri: https://block.github.io/elasticgraph/docs/main/
|
471
|
+
homepage_uri: https://block.github.io/elasticgraph/
|
472
|
+
source_code_uri: https://github.com/block/elasticgraph/tree/v0.19.0.0.rc1/elasticgraph-graphql
|
468
473
|
gem_category: core
|
469
474
|
post_install_message:
|
470
475
|
rdoc_options: []
|