elasticgraph-graphql 0.18.0.3 → 0.18.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a520631b7964685b34d5089eb26772a9754e955ac53ce7706d9a844a06c281c3
4
- data.tar.gz: 02e9144c3197c43a208c100fcc3a340301caf041fd12783b4cd08f37d3f28645
3
+ metadata.gz: b0cb73d5ec2940bb792cbcada0c615dd1415de774bb9111ffe09ae293cf2de38
4
+ data.tar.gz: fb0f23546ee15fd3c9f434518dde7ccb79df0838795392d6b0a8c9e60df01667
5
5
  SHA512:
6
- metadata.gz: 9540df01b507f1b3bcdae32699c3cf830b900507953f70504515afd0c5c7bf7160fb13b78750a272da823e81795d247c56c47ba7bff0d977193dad744ed7e6ba
7
- data.tar.gz: 68e4cee0d55d42693641901bafc0e811a7f596b831d438ce9882ae259c0006ac12580726785c9fa2918e1abb0cb5b804da088f7bef4f83a4220958b07b73fbec
6
+ metadata.gz: 4c21c60909ba34c7034b2c5506288f54d4e29aa715e9741e1f88a046b54d6a694082b657c00aa495da7d28eb771bec28c2a23d0c10b3c5dbfa46e20088c9802f
7
+ data.tar.gz: ecbd8f84362640638e36384875d43c8a9443a413bb407dd2fca5e1d18792bfe618ac5b15c14a9d0c7d2656b7234620d73ce95078bc18ed4cc72eecbec4958ad3
@@ -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/range_query"
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
- # Contains all query logic related to filtering. Not tested directly; tests drive the `Query` interface instead.
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(:runtime_metadata, :schema_names, :logger) do
23
+ FilterInterpreter = Support::MemoizableData.define(:filter_node_interpreter, :schema_names, :logger) do
27
24
  # @implements FilterInterpreter
28
25
 
