elasticgraph-graphql 0.18.0.4 → 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/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: []
|