elasticgraph-graphql 0.18.0.3 → 0.18.0.5
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/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
|