29
- def initialize(runtime_metadata:, logger:)
26
+ def initialize(filter_node_interpreter:, logger:)
30
27
  super(
31
- runtime_metadata: runtime_metadata,
32
- schema_names: runtime_metadata.schema_element_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 `runtime_metadata` and `logger` can be quite large and noisy. We generally don't care about
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} runtime_metadata=(hash: #{runtime_metadata.hash}) logger=(hash: #{logger.hash})>"
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 identify_expression_type(field_or_op, expression)
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 identify_expression_type(field_or_op, sub_expression)
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
@@ -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(filter, index_fields_by_path, parent_path: "")
73
- filter.compact.flat_map do |field_name, value|
74
- path = parent_path + field_name
75
-
76
- if (index_field = index_fields_by_path[path])
77
- # We've recursed down to a field path. We want that path to be returned if the
78
- # field is sourced from SELF_RELATIONSHIP_NAME.
79
- (index_field.source == SELF_RELATIONSHIP_NAME) ? [path] : []
80
- elsif field_name == schema_element_names.any_of
81
- # `any_of` represents an OR and the value will be an array, so we have to flat map over it.
82
- value.flat_map do |sub_filter|
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
- # ...otherwise, `field_name` is a parent field and we need to recurse down through the children.
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
@@ -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(schema_element_names: schema_element_names, filter_args_translator: filter_args_translator),
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(runtime_metadata: runtime_metadata, logger: logger)
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,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticgraph-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0.3
4
+ version: 0.18.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Myron Marston
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-05 00:00:00.000000000 Z
11
+ date: 2024-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop-factory_bot
@@ -266,28 +266,28 @@ dependencies:
266
266
  requirements:
267
267
  - - '='
268
268
  - !ruby/object:Gem::Version
269
- version: 0.18.0.3
269
+ version: 0.18.0.4
270
270
  type: :runtime
271
271
  prerelease: false
272
272
  version_requirements: !ruby/object:Gem::Requirement
273
273
  requirements:
274
274
  - - '='
275
275
  - !ruby/object:Gem::Version
276
- version: 0.18.0.3
276
+ version: 0.18.0.4
277
277
  - !ruby/object:Gem::Dependency
278
278
  name: elasticgraph-schema_artifacts
279
279
  requirement: !ruby/object:Gem::Requirement
280
280
  requirements:
281
281
  - - '='
282
282
  - !ruby/object:Gem::Version
283
- version: 0.18.0.3
283
+ version: 0.18.0.4
284
284
  type: :runtime
285
285
  prerelease: false
286
286
  version_requirements: !ruby/object:Gem::Requirement
287
287
  requirements:
288
288
  - - '='
289
289
  - !ruby/object:Gem::Version
290
- version: 0.18.0.3
290
+ version: 0.18.0.4
291
291
  - !ruby/object:Gem::Dependency
292
292
  name: graphql
293
293
  requirement: !ruby/object:Gem::Requirement
@@ -308,70 +308,70 @@ dependencies:
308
308
  requirements:
309
309
  - - '='
310
310
  - !ruby/object:Gem::Version
311
- version: 0.18.0.3
311
+ version: 0.18.0.4
312
312
  type: :development
313
313
  prerelease: false
314
314
  version_requirements: !ruby/object:Gem::Requirement
315
315
  requirements:
316
316
  - - '='
317
317
  - !ruby/object:Gem::Version
318
- version: 0.18.0.3
318
+ version: 0.18.0.4
319
319
  - !ruby/object:Gem::Dependency
320
320
  name: elasticgraph-elasticsearch
321
321
  requirement: !ruby/object:Gem::Requirement
322
322
  requirements:
323
323
  - - '='
324
324
  - !ruby/object:Gem::Version
325
- version: 0.18.0.3
325
+ version: 0.18.0.4
326
326
  type: :development
327
327
  prerelease: false
328
328
  version_requirements: !ruby/object:Gem::Requirement
329
329
  requirements:
330
330
  - - '='
331
331
  - !ruby/object:Gem::Version
332
- version: 0.18.0.3
332
+ version: 0.18.0.4
333
333
  - !ruby/object:Gem::Dependency
334
334
  name: elasticgraph-opensearch
335
335
  requirement: !ruby/object:Gem::Requirement
336
336
  requirements:
337
337
  - - '='
338
338
  - !ruby/object:Gem::Version
339
- version: 0.18.0.3
339
+ version: 0.18.0.4
340
340
  type: :development
341
341
  prerelease: false
342
342
  version_requirements: !ruby/object:Gem::Requirement
343
343
  requirements:
344
344
  - - '='
345
345
  - !ruby/object:Gem::Version
346
- version: 0.18.0.3
346
+ version: 0.18.0.4
347
347
  - !ruby/object:Gem::Dependency
348
348
  name: elasticgraph-indexer
349
349
  requirement: !ruby/object:Gem::Requirement
350
350
  requirements:
351
351
  - - '='
352
352
  - !ruby/object:Gem::Version
353
- version: 0.18.0.3
353
+ version: 0.18.0.4
354
354
  type: :development
355
355
  prerelease: false
356
356
  version_requirements: !ruby/object:Gem::Requirement
357
357
  requirements:
358
358
  - - '='
359
359
  - !ruby/object:Gem::Version
360
- version: 0.18.0.3
360
+ version: 0.18.0.4
361
361
  - !ruby/object:Gem::Dependency
362
362
  name: elasticgraph-schema_definition
363
363
  requirement: !ruby/object:Gem::Requirement
364
364
  requirements:
365
365
  - - '='
366
366
  - !ruby/object:Gem::Version
367
- version: 0.18.0.3
367
+ version: 0.18.0.4
368
368
  type: :development
369
369
  prerelease: false
370
370
  version_requirements: !ruby/object:Gem::Requirement
371
371
  requirements:
372
372
  - - '='
373
373
  - !ruby/object:Gem::Version
374
- version: 0.18.0.3
374
+ version: 0.18.0.4
375
375
  description:
376
376
  email:
377
377
  - myron@squareup.com
@@ -418,6 +418,7 @@ files:
418
418
  - lib/elastic_graph/graphql/filtering/field_path.rb
419
419
  - lib/elastic_graph/graphql/filtering/filter_args_translator.rb
420
420
  - lib/elastic_graph/graphql/filtering/filter_interpreter.rb
421
+ - lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb
421
422
  - lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb
422
423
  - lib/elastic_graph/graphql/filtering/range_query.rb
423
424
  - lib/elastic_graph/graphql/http_endpoint.rb