elasticgraph-graphql 0.18.0.4 → 0.19.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/elasticgraph-graphql.gemspec +1 -1
- data/lib/elastic_graph/graphql/aggregation/field_path_encoder.rb +2 -2
- data/lib/elastic_graph/graphql/aggregation/key.rb +2 -2
- data/lib/elastic_graph/graphql/aggregation/non_composite_grouping_adapter.rb +11 -2
- data/lib/elastic_graph/graphql/config.rb +3 -3
- data/lib/elastic_graph/graphql/datastore_query/document_paginator.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/paginator.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +3 -1
- data/lib/elastic_graph/graphql/datastore_query.rb +6 -6
- data/lib/elastic_graph/graphql/datastore_response/search_response.rb +2 -2
- data/lib/elastic_graph/graphql/datastore_search_router.rb +21 -4
- data/lib/elastic_graph/graphql/decoded_cursor.rb +7 -7
- 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 +5 -1
- data/lib/elastic_graph/graphql/query_adapter/filters.rb +1 -1
- data/lib/elastic_graph/graphql/query_executor.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/query_source.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +2 -3
- data/lib/elastic_graph/graphql/schema/arguments.rb +1 -1
- data/lib/elastic_graph/graphql/schema/enum_value.rb +2 -2
- data/lib/elastic_graph/graphql/schema/field.rb +2 -2
- data/lib/elastic_graph/graphql/schema/type.rb +3 -3
- data/lib/elastic_graph/graphql/schema.rb +18 -21
- data/lib/elastic_graph/graphql.rb +10 -1
- metadata +36 -29
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
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
|
11
11
|
module ElasticGraph
|
12
12
|
class GraphQL
|
@@ -38,7 +38,7 @@ module ElasticGraph
|
|
38
38
|
|
39
39
|
private_class_method def self.verify_delimiters(str)
|
40
40
|
if str.to_s.include?(DELIMITER)
|
41
|
-
raise InvalidArgumentValueError, %("#{str}" contains delimiter: "#{DELIMITER}")
|
41
|
+
raise Errors::InvalidArgumentValueError, %("#{str}" contains delimiter: "#{DELIMITER}")
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/graphql/aggregation/field_path_encoder"
|
11
11
|
|
12
12
|
module ElasticGraph
|
@@ -77,7 +77,7 @@ module ElasticGraph
|
|
77
77
|
def self.verify_no_delimiter_in(*parts)
|
78
78
|
parts.each do |part|
|
79
79
|
if part.to_s.include?(DELIMITER)
|
80
|
-
raise InvalidArgumentValueError, %("#{part}" contains delimiter: "#{DELIMITER}")
|
80
|
+
raise Errors::InvalidArgumentValueError, %("#{part}" contains delimiter: "#{DELIMITER}")
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
@@ -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)
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/graphql/client"
|
11
11
|
require "elastic_graph/schema_artifacts/runtime_metadata/extension_loader"
|
12
12
|
|
@@ -33,14 +33,14 @@ module ElasticGraph
|
|
33
33
|
extra_keys = parsed_yaml.keys - EXPECTED_KEYS
|
34
34
|
|
35
35
|
unless extra_keys.empty?
|
36
|
-
raise ConfigError, "Unknown `graphql` config settings: #{extra_keys.join(", ")}"
|
36
|
+
raise Errors::ConfigError, "Unknown `graphql` config settings: #{extra_keys.join(", ")}"
|
37
37
|
end
|
38
38
|
|
39
39
|
extension_loader = SchemaArtifacts::RuntimeMetadata::ExtensionLoader.new(::Module.new)
|
40
40
|
extension_mods = parsed_yaml.fetch("extension_modules", []).map do |mod_hash|
|
41
41
|
extension_loader.load(mod_hash.fetch("extension_name"), from: mod_hash.fetch("require_path"), config: {}).extension_class.tap do |mod|
|
42
42
|
unless mod.instance_of?(::Module)
|
43
|
-
raise ConfigError, "`#{mod_hash.fetch("extension_name")}` is not a module, but all application extension modules must be modules."
|
43
|
+
raise Errors::ConfigError, "`#{mod_hash.fetch("extension_name")}` is not a module, but all application extension modules must be modules."
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -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`
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/graphql/aggregation/query"
|
11
11
|
require "elastic_graph/graphql/aggregation/query_optimizer"
|
12
12
|
require "elastic_graph/graphql/decoded_cursor"
|
@@ -65,7 +65,7 @@ module ElasticGraph
|
|
65
65
|
)
|
66
66
|
|
67
67
|
if search_index_definitions.empty?
|
68
|
-
raise SearchFailedError, "Query is invalid, since it contains no `search_index_definitions`."
|
68
|
+
raise Errors::SearchFailedError, "Query is invalid, since it contains no `search_index_definitions`."
|
69
69
|
end
|
70
70
|
end
|
71
71
|
}
|
@@ -121,7 +121,7 @@ module ElasticGraph
|
|
121
121
|
missing_queries = expected_queries - actual_queries
|
122
122
|
extra_queries = actual_queries - expected_queries
|
123
123
|
|
124
|
-
raise SearchFailedError, "The `responses_hash` does not have the expected set of queries as keys. " \
|
124
|
+
raise Errors::SearchFailedError, "The `responses_hash` does not have the expected set of queries as keys. " \
|
125
125
|
"This can cause problems for the `GraphQL::Dataloader` and suggests a bug in the logic that should be fixed.\n\n" \
|
126
126
|
"Missing queries (#{missing_queries.size}):\n#{missing_queries.map(&:inspect).join("\n")}.\n\n" \
|
127
127
|
"Extra queries (#{extra_queries.size}): #{extra_queries.map(&:inspect).join("\n")}"
|
@@ -133,7 +133,7 @@ module ElasticGraph
|
|
133
133
|
# Both query objects are left unchanged.
|
134
134
|
def merge(other_query)
|
135
135
|
if search_index_definitions != other_query.search_index_definitions
|
136
|
-
raise ElasticGraph::InvalidMergeError, "`search_index_definitions` conflict while merging between " \
|
136
|
+
raise ElasticGraph::Errors::InvalidMergeError, "`search_index_definitions` conflict while merging between " \
|
137
137
|
"#{search_index_definitions} and #{other_query.search_index_definitions}"
|
138
138
|
end
|
139
139
|
|
@@ -177,11 +177,11 @@ module ElasticGraph
|
|
177
177
|
end
|
178
178
|
|
179
179
|
# Returns the name of the datastore cluster as a String where this query should be setn.
|
180
|
-
# Unless exactly 1 cluster name is found, this method raises a ConfigError.
|
180
|
+
# Unless exactly 1 cluster name is found, this method raises a Errors::ConfigError.
|
181
181
|
def cluster_name
|
182
182
|
cluster_name = search_index_definitions.map(&:cluster_to_query).uniq
|
183
183
|
return cluster_name.first if cluster_name.size == 1
|
184
|
-
raise ConfigError, "Found different datastore clusters (#{cluster_name}) to query " \
|
184
|
+
raise Errors::ConfigError, "Found different datastore clusters (#{cluster_name}) to query " \
|
185
185
|
"for query targeting indices: #{search_index_definitions}"
|
186
186
|
end
|
187
187
|
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/graphql/decoded_cursor"
|
11
11
|
require "elastic_graph/graphql/datastore_response/document"
|
12
12
|
require "forwardable"
|
@@ -66,7 +66,7 @@ module ElasticGraph
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def total_document_count
|
69
|
-
super || raise(CountUnavailableError, "#{__method__} is unavailable; set `query.total_document_count_needed = true` to make it available")
|
69
|
+
super || raise(Errors::CountUnavailableError, "#{__method__} is unavailable; set `query.total_document_count_needed = true` to make it available")
|
70
70
|
end
|
71
71
|
|
72
72
|
def to_s
|
@@ -7,7 +7,7 @@
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
9
|
require "elastic_graph/constants"
|
10
|
-
require "elastic_graph/
|
10
|
+
require "elastic_graph/errors"
|
11
11
|
require "elastic_graph/graphql/datastore_response/search_response"
|
12
12
|
require "elastic_graph/graphql/query_details_tracker"
|
13
13
|
require "elastic_graph/support/threading"
|
@@ -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)
|
@@ -108,7 +108,7 @@ module ElasticGraph
|
|
108
108
|
|
109
109
|
(min_query_deadline - @monotonic_clock.now_in_ms).tap do |timeout|
|
110
110
|
if timeout <= 0
|
111
|
-
raise RequestExceededDeadlineError, "It is already #{timeout.abs} ms past the search deadline."
|
111
|
+
raise Errors::RequestExceededDeadlineError, "It is already #{timeout.abs} ms past the search deadline."
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|
@@ -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
|
@@ -127,7 +129,22 @@ module ElasticGraph
|
|
127
129
|
ERROR
|
128
130
|
end.join("\n\n")
|
129
131
|
|
130
|
-
raise SearchFailedError, "Got #{failures.size} search failure(s):\n\n#{formatted_failures}"
|
132
|
+
raise Errors::SearchFailedError, "Got #{failures.size} search failure(s):\n\n#{formatted_failures}"
|
133
|
+
end
|
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
|
131
148
|
end
|
132
149
|
|
133
150
|
# Examine successful query responses and log any shard failure they encounter
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
require "base64"
|
10
10
|
require "elastic_graph/constants"
|
11
|
-
require "elastic_graph/
|
11
|
+
require "elastic_graph/errors"
|
12
12
|
require "elastic_graph/support/memoizable_data"
|
13
13
|
require "json"
|
14
14
|
|
@@ -39,17 +39,17 @@ module ElasticGraph
|
|
39
39
|
# Tries to decode the given string cursor, returning `nil` if it is invalid.
|
40
40
|
def self.try_decode(string)
|
41
41
|
decode!(string)
|
42
|
-
rescue InvalidCursorError
|
42
|
+
rescue Errors::InvalidCursorError
|
43
43
|
nil
|
44
44
|
end
|
45
45
|
|
46
|
-
# Tries to decode the given string cursor, raising an `InvalidCursorError` if it's invalid.
|
46
|
+
# Tries to decode the given string cursor, raising an `Errors::InvalidCursorError` if it's invalid.
|
47
47
|
def self.decode!(string)
|
48
48
|
return SINGLETON if string == SINGLETON_CURSOR
|
49
49
|
json = ::Base64.urlsafe_decode64(string)
|
50
50
|
new(::JSON.parse(json))
|
51
51
|
rescue ::ArgumentError, ::JSON::ParserError
|
52
|
-
raise InvalidCursorError, "`#{string}` is an invalid cursor."
|
52
|
+
raise Errors::InvalidCursorError, "`#{string}` is an invalid cursor."
|
53
53
|
end
|
54
54
|
|
55
55
|
# Encodes the cursor to a string using JSON and Base64 encoding.
|
@@ -79,7 +79,7 @@ module ElasticGraph
|
|
79
79
|
def self.from_sort_list(sort_list)
|
80
80
|
sort_fields = sort_list.map do |hash|
|
81
81
|
if hash.values.any? { |v| !v.is_a?(::Hash) } || hash.values.flat_map(&:keys) != ["order"]
|
82
|
-
raise InvalidSortFieldsError,
|
82
|
+
raise Errors::InvalidSortFieldsError,
|
83
83
|
"Given `sort_list` contained an invalid entry. Each must be a flat hash with one entry. Got: #{sort_list.inspect}"
|
84
84
|
end
|
85
85
|
|
@@ -89,7 +89,7 @@ module ElasticGraph
|
|
89
89
|
end
|
90
90
|
|
91
91
|
if sort_fields.uniq.size < sort_fields.size
|
92
|
-
raise InvalidSortFieldsError,
|
92
|
+
raise Errors::InvalidSortFieldsError,
|
93
93
|
"Given `sort_list` contains a duplicate field, which the CursorEncoder cannot handler. " \
|
94
94
|
"The caller is responsible for de-duplicating the sort list fist. Got: #{sort_list.inspect}"
|
95
95
|
end
|
@@ -99,7 +99,7 @@ module ElasticGraph
|
|
99
99
|
|
100
100
|
def build(sort_values)
|
101
101
|
unless sort_values.size == sort_fields.size
|
102
|
-
raise CursorEncodingError,
|
102
|
+
raise Errors::CursorEncodingError,
|
103
103
|
"size of sort values (#{sort_values.inspect}) does not match the " \
|
104
104
|
"size of sort fields (#{sort_fields.inspect})"
|
105
105
|
end
|
@@ -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`,
|
@@ -62,7 +62,7 @@ module ElasticGraph
|
|
62
62
|
|
63
63
|
HTTPResponse.json(200, result.to_h)
|
64
64
|
end
|
65
|
-
rescue RequestExceededDeadlineError
|
65
|
+
rescue Errors::RequestExceededDeadlineError
|
66
66
|
HTTPResponse.error(504, "Search exceeded requested timeout.")
|
67
67
|
end
|
68
68
|
|
@@ -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
|
# Executes the given `query_string` using the provided `variables`.
|
30
30
|
#
|
31
31
|
# `timeout_in_ms` can be provided to limit how long the query runs for. If the timeout
|
32
|
-
# is exceeded, `RequestExceededDeadlineError` will be raised. Note that `timeout_in_ms`
|
32
|
+
# is exceeded, `Errors::RequestExceededDeadlineError` will be raised. Note that `timeout_in_ms`
|
33
33
|
# does not provide an absolute guarantee that the query will take no longer than the
|
34
34
|
# provided value; it is only used to halt datastore queries. In process computation
|
35
35
|
# can make the total query time exceeded the specified timeout.
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/support/memoizable_data"
|
11
11
|
|
12
12
|
module ElasticGraph
|
@@ -23,7 +23,6 @@ module ElasticGraph
|
|
23
23
|
Support::MemoizableData.define(:schema_element_names, *fields) do
|
24
24
|
# @implements ResolvableValueClass
|
25
25
|
include ResolvableValue
|
26
|
-
# @type var block: (^() -> void)?
|
27
26
|
class_exec(&block) if block
|
28
27
|
end
|
29
28
|
end
|
@@ -48,7 +47,7 @@ module ElasticGraph
|
|
48
47
|
|
49
48
|
def canonical_name_for(name, element_type)
|
50
49
|
schema_element_names.canonical_name_for(name) ||
|
51
|
-
raise(SchemaError, "#{element_type} `#{name}` is not a defined schema element")
|
50
|
+
raise(Errors::SchemaError, "#{element_type} `#{name}` is not a defined schema element")
|
52
51
|
end
|
53
52
|
end
|
54
53
|
end
|
@@ -58,7 +58,7 @@ module ElasticGraph
|
|
58
58
|
# extra memory allocation and GC for the hash.
|
59
59
|
arg_defn = arg_defns.find do |a|
|
60
60
|
a.keyword == key
|
61
|
-
end || raise(SchemaError, "Cannot find an argument definition for #{key.inspect} on `#{args_owner.name}`")
|
61
|
+
end || raise(Errors::SchemaError, "Cannot find an argument definition for #{key.inspect} on `#{args_owner.name}`")
|
62
62
|
|
63
63
|
next_owner = arg_defn.type.unwrap
|
64
64
|
accumulator[arg_defn.name] = to_schema_form(value, next_owner)
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
|
11
11
|
module ElasticGraph
|
12
12
|
class GraphQL
|
@@ -15,7 +15,7 @@ module ElasticGraph
|
|
15
15
|
class EnumValue < ::Data.define(:name, :type, :runtime_metadata)
|
16
16
|
def sort_clauses
|
17
17
|
sort_clause = runtime_metadata&.sort_field&.then { |sf| {sf.field_path => {"order" => sf.direction.to_s}} } ||
|
18
|
-
raise(SchemaError, "Runtime metadata provides no `sort_field` for #{type.name}.#{name} enum value.")
|
18
|
+
raise(Errors::SchemaError, "Runtime metadata provides no `sort_field` for #{type.name}.#{name} enum value.")
|
19
19
|
|
20
20
|
[sort_clause]
|
21
21
|
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/
|
9
|
+
require "elastic_graph/errors"
|
10
10
|
require "elastic_graph/graphql/schema/relation_join"
|
11
11
|
require "elastic_graph/graphql/schema/arguments"
|
12
12
|
|
@@ -126,7 +126,7 @@ module ElasticGraph
|
|
126
126
|
def sort_argument_type
|
127
127
|
@sort_argument_type ||= begin
|
128
128
|
graphql_argument = @graphql_field.arguments.fetch(schema_element_names.order_by) do
|
129
|
-
raise SchemaError, "`#{schema_element_names.order_by}` argument not defined for field `#{parent_type.name}.#{name}`."
|
129
|
+
raise Errors::SchemaError, "`#{schema_element_names.order_by}` argument not defined for field `#{parent_type.name}.#{name}`."
|
130
130
|
end
|
131
131
|
@schema.type_from(graphql_argument.type.unwrap)
|
132
132
|
end
|
@@ -7,7 +7,7 @@
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
9
|
require "elastic_graph/datastore_core/index_definition"
|
10
|
-
require "elastic_graph/
|
10
|
+
require "elastic_graph/errors"
|
11
11
|
require "elastic_graph/graphql/schema/field"
|
12
12
|
require "elastic_graph/graphql/schema/enum_value"
|
13
13
|
require "forwardable"
|
@@ -118,7 +118,7 @@ module ElasticGraph
|
|
118
118
|
rescue KeyError => e
|
119
119
|
msg = "No field named #{field_name} (on type #{name}) could be found"
|
120
120
|
msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
|
121
|
-
raise NotFoundError, msg
|
121
|
+
raise Errors::NotFoundError, msg
|
122
122
|
end
|
123
123
|
|
124
124
|
def enum_value_named(enum_value_name)
|
@@ -233,7 +233,7 @@ module ElasticGraph
|
|
233
233
|
rescue KeyError => e
|
234
234
|
msg = "No enum value named #{enum_value_name} (on type #{name}) could be found"
|
235
235
|
msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
|
236
|
-
raise NotFoundError, msg
|
236
|
+
raise Errors::NotFoundError, msg
|
237
237
|
end
|
238
238
|
|
239
239
|
def build_fields_by_name_hash(schema, graphql_type)
|
@@ -10,7 +10,7 @@ require "digest/md5"
|
|
10
10
|
require "forwardable"
|
11
11
|
require "graphql"
|
12
12
|
require "elastic_graph/constants"
|
13
|
-
require "elastic_graph/
|
13
|
+
require "elastic_graph/errors"
|
14
14
|
require "elastic_graph/graphql/monkey_patches/schema_field"
|
15
15
|
require "elastic_graph/graphql/monkey_patches/schema_object"
|
16
16
|
require "elastic_graph/graphql/schema/field"
|
@@ -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)
|
@@ -88,7 +91,7 @@ module ElasticGraph
|
|
88
91
|
if index_definition_name.include?(ROLLOVER_INDEX_INFIX_MARKER)
|
89
92
|
raise ArgumentError, "`#{index_definition_name}` is the name of a rollover index; pass the name of the parent index definition instead."
|
90
93
|
else
|
91
|
-
raise NotFoundError, "The index definition `#{index_definition_name}` does not appear to exist. Is it misspelled?"
|
94
|
+
raise Errors::NotFoundError, "The index definition `#{index_definition_name}` does not appear to exist. Is it misspelled?"
|
92
95
|
end
|
93
96
|
end
|
94
97
|
end
|
@@ -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,31 +135,21 @@ 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 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
|
152
149
|
@indexed_document_types_by_index_definition_name ||= indexed_document_types.each_with_object({}) do |type, hash|
|
153
150
|
type.index_definitions.each do |index_def|
|
154
151
|
if hash.key?(index_def.name)
|
155
|
-
raise SchemaError, "DatastoreCore::IndexDefinition #{index_def.name} is used multiple times: #{type} vs #{hash[index_def.name]}"
|
152
|
+
raise Errors::SchemaError, "DatastoreCore::IndexDefinition #{index_def.name} is used multiple times: #{type} vs #{hash[index_def.name]}"
|
156
153
|
end
|
157
154
|
|
158
155
|
hash[index_def.name] = type
|
@@ -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,14 +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
|
+
- Ben VandenBos
|
9
|
+
- Block Engineering
|
8
10
|
autorequire:
|
9
11
|
bindir: exe
|
10
12
|
cert_chain: []
|
11
|
-
date: 2024-
|
13
|
+
date: 2024-12-03 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: rubocop-factory_bot
|
@@ -44,42 +46,42 @@ dependencies:
|
|
44
46
|
requirements:
|
45
47
|
- - "~>"
|
46
48
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
49
|
+
version: '3.1'
|
48
50
|
type: :development
|
49
51
|
prerelease: false
|
50
52
|
version_requirements: !ruby/object:Gem::Requirement
|
51
53
|
requirements:
|
52
54
|
- - "~>"
|
53
55
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
56
|
+
version: '3.1'
|
55
57
|
- !ruby/object:Gem::Dependency
|
56
58
|
name: standard
|
57
59
|
requirement: !ruby/object:Gem::Requirement
|
58
60
|
requirements:
|
59
61
|
- - "~>"
|
60
62
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
63
|
+
version: 1.41.0
|
62
64
|
type: :development
|
63
65
|
prerelease: false
|
64
66
|
version_requirements: !ruby/object:Gem::Requirement
|
65
67
|
requirements:
|
66
68
|
- - "~>"
|
67
69
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
70
|
+
version: 1.41.0
|
69
71
|
- !ruby/object:Gem::Dependency
|
70
72
|
name: steep
|
71
73
|
requirement: !ruby/object:Gem::Requirement
|
72
74
|
requirements:
|
73
75
|
- - "~>"
|
74
76
|
- !ruby/object:Gem::Version
|
75
|
-
version: '1.
|
77
|
+
version: '1.8'
|
76
78
|
type: :development
|
77
79
|
prerelease: false
|
78
80
|
version_requirements: !ruby/object:Gem::Requirement
|
79
81
|
requirements:
|
80
82
|
- - "~>"
|
81
83
|
- !ruby/object:Gem::Version
|
82
|
-
version: '1.
|
84
|
+
version: '1.8'
|
83
85
|
- !ruby/object:Gem::Dependency
|
84
86
|
name: coderay
|
85
87
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,14 +136,14 @@ dependencies:
|
|
134
136
|
requirements:
|
135
137
|
- - "~>"
|
136
138
|
- !ruby/object:Gem::Version
|
137
|
-
version: '0.
|
139
|
+
version: '0.13'
|
138
140
|
type: :development
|
139
141
|
prerelease: false
|
140
142
|
version_requirements: !ruby/object:Gem::Requirement
|
141
143
|
requirements:
|
142
144
|
- - "~>"
|
143
145
|
- !ruby/object:Gem::Version
|
144
|
-
version: '0.
|
146
|
+
version: '0.13'
|
145
147
|
- !ruby/object:Gem::Dependency
|
146
148
|
name: simplecov
|
147
149
|
requirement: !ruby/object:Gem::Requirement
|
@@ -252,126 +254,126 @@ dependencies:
|
|
252
254
|
requirements:
|
253
255
|
- - "~>"
|
254
256
|
- !ruby/object:Gem::Version
|
255
|
-
version: '3.
|
257
|
+
version: '3.5'
|
256
258
|
type: :development
|
257
259
|
prerelease: false
|
258
260
|
version_requirements: !ruby/object:Gem::Requirement
|
259
261
|
requirements:
|
260
262
|
- - "~>"
|
261
263
|
- !ruby/object:Gem::Version
|
262
|
-
version: '3.
|
264
|
+
version: '3.5'
|
263
265
|
- !ruby/object:Gem::Dependency
|
264
266
|
name: elasticgraph-datastore_core
|
265
267
|
requirement: !ruby/object:Gem::Requirement
|
266
268
|
requirements:
|
267
269
|
- - '='
|
268
270
|
- !ruby/object:Gem::Version
|
269
|
-
version: 0.
|
271
|
+
version: 0.19.0.0.rc1
|
270
272
|
type: :runtime
|
271
273
|
prerelease: false
|
272
274
|
version_requirements: !ruby/object:Gem::Requirement
|
273
275
|
requirements:
|
274
276
|
- - '='
|
275
277
|
- !ruby/object:Gem::Version
|
276
|
-
version: 0.
|
278
|
+
version: 0.19.0.0.rc1
|
277
279
|
- !ruby/object:Gem::Dependency
|
278
280
|
name: elasticgraph-schema_artifacts
|
279
281
|
requirement: !ruby/object:Gem::Requirement
|
280
282
|
requirements:
|
281
283
|
- - '='
|
282
284
|
- !ruby/object:Gem::Version
|
283
|
-
version: 0.
|
285
|
+
version: 0.19.0.0.rc1
|
284
286
|
type: :runtime
|
285
287
|
prerelease: false
|
286
288
|
version_requirements: !ruby/object:Gem::Requirement
|
287
289
|
requirements:
|
288
290
|
- - '='
|
289
291
|
- !ruby/object:Gem::Version
|
290
|
-
version: 0.
|
292
|
+
version: 0.19.0.0.rc1
|
291
293
|
- !ruby/object:Gem::Dependency
|
292
294
|
name: graphql
|
293
295
|
requirement: !ruby/object:Gem::Requirement
|
294
296
|
requirements:
|
295
297
|
- - "~>"
|
296
298
|
- !ruby/object:Gem::Version
|
297
|
-
version: 2.
|
299
|
+
version: 2.4.5
|
298
300
|
type: :runtime
|
299
301
|
prerelease: false
|
300
302
|
version_requirements: !ruby/object:Gem::Requirement
|
301
303
|
requirements:
|
302
304
|
- - "~>"
|
303
305
|
- !ruby/object:Gem::Version
|
304
|
-
version: 2.
|
306
|
+
version: 2.4.5
|
305
307
|
- !ruby/object:Gem::Dependency
|
306
308
|
name: elasticgraph-admin
|
307
309
|
requirement: !ruby/object:Gem::Requirement
|
308
310
|
requirements:
|
309
311
|
- - '='
|
310
312
|
- !ruby/object:Gem::Version
|
311
|
-
version: 0.
|
313
|
+
version: 0.19.0.0.rc1
|
312
314
|
type: :development
|
313
315
|
prerelease: false
|
314
316
|
version_requirements: !ruby/object:Gem::Requirement
|
315
317
|
requirements:
|
316
318
|
- - '='
|
317
319
|
- !ruby/object:Gem::Version
|
318
|
-
version: 0.
|
320
|
+
version: 0.19.0.0.rc1
|
319
321
|
- !ruby/object:Gem::Dependency
|
320
322
|
name: elasticgraph-elasticsearch
|
321
323
|
requirement: !ruby/object:Gem::Requirement
|
322
324
|
requirements:
|
323
325
|
- - '='
|
324
326
|
- !ruby/object:Gem::Version
|
325
|
-
version: 0.
|
327
|
+
version: 0.19.0.0.rc1
|
326
328
|
type: :development
|
327
329
|
prerelease: false
|
328
330
|
version_requirements: !ruby/object:Gem::Requirement
|
329
331
|
requirements:
|
330
332
|
- - '='
|
331
333
|
- !ruby/object:Gem::Version
|
332
|
-
version: 0.
|
334
|
+
version: 0.19.0.0.rc1
|
333
335
|
- !ruby/object:Gem::Dependency
|
334
336
|
name: elasticgraph-opensearch
|
335
337
|
requirement: !ruby/object:Gem::Requirement
|
336
338
|
requirements:
|
337
339
|
- - '='
|
338
340
|
- !ruby/object:Gem::Version
|
339
|
-
version: 0.
|
341
|
+
version: 0.19.0.0.rc1
|
340
342
|
type: :development
|
341
343
|
prerelease: false
|
342
344
|
version_requirements: !ruby/object:Gem::Requirement
|
343
345
|
requirements:
|
344
346
|
- - '='
|
345
347
|
- !ruby/object:Gem::Version
|
346
|
-
version: 0.
|
348
|
+
version: 0.19.0.0.rc1
|
347
349
|
- !ruby/object:Gem::Dependency
|
348
350
|
name: elasticgraph-indexer
|
349
351
|
requirement: !ruby/object:Gem::Requirement
|
350
352
|
requirements:
|
351
353
|
- - '='
|
352
354
|
- !ruby/object:Gem::Version
|
353
|
-
version: 0.
|
355
|
+
version: 0.19.0.0.rc1
|
354
356
|
type: :development
|
355
357
|
prerelease: false
|
356
358
|
version_requirements: !ruby/object:Gem::Requirement
|
357
359
|
requirements:
|
358
360
|
- - '='
|
359
361
|
- !ruby/object:Gem::Version
|
360
|
-
version: 0.
|
362
|
+
version: 0.19.0.0.rc1
|
361
363
|
- !ruby/object:Gem::Dependency
|
362
364
|
name: elasticgraph-schema_definition
|
363
365
|
requirement: !ruby/object:Gem::Requirement
|
364
366
|
requirements:
|
365
367
|
- - '='
|
366
368
|
- !ruby/object:Gem::Version
|
367
|
-
version: 0.
|
369
|
+
version: 0.19.0.0.rc1
|
368
370
|
type: :development
|
369
371
|
prerelease: false
|
370
372
|
version_requirements: !ruby/object:Gem::Requirement
|
371
373
|
requirements:
|
372
374
|
- - '='
|
373
375
|
- !ruby/object:Gem::Version
|
374
|
-
version: 0.
|
376
|
+
version: 0.19.0.0.rc1
|
375
377
|
description:
|
376
378
|
email:
|
377
379
|
- myron@squareup.com
|
@@ -459,10 +461,15 @@ files:
|
|
459
461
|
- lib/elastic_graph/graphql/schema/type.rb
|
460
462
|
- script/dump_time_zones
|
461
463
|
- script/dump_time_zones.java
|
462
|
-
homepage:
|
464
|
+
homepage: https://block.github.io/elasticgraph/
|
463
465
|
licenses:
|
464
466
|
- MIT
|
465
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
|
466
473
|
gem_category: core
|
467
474
|
post_install_message:
|
468
475
|
rdoc_options: []
|