elasticgraph-graphql 0.19.0.0.rc1 → 0.19.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/elasticgraph-graphql.gemspec +1 -1
- data/lib/elastic_graph/graphql/aggregation/query_optimizer.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +9 -2
- data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +10 -76
- data/lib/elastic_graph/graphql/datastore_query.rb +11 -5
- data/lib/elastic_graph/graphql/filtering/filter_args_translator.rb +1 -1
- data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +0 -10
- data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +12 -15
- data/lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb +66 -15
- data/lib/elastic_graph/graphql/http_endpoint.rb +1 -1
- data/lib/elastic_graph/graphql/query_adapter/filters.rb +8 -2
- data/lib/elastic_graph/graphql/resolvers/query_source.rb +1 -1
- data/lib/elastic_graph/graphql.rb +4 -3
- metadata +27 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87e3b12d1297421b2631906b7c682d38f87d4111fc9929d56d2be55f6cc481fe
|
4
|
+
data.tar.gz: bb454a309d0869c7c0d446d5ca2882cda4d005e412966db327e3dab56c0916f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 803c34dfff0d787e61987f5d1877ea93485180161d3f1fc730bf6989d52f17e929c7fe476b434d4d59d59acf86622c5f68e6b2ed2f050df4abcdc13257c33bb8
|
7
|
+
data.tar.gz: 505707f3dd8bab37cc388cc542c343d4a541f5354c8e192d131a3cff36096e2d1ad9578872a68b169806f476acf0bdfd46d4db5389bb8e1dce09a984745265df
|
@@ -13,7 +13,7 @@ ElasticGraphGemspecHelper.define_elasticgraph_gem(gemspec_file: __FILE__, catego
|
|
13
13
|
|
14
14
|
spec.add_dependency "elasticgraph-datastore_core", eg_version
|
15
15
|
spec.add_dependency "elasticgraph-schema_artifacts", eg_version
|
16
|
-
spec.add_dependency "graphql", "~> 2.4.
|
16
|
+
spec.add_dependency "graphql", "~> 2.4.8"
|
17
17
|
|
18
18
|
spec.add_development_dependency "elasticgraph-admin", eg_version
|
19
19
|
spec.add_development_dependency "elasticgraph-elasticsearch", eg_version
|
@@ -154,7 +154,7 @@ module ElasticGraph
|
|
154
154
|
# If there are no aggregations, there's nothing to unmerge--just return it as is.
|
155
155
|
return response_from_merged_query unless (aggs = response_from_merged_query["aggregations"])
|
156
156
|
|
157
|
-
prefix = @unique_prefix_by_query[original_query]
|
157
|
+
prefix = @unique_prefix_by_query[original_query] # : ::String
|
158
158
|
agg_names = original_query.aggregations.keys.map { |name| "#{prefix}#{name}" }.to_set
|
159
159
|
|
160
160
|
filtered_aggs = aggs
|
@@ -14,8 +14,13 @@ module ElasticGraph
|
|
14
14
|
class DatastoreQuery
|
15
15
|
# Responsible for building a search index expression for a specific query based on the filters.
|
16
16
|
class IndexExpressionBuilder
|
17
|
-
def initialize(schema_names:)
|
18
|
-
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(
|
17
|
+
def initialize(filter_node_interpreter:, schema_names:)
|
18
|
+
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(
|
19
|
+
filter_node_interpreter,
|
20
|
+
schema_names,
|
21
|
+
Support::TimeSet::ALL,
|
22
|
+
Support::TimeSet::EMPTY
|
23
|
+
) do |operator, filter_value|
|
19
24
|
case operator
|
20
25
|
when :gt, :gte, :lt, :lte
|
21
26
|
if date_string?(filter_value)
|
@@ -73,6 +78,8 @@ module ElasticGraph
|
|
73
78
|
|
74
79
|
time_set = @filter_value_set_extractor.extract_filter_value_set(filter_hashes, [index_def.timestamp_field_path])
|
75
80
|
|
81
|
+
return IndexExpression.only(index_def.index_expression_for_search) if time_set.nil?
|
82
|
+
|
76
83
|
if time_set.empty?
|
77
84
|
return require_indices ?
|
78
85
|
# Indices are required. Given the time set is empty, it's impossible for any documents to match our search.
|
@@ -13,21 +13,19 @@ module ElasticGraph
|
|
13
13
|
class DatastoreQuery
|
14
14
|
# Responsible for picking routing values for a specific query based on the filters.
|
15
15
|
class RoutingPicker
|
16
|
-
def initialize(schema_names:)
|
17
|
-
# @type var all_values_set: _RoutingValueSet
|
16
|
+
def initialize(filter_node_interpreter:, schema_names:)
|
18
17
|
all_values_set = RoutingValueSet::ALL
|
19
18
|
empty_set = RoutingValueSet::EMPTY
|
20
19
|
|
21
|
-
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(
|
20
|
+
@filter_value_set_extractor = Filtering::FilterValueSetExtractor.new(
|
21
|
+
filter_node_interpreter,
|
22
|
+
schema_names,
|
23
|
+
all_values_set,
|
24
|
+
empty_set
|
25
|
+
) do |operator, filter_value|
|
22
26
|
if operator == :equal_to_any_of
|
23
27
|
# This calls `.compact` to remove `nil` filter_value values
|
24
28
|
RoutingValueSet.of(filter_value.compact)
|
25
|
-
else # gt, lt, gte, lte, matches
|
26
|
-
# With one of these inexact/inequality operators, we don't have a way to precisely represent
|
27
|
-
# the set of values. Instead, we represent it with the special UnboundedWithExclusions
|
28
|
-
# implementation since when these operators are used the set is unbounded (there's an infinite
|
29
|
-
# number of values in the set) but it doesn't contain all values (it has some exclusions).
|
30
|
-
RoutingValueSet::UnboundedWithExclusions
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|
@@ -55,13 +53,11 @@ module ElasticGraph
|
|
55
53
|
# end
|
56
54
|
# ```
|
57
55
|
def extract_eligible_routing_values(filter_hashes, routing_field_paths)
|
58
|
-
@filter_value_set_extractor.extract_filter_value_set(filter_hashes, routing_field_paths)
|
56
|
+
@filter_value_set_extractor.extract_filter_value_set(filter_hashes, routing_field_paths)&.to_return_value
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
62
60
|
class RoutingValueSet < Data.define(:type, :routing_values)
|
63
|
-
# @dynamic ==
|
64
|
-
|
65
61
|
def self.of(routing_values)
|
66
62
|
new(:inclusive, routing_values.to_set)
|
67
63
|
end
|
@@ -73,15 +69,7 @@ module ElasticGraph
|
|
73
69
|
ALL = of_all_except([])
|
74
70
|
EMPTY = of([])
|
75
71
|
|
76
|
-
def intersection(
|
77
|
-
# Here we return `self` to preserve the commutative property of `intersection`. Returning `self`
|
78
|
-
# here matches the behavior of `UnboundedWithExclusions.intersection`. See the comment there for
|
79
|
-
# rationale.
|
80
|
-
return self if other_set == UnboundedWithExclusions
|
81
|
-
|
82
|
-
# @type var other: RoutingValueSet
|
83
|
-
other = _ = other_set
|
84
|
-
|
72
|
+
def intersection(other)
|
85
73
|
if inclusive? && other.inclusive?
|
86
74
|
# Since both sets are inclusive, we can just delegate to `Set#intersection` here.
|
87
75
|
RoutingValueSet.of(routing_values.intersection(other.routing_values))
|
@@ -111,15 +99,7 @@ module ElasticGraph
|
|
111
99
|
end
|
112
100
|
end
|
113
101
|
|
114
|
-
def union(
|
115
|
-
# Here we return `other` to preserve the commutative property of `union`. Returning `other`
|
116
|
-
# here matches the behavior of `UnboundedWithExclusions.union`. See the comment there for
|
117
|
-
# rationale.
|
118
|
-
return other_set if other_set == UnboundedWithExclusions
|
119
|
-
|
120
|
-
# @type var other: RoutingValueSet
|
121
|
-
other = _ = other_set
|
122
|
-
|
102
|
+
def union(other)
|
123
103
|
if inclusive? && other.inclusive?
|
124
104
|
# Since both sets are inclusive, we can just delegate to `Set#union` here.
|
125
105
|
RoutingValueSet.of(routing_values.union(other.routing_values))
|
@@ -180,52 +160,6 @@ module ElasticGraph
|
|
180
160
|
def get_included_and_excluded_values(other)
|
181
161
|
inclusive? ? [routing_values, other.routing_values] : [other.routing_values, routing_values]
|
182
162
|
end
|
183
|
-
|
184
|
-
# This `RoutingValueSet` implementation is used for otherwise unrepresentable cases. We use it when
|
185
|
-
# a filter on one of the `routing_field_paths` uses an inequality like:
|
186
|
-
#
|
187
|
-
# {routing_field: {gt: "abc"}}
|
188
|
-
#
|
189
|
-
# In a case like that, the set is unbounded (there's an infinite number of values that are greater
|
190
|
-
# than `"abc"`...), but it's not `RoutingValueSet::ALL`--since it's based on an inequality, there are
|
191
|
-
# _some_ values that are excluded from the set. But we can't use `RoutingValueSet.of_all_except(...)`
|
192
|
-
# because the set of exclusions is also unbounded!
|
193
|
-
#
|
194
|
-
# When our filter value extraction results in this set, we must search all shards of the index and
|
195
|
-
# cannot pass any `routing` value to the datastore at all.
|
196
|
-
module UnboundedWithExclusions
|
197
|
-
# @dynamic self.==
|
198
|
-
|
199
|
-
def self.intersection(other)
|
200
|
-
# Technically, the "true" intersection would be `other - values_of(self)` but as we don't have
|
201
|
-
# any known values from this unbounded set, we just return `other`. It's OK to include extra values
|
202
|
-
# in the set (we'll search additional shards) but not OK to fail to include necessary values in
|
203
|
-
# the set (we'd avoid searching a shard that may have matching documents) so we err on the side of
|
204
|
-
# including more values.
|
205
|
-
other
|
206
|
-
end
|
207
|
-
|
208
|
-
def self.union(other)
|
209
|
-
# Since our set here is unbounded, the resulting union is also unbounded. This errs on the side
|
210
|
-
# of safety since this set's `to_return_value` returns `nil` to cause the datastore to search
|
211
|
-
# all shards.
|
212
|
-
self
|
213
|
-
end
|
214
|
-
|
215
|
-
def self.negate
|
216
|
-
# This here is the only difference in behavior of this set implementation vs `RoutingValueSet::ALL`.
|
217
|
-
# Where as `ALL.negate` returns an empty set, we treat `negate` as a no-op. We do that because the
|
218
|
-
# negation of an inexact unbounded set is still an inexact unbounded set. While it flips which values
|
219
|
-
# are in or out of the set, this object is still the representation in our datamodel for that case.
|
220
|
-
self
|
221
|
-
end
|
222
|
-
|
223
|
-
def self.to_return_value
|
224
|
-
# Here we return `nil` to make sure that the datastore searches all shards, since we don't have
|
225
|
-
# any information we can use to safely limit what shards it searches.
|
226
|
-
nil
|
227
|
-
end
|
228
|
-
end
|
229
163
|
end
|
230
164
|
|
231
165
|
# `Query::RoutingPicker` exists only for use by `Query` and is effectively private.
|
@@ -344,17 +344,23 @@ module ElasticGraph
|
|
344
344
|
|
345
345
|
# Encapsulates dependencies of `Query`, giving us something we can expose off of `application`
|
346
346
|
# to build queries when desired.
|
347
|
-
class Builder < Support::MemoizableData.define(:runtime_metadata, :logger, :query_defaults)
|
348
|
-
def self.with(runtime_metadata:, logger:, **query_defaults)
|
349
|
-
new(runtime_metadata
|
347
|
+
class Builder < Support::MemoizableData.define(:runtime_metadata, :logger, :filter_node_interpreter, :query_defaults)
|
348
|
+
def self.with(runtime_metadata:, logger:, filter_node_interpreter:, **query_defaults)
|
349
|
+
new(runtime_metadata:, logger:, filter_node_interpreter:, query_defaults:)
|
350
350
|
end
|
351
351
|
|
352
352
|
def routing_picker
|
353
|
-
@routing_picker ||= RoutingPicker.new(
|
353
|
+
@routing_picker ||= RoutingPicker.new(
|
354
|
+
filter_node_interpreter: filter_node_interpreter,
|
355
|
+
schema_names: runtime_metadata.schema_element_names
|
356
|
+
)
|
354
357
|
end
|
355
358
|
|
356
359
|
def index_expression_builder
|
357
|
-
@index_expression_builder ||= IndexExpressionBuilder.new(
|
360
|
+
@index_expression_builder ||= IndexExpressionBuilder.new(
|
361
|
+
filter_node_interpreter: filter_node_interpreter,
|
362
|
+
schema_names: runtime_metadata.schema_element_names
|
363
|
+
)
|
358
364
|
end
|
359
365
|
|
360
366
|
def new_query(**options)
|
@@ -26,7 +26,7 @@ module ElasticGraph
|
|
26
26
|
# vs GraphQL.
|
27
27
|
def translate_filter_args(field:, args:)
|
28
28
|
return nil unless (filter_hash = args[filter_arg_name])
|
29
|
-
filter_type = field.schema.type_from(field.graphql_field.arguments
|
29
|
+
filter_type = field.schema.type_from(field.graphql_field.arguments.fetch(filter_arg_name).type)
|
30
30
|
convert(filter_type, filter_hash)
|
31
31
|
end
|
32
32
|
|
@@ -137,8 +137,6 @@ module ElasticGraph
|
|
137
137
|
# this because we do not generate `any_satisfy` filters on `object` list fields (instead,
|
138
138
|
# they get generated on their leaf fields).
|
139
139
|
def process_list_any_filter_expression(bool_node, filter, field_path)
|
140
|
-
return if filter.nil? || filter == {}
|
141
|
-
|
142
140
|
if filters_on_sub_fields?(filter)
|
143
141
|
process_any_satisfy_filter_expression_on_nested_object_list(bool_node, filter, field_path)
|
144
142
|
else
|
@@ -195,8 +193,6 @@ module ElasticGraph
|
|
195
193
|
# * `filter: {anyOf: [{field: null}]}` -> return all results
|
196
194
|
# * `filter: {anyOf: [{field: null}, {field: ...}]}` -> return all results
|
197
195
|
def process_any_of_expression(bool_node, expressions, field_path)
|
198
|
-
return if expressions.nil? || expressions == {}
|
199
|
-
|
200
196
|
if expressions.empty?
|
201
197
|
# When our `expressions` array is empty, we want to match no documents. However, that's
|
202
198
|
# not the behavior the datastore will give us if we have an empty array in the query under
|
@@ -218,8 +214,6 @@ module ElasticGraph
|
|
218
214
|
end
|
219
215
|
|
220
216
|
def process_all_of_expression(bool_node, expressions, field_path)
|
221
|
-
return if expressions.nil? || expressions == {}
|
222
|
-
|
223
217
|
# `all_of` represents an AND. AND is the default way that `process_filter_hash` combines
|
224
218
|
# filters so we just have to call it for each sub-expression.
|
225
219
|
expressions.each do |sub_expression|
|
@@ -228,8 +222,6 @@ module ElasticGraph
|
|
228
222
|
end
|
229
223
|
|
230
224
|
def process_operator_expression(bool_node, operator, expression, field_path)
|
231
|
-
return if expression.nil? || expression == {}
|
232
|
-
|
233
225
|
# `operator` is a filtering operator, and `expression` is the value the filtering
|
234
226
|
# operator should be applied to. The `op_applicator` lambda, when called, will
|
235
227
|
# return a Clause instance (defined in this module).
|
@@ -317,8 +309,6 @@ module ElasticGraph
|
|
317
309
|
end
|
318
310
|
|
319
311
|
def process_list_count_expression(bool_node, expression, field_path)
|
320
|
-
return if expression.nil? || expression == {}
|
321
|
-
|
322
312
|
# Normally, we don't have to do anything special for list count expressions.
|
323
313
|
# That's the case, for example, for an expression like:
|
324
314
|
#
|
@@ -25,33 +25,30 @@ module ElasticGraph
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def identify_node_type(field_or_op, sub_expression)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def filter_operators
|
32
|
-
@filter_operators ||= build_filter_operators(runtime_metadata)
|
33
|
-
end
|
28
|
+
# `:not` must go before `:empty`, because `not: empty_filter` must be inverted from just `empty_filter`.
|
29
|
+
return :not if field_or_op == schema_names.not
|
34
30
|
|
35
|
-
|
31
|
+
# The `:empty` check can go before all other checks; besides `not`, none of the other operators require special
|
32
|
+
# handling when the filter is empty, and we want to detect this as early as possible.
|
33
|
+
# Note: `any_of: [empty_filter]` does have special handling, but `any_of: empty_filter` does not.
|
34
|
+
return :empty if sub_expression.nil? || sub_expression == {}
|
36
35
|
|
37
|
-
def identify_by_field_or_op(field_or_op)
|
38
|
-
return :not if field_or_op == schema_names.not
|
39
36
|
return :list_any_filter if field_or_op == schema_names.any_satisfy
|
40
37
|
return :all_of if field_or_op == schema_names.all_of
|
41
38
|
return :any_of if field_or_op == schema_names.any_of
|
42
39
|
return :operator if filter_operators.key?(field_or_op)
|
43
40
|
return :list_count if field_or_op == LIST_COUNTS_FIELD
|
41
|
+
return :sub_field if sub_expression.is_a?(::Hash)
|
44
42
|
|
45
|
-
|
43
|
+
:unknown
|
46
44
|
end
|
47
45
|
|
48
|
-
def
|
49
|
-
|
50
|
-
return :sub_field if sub_expression.is_a?(::Hash)
|
51
|
-
|
52
|
-
nil
|
46
|
+
def filter_operators
|
47
|
+
@filter_operators ||= build_filter_operators(runtime_metadata)
|
53
48
|
end
|
54
49
|
|
50
|
+
private
|
51
|
+
|
55
52
|
def build_filter_operators(runtime_metadata)
|
56
53
|
filter_by_time_of_day_script_id = runtime_metadata
|
57
54
|
.static_script_ids_by_scoped_name
|
@@ -12,7 +12,8 @@ module ElasticGraph
|
|
12
12
|
# Responsible for extracting a set of values from query filters, based on a using a custom
|
13
13
|
# set type that is able to efficiently model the "all values" case.
|
14
14
|
class FilterValueSetExtractor
|
15
|
-
def initialize(schema_names, all_values_set, empty_set, &build_set_for_filter)
|
15
|
+
def initialize(filter_node_interpreter, schema_names, all_values_set, empty_set, &build_set_for_filter)
|
16
|
+
@filter_node_interpreter = filter_node_interpreter
|
16
17
|
@schema_names = schema_names
|
17
18
|
@all_values_set = all_values_set
|
18
19
|
@empty_set = empty_set
|
@@ -41,9 +42,12 @@ module ElasticGraph
|
|
41
42
|
# routing values causes no adverse behavior (although it may introduce an inefficiency)
|
42
43
|
# but if we fail to route to a shard that contains a matching document, the search results
|
43
44
|
# will be incorrect.
|
44
|
-
map_reduce_sets(target_field_paths, :union, negate: false) do |target_field_path|
|
45
|
+
value_set = map_reduce_sets(target_field_paths, :union, negate: false) do |target_field_path|
|
45
46
|
filter_value_set_for_target_field_path(target_field_path, filter_hashes)
|
46
47
|
end
|
48
|
+
|
49
|
+
return nil if (_ = value_set) == UnboundedSetWithExclusions
|
50
|
+
_ = value_set
|
47
51
|
end
|
48
52
|
|
49
53
|
private
|
@@ -83,24 +87,32 @@ module ElasticGraph
|
|
83
87
|
# the key could identify either a field we are filtering on or a filtering operator to apply
|
84
88
|
# to a particular field.
|
85
89
|
def filter_value_set_for_filter_hash_entry(field_or_op, filter_value, target_field_path_parts, traversed_field_path_parts, negate:)
|
86
|
-
|
87
|
-
|
90
|
+
node_type = @filter_node_interpreter.identify_node_type(field_or_op, filter_value)
|
91
|
+
case node_type
|
92
|
+
when :empty
|
93
|
+
# Any empty filter is effectively treated as `true` by our filtering logic, so we need
|
88
94
|
# to return our `@all_values_set` to indicate this filter matches all documents.
|
89
95
|
@all_values_set
|
90
|
-
|
91
|
-
filter_value_set_for_filter_hash(filter_value, target_field_path_parts, traversed_field_path_parts, negate: !negate)
|
92
|
-
|
93
|
-
# the only time `value` is a hash is when `field_or_op` is a field name.
|
94
|
-
# In that case, `value` is a hash of filters that apply to that field.
|
96
|
+
when :not
|
97
|
+
filter_value_set_for_filter_hash(filter_value || {}, target_field_path_parts, traversed_field_path_parts, negate: !negate)
|
98
|
+
when :sub_field
|
95
99
|
filter_value_set_for_filter_hash(filter_value, target_field_path_parts, traversed_field_path_parts + [field_or_op], negate: negate)
|
96
|
-
|
100
|
+
when :any_of
|
97
101
|
filter_value_set_for_any_of(filter_value, target_field_path_parts, traversed_field_path_parts, negate: negate)
|
98
|
-
|
102
|
+
when :operator
|
103
|
+
# Check to make sure the operator applies to the target field. If not, we have no information
|
104
|
+
# in this clause. The set is unbounded, and may have exclusions.
|
105
|
+
return UnboundedSetWithExclusions unless target_field_path_parts == traversed_field_path_parts
|
106
|
+
|
99
107
|
set = filter_value_set_for_field_filter(field_or_op, filter_value)
|
100
108
|
negate ? set.negate : set
|
109
|
+
when :all_of, :list_any_filter, :list_count, :unknown
|
110
|
+
# We have no information in this clause. The set is unbounded, and may have exclusions.
|
111
|
+
UnboundedSetWithExclusions
|
101
112
|
else
|
102
|
-
#
|
103
|
-
|
113
|
+
# :nocov: -- not possible to cover without mocking `@filter_node_interpreter` to return a node type outside the allowed values.
|
114
|
+
raise "`FilterValueSetExtractor` must be updated to handle `:#{node_type}` nodes."
|
115
|
+
# :nocov:
|
104
116
|
end
|
105
117
|
end
|
106
118
|
|
@@ -125,7 +137,7 @@ module ElasticGraph
|
|
125
137
|
# Determines the set of filter values for a single filter on a single field.
|
126
138
|
def filter_value_set_for_field_filter(filter_op, filter_value)
|
127
139
|
operator_name = @schema_names.canonical_name_for(filter_op)
|
128
|
-
@build_set_for_filter.call(operator_name, filter_value) ||
|
140
|
+
@build_set_for_filter.call(operator_name, filter_value) || UnboundedSetWithExclusions
|
129
141
|
end
|
130
142
|
|
131
143
|
# Maps over the provided `collection` by applying the given `map_transform`
|
@@ -144,10 +156,49 @@ module ElasticGraph
|
|
144
156
|
# of each set is the difference between @all_values_set and the given set)--and vice versa.
|
145
157
|
reduction = REDUCTION_INVERSIONS.fetch(reduction) if negate
|
146
158
|
|
147
|
-
collection.map(&map_transform).reduce
|
159
|
+
collection.map(&map_transform).reduce do |s1, s2|
|
160
|
+
receiver, argument = ((_ = s2) == UnboundedSetWithExclusions) ? [s2, s1] : [s1, s2]
|
161
|
+
(reduction == :union) ? receiver.union(argument) : receiver.intersection(argument)
|
162
|
+
end
|
148
163
|
end
|
149
164
|
|
150
165
|
REDUCTION_INVERSIONS = {union: :intersection, intersection: :union}
|
166
|
+
|
167
|
+
# This minimal set implementation is used for otherwise unrepresentable cases. We use it when
|
168
|
+
# a filter on a `target_field_path` uses an inequality like:
|
169
|
+
#
|
170
|
+
# {field: {gt: "abc"}}
|
171
|
+
#
|
172
|
+
# In a case like that, the set is unbounded (there's an infinite number of values that are greater
|
173
|
+
# than `"abc"`...), but it's not `@all_values_set`--since it's based on an inequality, there are
|
174
|
+
# _some_ values that are excluded from the set. We also can't represent this case with an
|
175
|
+
# `all_values_except(...)` set implementation because the set of exclusions is also unbounded!
|
176
|
+
#
|
177
|
+
# When our filter value extraction results in this set, we cannot limit what shards or indices we must hit based
|
178
|
+
# on the filters.
|
179
|
+
module UnboundedSetWithExclusions
|
180
|
+
def self.intersection(other)
|
181
|
+
# Technically, the accurate intersection would be `other - values_of(self)` but as we don't have
|
182
|
+
# any known values from this unbounded set, we just return `other`. It's OK to include extra values
|
183
|
+
# in the set (we'll search additional shards or indices) but not OK to fail to include necessary values
|
184
|
+
# in the set (we'd avoid searching a shard that may have matching documents) so we err on the side of
|
185
|
+
# including more values.
|
186
|
+
other
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.union(other)
|
190
|
+
# Since our set here is unbounded, the resulting union is also unbounded.
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.negate
|
195
|
+
# The negation of an `UnboundedSetWithExclusions` is still an `UnboundedSetWithExclusions`. While it would flip
|
196
|
+
# which values are in or out of the set, this object is still the representation in our data model for that case.
|
197
|
+
self
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
private_constant :UnboundedSetWithExclusions
|
151
202
|
end
|
152
203
|
end
|
153
204
|
end
|
@@ -96,12 +96,18 @@ module ElasticGraph
|
|
96
96
|
# on `filter_value_set_extractor` to determine it, since it understands the semantics
|
97
97
|
# of `any_of`, `not`, etc.
|
98
98
|
def can_match_nil_values_at?(path, filter)
|
99
|
-
filter_value_set_extractor.extract_filter_value_set([filter], [path])
|
99
|
+
value_set = filter_value_set_extractor.extract_filter_value_set([filter], [path])
|
100
|
+
(_ = value_set).nil? || value_set.includes_nil?
|
100
101
|
end
|
101
102
|
|
102
103
|
def filter_value_set_extractor
|
103
104
|
@filter_value_set_extractor ||=
|
104
|
-
Filtering::FilterValueSetExtractor.new(
|
105
|
+
Filtering::FilterValueSetExtractor.new(
|
106
|
+
filter_node_interpreter,
|
107
|
+
schema_element_names,
|
108
|
+
IncludesNilSet,
|
109
|
+
ExcludesNilSet
|
110
|
+
) do |operator, filter_value|
|
105
111
|
if operator == :equal_to_any_of && filter_value.include?(nil)
|
106
112
|
IncludesNilSet
|
107
113
|
else
|
@@ -25,7 +25,7 @@ module ElasticGraph
|
|
25
25
|
def fetch(queries)
|
26
26
|
responses_by_query = @datastore_router.msearch(queries, query_tracker: @query_tracker)
|
27
27
|
@query_tracker.record_datastore_queries_for_single_request(queries)
|
28
|
-
queries.map { |q| responses_by_query
|
28
|
+
queries.map { |q| responses_by_query.fetch(q) }
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.execute_many(queries, for_context:)
|
@@ -129,9 +129,10 @@ module ElasticGraph
|
|
129
129
|
@datastore_query_builder ||= begin
|
130
130
|
require "elastic_graph/graphql/datastore_query"
|
131
131
|
DatastoreQuery::Builder.with(
|
132
|
-
filter_interpreter
|
133
|
-
|
134
|
-
|
132
|
+
filter_interpreter:,
|
133
|
+
filter_node_interpreter:,
|
134
|
+
runtime_metadata:,
|
135
|
+
logger:,
|
135
136
|
default_page_size: @config.default_page_size,
|
136
137
|
max_page_size: @config.max_page_size
|
137
138
|
)
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elasticgraph-graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.19.0.0
|
4
|
+
version: 0.19.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Myron Marston
|
8
8
|
- Ben VandenBos
|
9
9
|
- Block Engineering
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-12-
|
13
|
+
date: 2024-12-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rubocop-factory_bot
|
@@ -74,14 +74,14 @@ dependencies:
|
|
74
74
|
requirements:
|
75
75
|
- - "~>"
|
76
76
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
77
|
+
version: 1.9.0
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
82
|
- - "~>"
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version:
|
84
|
+
version: 1.9.0
|
85
85
|
- !ruby/object:Gem::Dependency
|
86
86
|
name: coderay
|
87
87
|
requirement: !ruby/object:Gem::Requirement
|
@@ -268,113 +268,113 @@ dependencies:
|
|
268
268
|
requirements:
|
269
269
|
- - '='
|
270
270
|
- !ruby/object:Gem::Version
|
271
|
-
version: 0.19.0.0
|
271
|
+
version: 0.19.0.0
|
272
272
|
type: :runtime
|
273
273
|
prerelease: false
|
274
274
|
version_requirements: !ruby/object:Gem::Requirement
|
275
275
|
requirements:
|
276
276
|
- - '='
|
277
277
|
- !ruby/object:Gem::Version
|
278
|
-
version: 0.19.0.0
|
278
|
+
version: 0.19.0.0
|
279
279
|
- !ruby/object:Gem::Dependency
|
280
280
|
name: elasticgraph-schema_artifacts
|
281
281
|
requirement: !ruby/object:Gem::Requirement
|
282
282
|
requirements:
|
283
283
|
- - '='
|
284
284
|
- !ruby/object:Gem::Version
|
285
|
-
version: 0.19.0.0
|
285
|
+
version: 0.19.0.0
|
286
286
|
type: :runtime
|
287
287
|
prerelease: false
|
288
288
|
version_requirements: !ruby/object:Gem::Requirement
|
289
289
|
requirements:
|
290
290
|
- - '='
|
291
291
|
- !ruby/object:Gem::Version
|
292
|
-
version: 0.19.0.0
|
292
|
+
version: 0.19.0.0
|
293
293
|
- !ruby/object:Gem::Dependency
|
294
294
|
name: graphql
|
295
295
|
requirement: !ruby/object:Gem::Requirement
|
296
296
|
requirements:
|
297
297
|
- - "~>"
|
298
298
|
- !ruby/object:Gem::Version
|
299
|
-
version: 2.4.
|
299
|
+
version: 2.4.8
|
300
300
|
type: :runtime
|
301
301
|
prerelease: false
|
302
302
|
version_requirements: !ruby/object:Gem::Requirement
|
303
303
|
requirements:
|
304
304
|
- - "~>"
|
305
305
|
- !ruby/object:Gem::Version
|
306
|
-
version: 2.4.
|
306
|
+
version: 2.4.8
|
307
307
|
- !ruby/object:Gem::Dependency
|
308
308
|
name: elasticgraph-admin
|
309
309
|
requirement: !ruby/object:Gem::Requirement
|
310
310
|
requirements:
|
311
311
|
- - '='
|
312
312
|
- !ruby/object:Gem::Version
|
313
|
-
version: 0.19.0.0
|
313
|
+
version: 0.19.0.0
|
314
314
|
type: :development
|
315
315
|
prerelease: false
|
316
316
|
version_requirements: !ruby/object:Gem::Requirement
|
317
317
|
requirements:
|
318
318
|
- - '='
|
319
319
|
- !ruby/object:Gem::Version
|
320
|
-
version: 0.19.0.0
|
320
|
+
version: 0.19.0.0
|
321
321
|
- !ruby/object:Gem::Dependency
|
322
322
|
name: elasticgraph-elasticsearch
|
323
323
|
requirement: !ruby/object:Gem::Requirement
|
324
324
|
requirements:
|
325
325
|
- - '='
|
326
326
|
- !ruby/object:Gem::Version
|
327
|
-
version: 0.19.0.0
|
327
|
+
version: 0.19.0.0
|
328
328
|
type: :development
|
329
329
|
prerelease: false
|
330
330
|
version_requirements: !ruby/object:Gem::Requirement
|
331
331
|
requirements:
|
332
332
|
- - '='
|
333
333
|
- !ruby/object:Gem::Version
|
334
|
-
version: 0.19.0.0
|
334
|
+
version: 0.19.0.0
|
335
335
|
- !ruby/object:Gem::Dependency
|
336
336
|
name: elasticgraph-opensearch
|
337
337
|
requirement: !ruby/object:Gem::Requirement
|
338
338
|
requirements:
|
339
339
|
- - '='
|
340
340
|
- !ruby/object:Gem::Version
|
341
|
-
version: 0.19.0.0
|
341
|
+
version: 0.19.0.0
|
342
342
|
type: :development
|
343
343
|
prerelease: false
|
344
344
|
version_requirements: !ruby/object:Gem::Requirement
|
345
345
|
requirements:
|
346
346
|
- - '='
|
347
347
|
- !ruby/object:Gem::Version
|
348
|
-
version: 0.19.0.0
|
348
|
+
version: 0.19.0.0
|
349
349
|
- !ruby/object:Gem::Dependency
|
350
350
|
name: elasticgraph-indexer
|
351
351
|
requirement: !ruby/object:Gem::Requirement
|
352
352
|
requirements:
|
353
353
|
- - '='
|
354
354
|
- !ruby/object:Gem::Version
|
355
|
-
version: 0.19.0.0
|
355
|
+
version: 0.19.0.0
|
356
356
|
type: :development
|
357
357
|
prerelease: false
|
358
358
|
version_requirements: !ruby/object:Gem::Requirement
|
359
359
|
requirements:
|
360
360
|
- - '='
|
361
361
|
- !ruby/object:Gem::Version
|
362
|
-
version: 0.19.0.0
|
362
|
+
version: 0.19.0.0
|
363
363
|
- !ruby/object:Gem::Dependency
|
364
364
|
name: elasticgraph-schema_definition
|
365
365
|
requirement: !ruby/object:Gem::Requirement
|
366
366
|
requirements:
|
367
367
|
- - '='
|
368
368
|
- !ruby/object:Gem::Version
|
369
|
-
version: 0.19.0.0
|
369
|
+
version: 0.19.0.0
|
370
370
|
type: :development
|
371
371
|
prerelease: false
|
372
372
|
version_requirements: !ruby/object:Gem::Requirement
|
373
373
|
requirements:
|
374
374
|
- - '='
|
375
375
|
- !ruby/object:Gem::Version
|
376
|
-
version: 0.19.0.0
|
377
|
-
description:
|
376
|
+
version: 0.19.0.0
|
377
|
+
description:
|
378
378
|
email:
|
379
379
|
- myron@squareup.com
|
380
380
|
executables: []
|
@@ -466,12 +466,12 @@ licenses:
|
|
466
466
|
- MIT
|
467
467
|
metadata:
|
468
468
|
bug_tracker_uri: https://github.com/block/elasticgraph/issues
|
469
|
-
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v0.19.0.0
|
469
|
+
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v0.19.0.0
|
470
470
|
documentation_uri: https://block.github.io/elasticgraph/docs/main/
|
471
471
|
homepage_uri: https://block.github.io/elasticgraph/
|
472
|
-
source_code_uri: https://github.com/block/elasticgraph/tree/v0.19.0.0
|
472
|
+
source_code_uri: https://github.com/block/elasticgraph/tree/v0.19.0.0/elasticgraph-graphql
|
473
473
|
gem_category: core
|
474
|
-
post_install_message:
|
474
|
+
post_install_message:
|
475
475
|
rdoc_options: []
|
476
476
|
require_paths:
|
477
477
|
- lib
|
@@ -486,8 +486,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
486
486
|
- !ruby/object:Gem::Version
|
487
487
|
version: '0'
|
488
488
|
requirements: []
|
489
|
-
rubygems_version: 3.5.
|
490
|
-
signing_key:
|
489
|
+
rubygems_version: 3.5.22
|
490
|
+
signing_key:
|
491
491
|
specification_version: 4
|
492
492
|
summary: The ElasticGraph GraphQL query engine.
|
493
493
|
test_files: []
|