elasticgraph-graphql 0.18.0.3 → 0.18.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- 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/config.rb +3 -3
- data/lib/elastic_graph/graphql/datastore_query/document_paginator.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/paginator.rb +1 -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 +3 -3
- data/lib/elastic_graph/graphql/decoded_cursor.rb +7 -7
- data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +12 -167
- data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +181 -0
- data/lib/elastic_graph/graphql/http_endpoint.rb +2 -2
- data/lib/elastic_graph/graphql/query_adapter/filters.rb +17 -20
- data/lib/elastic_graph/graphql/query_executor.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +2 -2
- 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 +4 -4
- data/lib/elastic_graph/graphql.rb +14 -2
- metadata +19 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53e5c759fafb5fe181ae65912c42b21d6d6dbe8cbd3558962cb5a56bc94944b6
|
4
|
+
data.tar.gz: 862a18dd839b8ee841b91430eb7188612c5483c5701930cf23cfbb22ce44e876
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4739bc5c2978a75528777b58073ba9e606a5964ff1adcfbb1b19c77eb43e29c5bb19e5aa137d2b530bd2937ca5499538149959b23cf55a7912f9b04b50809e3f
|
7
|
+
data.tar.gz: fb6b7226810f2f58bd7aedca80a9e1be097ffbbc8dbb5532805f9d98392006e2c4f0db7344c64250c3bd6adcdb917972e230324cc876c0639bba3a40d0601067
|
@@ -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
|
@@ -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
|
@@ -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"
|
@@ -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
|
@@ -127,7 +127,7 @@ module ElasticGraph
|
|
127
127
|
ERROR
|
128
128
|
end.join("\n\n")
|
129
129
|
|
130
|
-
raise SearchFailedError, "Got #{failures.size} search failure(s):\n\n#{formatted_failures}"
|
130
|
+
raise Errors::SearchFailedError, "Got #{failures.size} search failure(s):\n\n#{formatted_failures}"
|
131
131
|
end
|
132
132
|
|
133
133
|
# 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
|
@@ -6,30 +6,27 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
-
require "elastic_graph/constants"
|
10
|
-
require "elastic_graph/graphql/filtering/boolean_query"
|
11
9
|
require "elastic_graph/graphql/filtering/field_path"
|
12
|
-
require "elastic_graph/graphql/filtering/
|
13
|
-
require "elastic_graph/graphql/schema/enum_value"
|
10
|
+
require "elastic_graph/graphql/filtering/filter_node_interpreter"
|
14
11
|
require "elastic_graph/support/graphql_formatter"
|
15
12
|
require "elastic_graph/support/memoizable_data"
|
16
|
-
require "elastic_graph/support/time_util"
|
17
13
|
require "graphql"
|
18
14
|
|
19
15
|
module ElasticGraph
|
20
16
|
class GraphQL
|
21
17
|
module Filtering
|
22
|
-
#
|
18
|
+
# Responsible for interpreting a query's overall `filter`. Not tested directly; tests drive the `Query` interface instead.
|
19
|
+
#
|
23
20
|
# For more info on how this works, see:
|
24
21
|
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
|
25
22
|
# https://www.elastic.co/blog/lost-in-translation-boolean-operations-and-filters-in-the-bool-query
|
26
|
-
FilterInterpreter = Support::MemoizableData.define(:
|
23
|
+
FilterInterpreter = Support::MemoizableData.define(:filter_node_interpreter, :schema_names, :logger) do
|
27
24
|
# @implements FilterInterpreter
|
28
25
|
|
29
|
-
def initialize(
|
26
|
+
def initialize(filter_node_interpreter:, logger:)
|
30
27
|
super(
|
31
|
-
|
32
|
-
schema_names:
|
28
|
+
filter_node_interpreter: filter_node_interpreter,
|
29
|
+
schema_names: filter_node_interpreter.schema_names,
|
33
30
|
logger: logger
|
34
31
|
)
|
35
32
|
end
|
@@ -49,12 +46,12 @@ module ElasticGraph
|
|
49
46
|
end
|
50
47
|
|
51
48
|
def to_s
|
52
|
-
# The inspect/to_s output of `
|
49
|
+
# The inspect/to_s output of `filter_node_interpreter` and `logger` can be quite large and noisy. We generally don't care about
|
53
50
|
# those details but want to be able to tell at a glance if two `FilterInterpreter` instances are equal or not--and, if they
|
54
51
|
# aren't equal, which part is responsible for the inequality.
|
55
52
|
#
|
56
53
|
# Using the hash of the two initialize args provides us with that.
|
57
|
-
"#<data #{FilterInterpreter.name}
|
54
|
+
"#<data #{FilterInterpreter.name} filter_node_interpreter=(hash: #{filter_node_interpreter.hash}) logger=(hash: #{logger.hash})>"
|
58
55
|
end
|
59
56
|
alias_method :inspect, :to_s
|
60
57
|
|
@@ -67,7 +64,7 @@ module ElasticGraph
|
|
67
64
|
# below having to be aware of possible `nil` predicates.
|
68
65
|
expression = expression.compact if expression.is_a?(::Hash)
|
69
66
|
|
70
|
-
case
|
67
|
+
case filter_node_interpreter.identify_node_type(field_or_op, expression)
|
71
68
|
when :empty
|
72
69
|
# This is an "empty" filter predicate and we can ignore it.
|
73
70
|
when :not
|
@@ -90,23 +87,11 @@ module ElasticGraph
|
|
90
87
|
end
|
91
88
|
end
|
92
89
|
|
93
|
-
def identify_expression_type(field_or_op, expression)
|
94
|
-
return :empty if expression.nil? || expression == {}
|
95
|
-
return :not if field_or_op == schema_names.not
|
96
|
-
return :list_any_filter if field_or_op == schema_names.any_satisfy
|
97
|
-
return :all_of if field_or_op == schema_names.all_of
|
98
|
-
return :any_of if field_or_op == schema_names.any_of
|
99
|
-
return :operator if filter_operators.key?(field_or_op)
|
100
|
-
return :list_count if field_or_op == LIST_COUNTS_FIELD
|
101
|
-
return :sub_field if expression.is_a?(::Hash)
|
102
|
-
:unknown
|
103
|
-
end
|
104
|
-
|
105
90
|
# Indicates if the given `expression` applies filtering to subfields or just applies
|
106
91
|
# operators at the current field path.
|
107
92
|
def filters_on_sub_fields?(expression)
|
108
93
|
expression.any? do |field_or_op, sub_expression|
|
109
|
-
case
|
94
|
+
case filter_node_interpreter.identify_node_type(field_or_op, sub_expression)
|
110
95
|
when :sub_field
|
111
96
|
true
|
112
97
|
when :not, :list_any_filter
|
@@ -230,7 +215,7 @@ module ElasticGraph
|
|
230
215
|
# `operator` is a filtering operator, and `expression` is the value the filtering
|
231
216
|
# operator should be applied to. The `op_applicator` lambda, when called, will
|
232
217
|
# return a Clause instance (defined in this module).
|
233
|
-
bool_query = filter_operators.fetch(operator).call(field_path.from_root.join("."), expression)
|
218
|
+
bool_query = filter_node_interpreter.filter_operators.fetch(operator).call(field_path.from_root.join("."), expression)
|
234
219
|
bool_query&.merge_into(bool_node)
|
235
220
|
end
|
236
221
|
|
@@ -369,146 +354,6 @@ module ElasticGraph
|
|
369
354
|
end
|
370
355
|
end
|
371
356
|
|
372
|
-
def filter_operators
|
373
|
-
@filter_operators ||= build_filter_operators(runtime_metadata)
|
374
|
-
end
|
375
|
-
|
376
|
-
def build_filter_operators(runtime_metadata)
|
377
|
-
schema_names = runtime_metadata.schema_element_names
|
378
|
-
|
379
|
-
filter_by_time_of_day_script_id = runtime_metadata
|
380
|
-
.static_script_ids_by_scoped_name
|
381
|
-
.fetch("filter/by_time_of_day")
|
382
|
-
|
383
|
-
{
|
384
|
-
schema_names.equal_to_any_of => ->(field_name, value) {
|
385
|
-
values = to_datastore_value(value.compact.uniq) # : ::Array[untyped]
|
386
|
-
|
387
|
-
equality_sub_expression =
|
388
|
-
if field_name == "id"
|
389
|
-
# Use specialized "ids" query when querying on ID field.
|
390
|
-
# See: https://www.elastic.co/guide/en/elasticsearch/reference/7.15/query-dsl-ids-query.html
|
391
|
-
#
|
392
|
-
# We reject empty strings because we otherwise get an error from the datastore:
|
393
|
-
# "failed to create query: Ids can't be empty"
|
394
|
-
{ids: {values: values - [""]}}
|
395
|
-
else
|
396
|
-
{terms: {field_name => values}}
|
397
|
-
end
|
398
|
-
|
399
|
-
exists_sub_expression = {exists: {"field" => field_name}}
|
400
|
-
|
401
|
-
if !value.empty? && value.all?(&:nil?)
|
402
|
-
BooleanQuery.new(:must_not, [{bool: {filter: [exists_sub_expression]}}])
|
403
|
-
elsif value.include?(nil)
|
404
|
-
BooleanQuery.filter({bool: {
|
405
|
-
minimum_should_match: 1,
|
406
|
-
should: [
|
407
|
-
{bool: {filter: [equality_sub_expression]}},
|
408
|
-
{bool: {must_not: [{bool: {filter: [exists_sub_expression]}}]}}
|
409
|
-
]
|
410
|
-
}})
|
411
|
-
else
|
412
|
-
BooleanQuery.filter(equality_sub_expression)
|
413
|
-
end
|
414
|
-
},
|
415
|
-
schema_names.gt => ->(field_name, value) { RangeQuery.new(field_name, :gt, value) },
|
416
|
-
schema_names.gte => ->(field_name, value) { RangeQuery.new(field_name, :gte, value) },
|
417
|
-
schema_names.lt => ->(field_name, value) { RangeQuery.new(field_name, :lt, value) },
|
418
|
-
schema_names.lte => ->(field_name, value) { RangeQuery.new(field_name, :lte, value) },
|
419
|
-
schema_names.matches => ->(field_name, value) { BooleanQuery.must({match: {field_name => value}}) },
|
420
|
-
schema_names.matches_query => ->(field_name, value) do
|
421
|
-
allowed_edits_per_term = value.fetch(schema_names.allowed_edits_per_term).runtime_metadata.datastore_abbreviation
|
422
|
-
|
423
|
-
BooleanQuery.must(
|
424
|
-
{
|
425
|
-
match: {
|
426
|
-
field_name => {
|
427
|
-
query: value.fetch(schema_names.query),
|
428
|
-
# This is always a string field, even though the value is often an integer
|
429
|
-
fuzziness: allowed_edits_per_term.to_s,
|
430
|
-
operator: value[schema_names.require_all_terms] ? "AND" : "OR"
|
431
|
-
}
|
432
|
-
}
|
433
|
-
}
|
434
|
-
)
|
435
|
-
end,
|
436
|
-
schema_names.matches_phrase => ->(field_name, value) {
|
437
|
-
BooleanQuery.must(
|
438
|
-
{
|
439
|
-
match_phrase_prefix: {
|
440
|
-
field_name => {
|
441
|
-
query: value.fetch(schema_names.phrase)
|
442
|
-
}
|
443
|
-
}
|
444
|
-
}
|
445
|
-
)
|
446
|
-
},
|
447
|
-
|
448
|
-
# This filter operator wraps a geo distance query:
|
449
|
-
# https://www.elastic.co/guide/en/elasticsearch/reference/7.10/query-dsl-geo-distance-query.html
|
450
|
-
schema_names.near => ->(field_name, value) do
|
451
|
-
unit_abbreviation = value.fetch(schema_names.unit).runtime_metadata.datastore_abbreviation
|
452
|
-
|
453
|
-
BooleanQuery.filter({geo_distance: {
|
454
|
-
"distance" => "#{value.fetch(schema_names.max_distance)}#{unit_abbreviation}",
|
455
|
-
field_name => {
|
456
|
-
"lat" => value.fetch(schema_names.latitude),
|
457
|
-
"lon" => value.fetch(schema_names.longitude)
|
458
|
-
}
|
459
|
-
}})
|
460
|
-
end,
|
461
|
-
|
462
|
-
schema_names.time_of_day => ->(field_name, value) do
|
463
|
-
# To filter on time of day, we use the `filter/by_time_of_day` script. We accomplish
|
464
|
-
# this with a script because Elasticsearch/OpenSearch do not support this natively, and it's
|
465
|
-
# incredibly hard to implement correctly with respect to time zones without using a
|
466
|
-
# script. We considered indexing the `time_of_day` as a separate index field
|
467
|
-
# that we could directly filter on, but since we need the time of day to be relative
|
468
|
-
# to a specific time zone, there's no way to make that work with the reality of
|
469
|
-
# daylight savings time. For example, the `America/Los_Angeles` time zone has a -07:00
|
470
|
-
# UTC offset for part of the year and a `America/Los_Angeles` -08:00 UTC offset for
|
471
|
-
# part of the year. In a script we can use Java time zone APIs to handle this correctly.
|
472
|
-
params = {
|
473
|
-
field: field_name,
|
474
|
-
equal_to_any_of: list_of_nanos_of_day_from(value, schema_names.equal_to_any_of),
|
475
|
-
gt: nano_of_day_from(value, schema_names.gt),
|
476
|
-
gte: nano_of_day_from(value, schema_names.gte),
|
477
|
-
lt: nano_of_day_from(value, schema_names.lt),
|
478
|
-
lte: nano_of_day_from(value, schema_names.lte),
|
479
|
-
time_zone: value[schema_names.time_zone]
|
480
|
-
}.compact
|
481
|
-
|
482
|
-
# If there are no comparison operators, return `nil` instead of a `Clause` so that we avoid
|
483
|
-
# invoking the script for no reason. Note that `field` and `time_zone` will always be in
|
484
|
-
# `params` so we can't just check for an empty hash here.
|
485
|
-
if (params.keys - [:field, :time_zone]).any?
|
486
|
-
BooleanQuery.filter({script: {script: {id: filter_by_time_of_day_script_id, params: params}}})
|
487
|
-
end
|
488
|
-
end
|
489
|
-
}.freeze
|
490
|
-
end
|
491
|
-
|
492
|
-
def to_datastore_value(value)
|
493
|
-
case value
|
494
|
-
when ::Array
|
495
|
-
value.map { |v| to_datastore_value(v) }
|
496
|
-
when Schema::EnumValue
|
497
|
-
value.name.to_s
|
498
|
-
else
|
499
|
-
value
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
def nano_of_day_from(value, field)
|
504
|
-
local_time = value[field]
|
505
|
-
Support::TimeUtil.nano_of_day_from_local_time(local_time) if local_time
|
506
|
-
end
|
507
|
-
|
508
|
-
def list_of_nanos_of_day_from(value, field)
|
509
|
-
value[field]&.map { |t| Support::TimeUtil.nano_of_day_from_local_time(t) }
|
510
|
-
end
|
511
|
-
|
512
357
|
# Counts how many clauses in `bool_query` are required to match for a document to be a search hit.
|
513
358
|
def required_matching_clause_count(bool_query)
|
514
359
|
bool_query.reduce(0) do |count, (occurrence, clauses)|
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# Copyright 2024 Block, Inc.
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
#
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "elastic_graph/constants"
|
10
|
+
require "elastic_graph/graphql/filtering/boolean_query"
|
11
|
+
require "elastic_graph/graphql/filtering/range_query"
|
12
|
+
require "elastic_graph/graphql/schema/enum_value"
|
13
|
+
require "elastic_graph/support/memoizable_data"
|
14
|
+
require "elastic_graph/support/time_util"
|
15
|
+
|
16
|
+
module ElasticGraph
|
17
|
+
class GraphQL
|
18
|
+
module Filtering
|
19
|
+
# Responsible for interpreting a single `node` in a `filter` expression.
|
20
|
+
FilterNodeInterpreter = Support::MemoizableData.define(:runtime_metadata, :schema_names) do
|
21
|
+
# @implements FilterNodeInterpreter
|
22
|
+
|
23
|
+
def initialize(runtime_metadata:)
|
24
|
+
super(runtime_metadata: runtime_metadata, schema_names: runtime_metadata.schema_element_names)
|
25
|
+
end
|
26
|
+
|
27
|
+
def identify_node_type(field_or_op, sub_expression)
|
28
|
+
return :empty if sub_expression.nil? || sub_expression == {}
|
29
|
+
return :not if field_or_op == schema_names.not
|
30
|
+
return :list_any_filter if field_or_op == schema_names.any_satisfy
|
31
|
+
return :all_of if field_or_op == schema_names.all_of
|
32
|
+
return :any_of if field_or_op == schema_names.any_of
|
33
|
+
return :operator if filter_operators.key?(field_or_op)
|
34
|
+
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
|
+
|
39
|
+
def filter_operators
|
40
|
+
@filter_operators ||= build_filter_operators(runtime_metadata)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def build_filter_operators(runtime_metadata)
|
46
|
+
filter_by_time_of_day_script_id = runtime_metadata
|
47
|
+
.static_script_ids_by_scoped_name
|
48
|
+
.fetch("filter/by_time_of_day")
|
49
|
+
|
50
|
+
{
|
51
|
+
schema_names.equal_to_any_of => ->(field_name, value) {
|
52
|
+
values = to_datastore_value(value.compact.uniq) # : ::Array[untyped]
|
53
|
+
|
54
|
+
equality_sub_expression =
|
55
|
+
if field_name == "id"
|
56
|
+
# Use specialized "ids" query when querying on ID field.
|
57
|
+
# See: https://www.elastic.co/guide/en/elasticsearch/reference/7.15/query-dsl-ids-query.html
|
58
|
+
#
|
59
|
+
# We reject empty strings because we otherwise get an error from the datastore:
|
60
|
+
# "failed to create query: Ids can't be empty"
|
61
|
+
{ids: {values: values - [""]}}
|
62
|
+
else
|
63
|
+
{terms: {field_name => values}}
|
64
|
+
end
|
65
|
+
|
66
|
+
exists_sub_expression = {exists: {"field" => field_name}}
|
67
|
+
|
68
|
+
if !value.empty? && value.all?(&:nil?)
|
69
|
+
BooleanQuery.new(:must_not, [{bool: {filter: [exists_sub_expression]}}])
|
70
|
+
elsif value.include?(nil)
|
71
|
+
BooleanQuery.filter({bool: {
|
72
|
+
minimum_should_match: 1,
|
73
|
+
should: [
|
74
|
+
{bool: {filter: [equality_sub_expression]}},
|
75
|
+
{bool: {must_not: [{bool: {filter: [exists_sub_expression]}}]}}
|
76
|
+
]
|
77
|
+
}})
|
78
|
+
else
|
79
|
+
BooleanQuery.filter(equality_sub_expression)
|
80
|
+
end
|
81
|
+
},
|
82
|
+
schema_names.gt => ->(field_name, value) { RangeQuery.new(field_name, :gt, value) },
|
83
|
+
schema_names.gte => ->(field_name, value) { RangeQuery.new(field_name, :gte, value) },
|
84
|
+
schema_names.lt => ->(field_name, value) { RangeQuery.new(field_name, :lt, value) },
|
85
|
+
schema_names.lte => ->(field_name, value) { RangeQuery.new(field_name, :lte, value) },
|
86
|
+
schema_names.matches => ->(field_name, value) { BooleanQuery.must({match: {field_name => value}}) },
|
87
|
+
schema_names.matches_query => ->(field_name, value) do
|
88
|
+
allowed_edits_per_term = value.fetch(schema_names.allowed_edits_per_term).runtime_metadata.datastore_abbreviation
|
89
|
+
|
90
|
+
BooleanQuery.must(
|
91
|
+
{
|
92
|
+
match: {
|
93
|
+
field_name => {
|
94
|
+
query: value.fetch(schema_names.query),
|
95
|
+
# This is always a string field, even though the value is often an integer
|
96
|
+
fuzziness: allowed_edits_per_term.to_s,
|
97
|
+
operator: value[schema_names.require_all_terms] ? "AND" : "OR"
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
)
|
102
|
+
end,
|
103
|
+
schema_names.matches_phrase => ->(field_name, value) {
|
104
|
+
BooleanQuery.must(
|
105
|
+
{
|
106
|
+
match_phrase_prefix: {
|
107
|
+
field_name => {
|
108
|
+
query: value.fetch(schema_names.phrase)
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
)
|
113
|
+
},
|
114
|
+
|
115
|
+
# This filter operator wraps a geo distance query:
|
116
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/7.10/query-dsl-geo-distance-query.html
|
117
|
+
schema_names.near => ->(field_name, value) do
|
118
|
+
unit_abbreviation = value.fetch(schema_names.unit).runtime_metadata.datastore_abbreviation
|
119
|
+
|
120
|
+
BooleanQuery.filter({geo_distance: {
|
121
|
+
"distance" => "#{value.fetch(schema_names.max_distance)}#{unit_abbreviation}",
|
122
|
+
field_name => {
|
123
|
+
"lat" => value.fetch(schema_names.latitude),
|
124
|
+
"lon" => value.fetch(schema_names.longitude)
|
125
|
+
}
|
126
|
+
}})
|
127
|
+
end,
|
128
|
+
|
129
|
+
schema_names.time_of_day => ->(field_name, value) do
|
130
|
+
# To filter on time of day, we use the `filter/by_time_of_day` script. We accomplish
|
131
|
+
# this with a script because Elasticsearch/OpenSearch do not support this natively, and it's
|
132
|
+
# incredibly hard to implement correctly with respect to time zones without using a
|
133
|
+
# script. We considered indexing the `time_of_day` as a separate index field
|
134
|
+
# that we could directly filter on, but since we need the time of day to be relative
|
135
|
+
# to a specific time zone, there's no way to make that work with the reality of
|
136
|
+
# daylight savings time. For example, the `America/Los_Angeles` time zone has a -07:00
|
137
|
+
# UTC offset for part of the year and a `America/Los_Angeles` -08:00 UTC offset for
|
138
|
+
# part of the year. In a script we can use Java time zone APIs to handle this correctly.
|
139
|
+
params = {
|
140
|
+
field: field_name,
|
141
|
+
equal_to_any_of: list_of_nanos_of_day_from(value, schema_names.equal_to_any_of),
|
142
|
+
gt: nano_of_day_from(value, schema_names.gt),
|
143
|
+
gte: nano_of_day_from(value, schema_names.gte),
|
144
|
+
lt: nano_of_day_from(value, schema_names.lt),
|
145
|
+
lte: nano_of_day_from(value, schema_names.lte),
|
146
|
+
time_zone: value[schema_names.time_zone]
|
147
|
+
}.compact
|
148
|
+
|
149
|
+
# If there are no comparison operators, return `nil` instead of a `Clause` so that we avoid
|
150
|
+
# invoking the script for no reason. Note that `field` and `time_zone` will always be in
|
151
|
+
# `params` so we can't just check for an empty hash here.
|
152
|
+
if (params.keys - [:field, :time_zone]).any?
|
153
|
+
BooleanQuery.filter({script: {script: {id: filter_by_time_of_day_script_id, params: params}}})
|
154
|
+
end
|
155
|
+
end
|
156
|
+
}.freeze
|
157
|
+
end
|
158
|
+
|
159
|
+
def to_datastore_value(value)
|
160
|
+
case value
|
161
|
+
when ::Array
|
162
|
+
value.map { |v| to_datastore_value(v) }
|
163
|
+
when Schema::EnumValue
|
164
|
+
value.name.to_s
|
165
|
+
else
|
166
|
+
value
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def nano_of_day_from(value, field)
|
171
|
+
local_time = value[field]
|
172
|
+
Support::TimeUtil.nano_of_day_from_local_time(local_time) if local_time
|
173
|
+
end
|
174
|
+
|
175
|
+
def list_of_nanos_of_day_from(value, field)
|
176
|
+
value[field]&.map { |t| Support::TimeUtil.nano_of_day_from_local_time(t) }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -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
|
|
@@ -222,7 +222,7 @@ module ElasticGraph
|
|
222
222
|
|
223
223
|
# Steep weirdly expects them here...
|
224
224
|
# @dynamic initialize, config, logger, runtime_metadata, graphql_schema_string, datastore_core, clock
|
225
|
-
# @dynamic graphql_http_endpoint, graphql_query_executor, schema, datastore_search_router, filter_interpreter
|
225
|
+
# @dynamic graphql_http_endpoint, graphql_query_executor, schema, datastore_search_router, filter_interpreter, filter_node_interpreter
|
226
226
|
# @dynamic datastore_query_builder, graphql_gem_plugins, graphql_resolvers, datastore_query_adapters, monotonic_clock
|
227
227
|
# @dynamic load_dependencies_eagerly, self.from_parsed_yaml, filter_args_translator, sub_aggregation_grouping_adapter
|
228
228
|
end
|
@@ -13,7 +13,7 @@ require "elastic_graph/support/memoizable_data"
|
|
13
13
|
module ElasticGraph
|
14
14
|
class GraphQL
|
15
15
|
class QueryAdapter
|
16
|
-
class Filters < Support::MemoizableData.define(:schema_element_names, :filter_args_translator)
|
16
|
+
class Filters < Support::MemoizableData.define(:schema_element_names, :filter_args_translator, :filter_node_interpreter)
|
17
17
|
def call(field:, query:, args:, lookahead:, context:)
|
18
18
|
filter_from_args = filter_args_translator.translate_filter_args(field: field, args: args)
|
19
19
|
automatic_filter = build_automatic_filter(filter_from_args: filter_from_args, query: query)
|
@@ -69,28 +69,25 @@ module ElasticGraph
|
|
69
69
|
#
|
70
70
|
# - The field paths we are filtering on.
|
71
71
|
# - The field paths that are sourced from `SELF_RELATIONSHIP_NAME`.
|
72
|
-
def determine_paths_to_check(
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
if (
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
72
|
+
def determine_paths_to_check(expression, index_fields_by_path, parent_path: nil)
|
73
|
+
return [] unless expression.is_a?(::Hash)
|
74
|
+
|
75
|
+
expression.compact.flat_map do |field_or_op, sub_expression|
|
76
|
+
if filter_node_interpreter.identify_node_type(field_or_op, sub_expression) == :sub_field
|
77
|
+
path = parent_path ? "#{parent_path}.#{field_or_op}" : field_or_op
|
78
|
+
if (index_field = index_fields_by_path[path])
|
79
|
+
# We've recursed down to a leaf field path. We want that path to be returned if the
|
80
|
+
# field is sourced from SELF_RELATIONSHIP_NAME.
|
81
|
+
(index_field.source == SELF_RELATIONSHIP_NAME) ? [path] : []
|
82
|
+
else
|
83
|
+
determine_paths_to_check(sub_expression, index_fields_by_path, parent_path: path)
|
84
|
+
end
|
85
|
+
elsif sub_expression.is_a?(::Array)
|
86
|
+
sub_expression.flat_map do |sub_filter|
|
83
87
|
determine_paths_to_check(sub_filter, index_fields_by_path, parent_path: parent_path)
|
84
88
|
end
|
85
|
-
elsif field_name == schema_element_names.not
|
86
|
-
# While `not` represents negation, we don't have to negate anything here because the negation
|
87
|
-
# is handled later (when we use `filter_value_set_extractor`). Here we are just determining the
|
88
|
-
# paths to check. We want to recurse without adding `not` to the `parent_path` since it's not
|
89
|
-
# part of the field path.
|
90
|
-
determine_paths_to_check(value, index_fields_by_path, parent_path: parent_path)
|
91
89
|
else
|
92
|
-
|
93
|
-
determine_paths_to_check(value, index_fields_by_path, parent_path: "#{path}.")
|
90
|
+
determine_paths_to_check(sub_expression, index_fields_by_path, parent_path: parent_path)
|
94
91
|
end
|
95
92
|
end
|
96
93
|
end
|
@@ -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
|
@@ -48,7 +48,7 @@ module ElasticGraph
|
|
48
48
|
|
49
49
|
def canonical_name_for(name, element_type)
|
50
50
|
schema_element_names.canonical_name_for(name) ||
|
51
|
-
raise(SchemaError, "#{element_type} `#{name}` is not a defined schema element")
|
51
|
+
raise(Errors::SchemaError, "#{element_type} `#{name}` is not a defined schema element")
|
52
52
|
end
|
53
53
|
end
|
54
54
|
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"
|
@@ -88,7 +88,7 @@ module ElasticGraph
|
|
88
88
|
if index_definition_name.include?(ROLLOVER_INDEX_INFIX_MARKER)
|
89
89
|
raise ArgumentError, "`#{index_definition_name}` is the name of a rollover index; pass the name of the parent index definition instead."
|
90
90
|
else
|
91
|
-
raise NotFoundError, "The index definition `#{index_definition_name}` does not appear to exist. Is it misspelled?"
|
91
|
+
raise Errors::NotFoundError, "The index definition `#{index_definition_name}` does not appear to exist. Is it misspelled?"
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
@@ -133,7 +133,7 @@ module ElasticGraph
|
|
133
133
|
rescue KeyError => e
|
134
134
|
msg = "No type named #{type_name} could be found"
|
135
135
|
msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
|
136
|
-
raise NotFoundError, msg
|
136
|
+
raise Errors::NotFoundError, msg
|
137
137
|
end
|
138
138
|
|
139
139
|
def resolver
|
@@ -152,7 +152,7 @@ module ElasticGraph
|
|
152
152
|
@indexed_document_types_by_index_definition_name ||= indexed_document_types.each_with_object({}) do |type, hash|
|
153
153
|
type.index_definitions.each do |index_def|
|
154
154
|
if hash.key?(index_def.name)
|
155
|
-
raise SchemaError, "DatastoreCore::IndexDefinition #{index_def.name} is used multiple times: #{type} vs #{hash[index_def.name]}"
|
155
|
+
raise Errors::SchemaError, "DatastoreCore::IndexDefinition #{index_def.name} is used multiple times: #{type} vs #{hash[index_def.name]}"
|
156
156
|
end
|
157
157
|
|
158
158
|
hash[index_def.name] = type
|
@@ -181,7 +181,11 @@ module ElasticGraph
|
|
181
181
|
|
182
182
|
[
|
183
183
|
GraphQL::QueryAdapter::Pagination.new(schema_element_names: schema_element_names),
|
184
|
-
GraphQL::QueryAdapter::Filters.new(
|
184
|
+
GraphQL::QueryAdapter::Filters.new(
|
185
|
+
schema_element_names: schema_element_names,
|
186
|
+
filter_args_translator: filter_args_translator,
|
187
|
+
filter_node_interpreter: filter_node_interpreter
|
188
|
+
),
|
185
189
|
GraphQL::QueryAdapter::Sort.new(order_by_arg_name: schema_element_names.order_by),
|
186
190
|
Aggregation::QueryAdapter.new(
|
187
191
|
schema: schema,
|
@@ -199,7 +203,15 @@ module ElasticGraph
|
|
199
203
|
def filter_interpreter
|
200
204
|
@filter_interpreter ||= begin
|
201
205
|
require "elastic_graph/graphql/filtering/filter_interpreter"
|
202
|
-
Filtering::FilterInterpreter.new(
|
206
|
+
Filtering::FilterInterpreter.new(filter_node_interpreter: filter_node_interpreter, logger: logger)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# @private
|
211
|
+
def filter_node_interpreter
|
212
|
+
@filter_node_interpreter ||= begin
|
213
|
+
require "elastic_graph/graphql/filtering/filter_node_interpreter"
|
214
|
+
Filtering::FilterNodeInterpreter.new(runtime_metadata: runtime_metadata)
|
203
215
|
end
|
204
216
|
end
|
205
217
|
|
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.18.0.
|
4
|
+
version: 0.18.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Myron Marston
|
8
|
+
- Ben VandenBos
|
9
|
+
- Square Engineering
|
8
10
|
autorequire:
|
9
11
|
bindir: exe
|
10
12
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
13
|
+
date: 2024-09-20 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: rubocop-factory_bot
|
@@ -266,28 +268,28 @@ dependencies:
|
|
266
268
|
requirements:
|
267
269
|
- - '='
|
268
270
|
- !ruby/object:Gem::Version
|
269
|
-
version: 0.18.0.
|
271
|
+
version: 0.18.0.5
|
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.18.0.
|
278
|
+
version: 0.18.0.5
|
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.18.0.
|
285
|
+
version: 0.18.0.5
|
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.18.0.
|
292
|
+
version: 0.18.0.5
|
291
293
|
- !ruby/object:Gem::Dependency
|
292
294
|
name: graphql
|
293
295
|
requirement: !ruby/object:Gem::Requirement
|
@@ -308,70 +310,70 @@ dependencies:
|
|
308
310
|
requirements:
|
309
311
|
- - '='
|
310
312
|
- !ruby/object:Gem::Version
|
311
|
-
version: 0.18.0.
|
313
|
+
version: 0.18.0.5
|
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.18.0.
|
320
|
+
version: 0.18.0.5
|
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.18.0.
|
327
|
+
version: 0.18.0.5
|
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.18.0.
|
334
|
+
version: 0.18.0.5
|
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.18.0.
|
341
|
+
version: 0.18.0.5
|
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.18.0.
|
348
|
+
version: 0.18.0.5
|
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.18.0.
|
355
|
+
version: 0.18.0.5
|
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.18.0.
|
362
|
+
version: 0.18.0.5
|
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.18.0.
|
369
|
+
version: 0.18.0.5
|
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.18.0.
|
376
|
+
version: 0.18.0.5
|
375
377
|
description:
|
376
378
|
email:
|
377
379
|
- myron@squareup.com
|
@@ -418,6 +420,7 @@ files:
|
|
418
420
|
- lib/elastic_graph/graphql/filtering/field_path.rb
|
419
421
|
- lib/elastic_graph/graphql/filtering/filter_args_translator.rb
|
420
422
|
- lib/elastic_graph/graphql/filtering/filter_interpreter.rb
|
423
|
+
- lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb
|
421
424
|
- lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb
|
422
425
|
- lib/elastic_graph/graphql/filtering/range_query.rb
|
423
426
|
- lib/elastic_graph/graphql/http_endpoint.rb
|