elasticgraph-graphql 0.19.1.1 → 0.19.2.0

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/elastic_graph/graphql/aggregation/composite_grouping_adapter.rb +1 -1
  4. data/lib/elastic_graph/graphql/aggregation/computation.rb +1 -1
  5. data/lib/elastic_graph/graphql/aggregation/date_histogram_grouping.rb +1 -1
  6. data/lib/elastic_graph/graphql/aggregation/field_path_encoder.rb +1 -1
  7. data/lib/elastic_graph/graphql/aggregation/field_term_grouping.rb +1 -1
  8. data/lib/elastic_graph/graphql/aggregation/key.rb +1 -1
  9. data/lib/elastic_graph/graphql/aggregation/nested_sub_aggregation.rb +1 -1
  10. data/lib/elastic_graph/graphql/aggregation/non_composite_grouping_adapter.rb +2 -2
  11. data/lib/elastic_graph/graphql/aggregation/path_segment.rb +2 -2
  12. data/lib/elastic_graph/graphql/aggregation/query.rb +1 -1
  13. data/lib/elastic_graph/graphql/aggregation/query_adapter.rb +33 -6
  14. data/lib/elastic_graph/graphql/aggregation/query_optimizer.rb +1 -1
  15. data/lib/elastic_graph/graphql/aggregation/resolvers/aggregated_values.rb +2 -6
  16. data/lib/elastic_graph/graphql/aggregation/resolvers/count_detail.rb +1 -1
  17. data/lib/elastic_graph/graphql/aggregation/resolvers/grouped_by.rb +26 -6
  18. data/lib/elastic_graph/graphql/aggregation/resolvers/node.rb +1 -1
  19. data/lib/elastic_graph/graphql/aggregation/resolvers/relay_connection_builder.rb +5 -6
  20. data/lib/elastic_graph/graphql/aggregation/resolvers/sub_aggregations.rb +10 -8
  21. data/lib/elastic_graph/graphql/aggregation/script_term_grouping.rb +1 -1
  22. data/lib/elastic_graph/graphql/aggregation/term_grouping.rb +2 -2
  23. data/lib/elastic_graph/graphql/client.rb +1 -1
  24. data/lib/elastic_graph/graphql/config.rb +21 -6
  25. data/lib/elastic_graph/graphql/datastore_query/document_paginator.rb +10 -5
  26. data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +2 -3
  27. data/lib/elastic_graph/graphql/datastore_query/paginator.rb +1 -1
  28. data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +2 -3
  29. data/lib/elastic_graph/graphql/datastore_query.rb +66 -74
  30. data/lib/elastic_graph/graphql/datastore_response/document.rb +1 -1
  31. data/lib/elastic_graph/graphql/datastore_response/search_response.rb +83 -9
  32. data/lib/elastic_graph/graphql/datastore_search_router.rb +19 -4
  33. data/lib/elastic_graph/graphql/decoded_cursor.rb +1 -1
  34. data/lib/elastic_graph/graphql/filtering/boolean_query.rb +1 -1
  35. data/lib/elastic_graph/graphql/filtering/field_path.rb +1 -1
  36. data/lib/elastic_graph/graphql/filtering/filter_args_translator.rb +2 -2
  37. data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +10 -5
  38. data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +2 -2
  39. data/lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb +17 -2
  40. data/lib/elastic_graph/graphql/filtering/range_query.rb +1 -1
  41. data/lib/elastic_graph/graphql/http_endpoint.rb +2 -2
  42. data/lib/elastic_graph/graphql/monkey_patches/schema_field.rb +1 -1
  43. data/lib/elastic_graph/graphql/monkey_patches/schema_object.rb +1 -1
  44. data/lib/elastic_graph/graphql/query_adapter/filters.rb +1 -1
  45. data/lib/elastic_graph/graphql/query_adapter/pagination.rb +1 -1
  46. data/lib/elastic_graph/graphql/query_adapter/requested_fields.rb +18 -3
  47. data/lib/elastic_graph/graphql/query_adapter/sort.rb +1 -1
  48. data/lib/elastic_graph/graphql/query_details_tracker.rb +13 -4
  49. data/lib/elastic_graph/graphql/query_executor.rb +12 -5
  50. data/lib/elastic_graph/graphql/resolvers/get_record_field_value.rb +6 -12
  51. data/lib/elastic_graph/graphql/resolvers/graphql_adapter_builder.rb +123 -0
  52. data/lib/elastic_graph/graphql/resolvers/list_records.rb +4 -4
  53. data/lib/elastic_graph/graphql/resolvers/nested_relationships.rb +57 -27
  54. data/lib/elastic_graph/graphql/resolvers/nested_relationships_source.rb +324 -0
  55. data/lib/elastic_graph/graphql/resolvers/object.rb +36 -0
  56. data/lib/elastic_graph/graphql/resolvers/query_adapter.rb +2 -2
  57. data/lib/elastic_graph/graphql/resolvers/query_source.rb +6 -3
  58. data/lib/elastic_graph/graphql/resolvers/relay_connection/array_adapter.rb +1 -1
  59. data/lib/elastic_graph/graphql/resolvers/relay_connection/generic_adapter.rb +1 -1
  60. data/lib/elastic_graph/graphql/resolvers/relay_connection/page_info.rb +1 -1
  61. data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb +1 -1
  62. data/lib/elastic_graph/graphql/resolvers/relay_connection.rb +1 -1
  63. data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +2 -7
  64. data/lib/elastic_graph/graphql/scalar_coercion_adapters/cursor.rb +1 -1
  65. data/lib/elastic_graph/graphql/scalar_coercion_adapters/date.rb +1 -1
  66. data/lib/elastic_graph/graphql/scalar_coercion_adapters/date_time.rb +1 -1
  67. data/lib/elastic_graph/graphql/scalar_coercion_adapters/local_time.rb +1 -1
  68. data/lib/elastic_graph/graphql/scalar_coercion_adapters/longs.rb +1 -1
  69. data/lib/elastic_graph/graphql/scalar_coercion_adapters/no_op.rb +1 -1
  70. data/lib/elastic_graph/graphql/scalar_coercion_adapters/time_zone.rb +1 -1
  71. data/lib/elastic_graph/graphql/scalar_coercion_adapters/untyped.rb +1 -1
  72. data/lib/elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones.rb +1 -1
  73. data/lib/elastic_graph/graphql/schema/arguments.rb +1 -1
  74. data/lib/elastic_graph/graphql/schema/enum_value.rb +1 -1
  75. data/lib/elastic_graph/graphql/schema/field.rb +12 -27
  76. data/lib/elastic_graph/graphql/schema/relation_join.rb +17 -9
  77. data/lib/elastic_graph/graphql/schema/type.rb +15 -7
  78. data/lib/elastic_graph/graphql/schema.rb +11 -31
  79. data/lib/elastic_graph/graphql.rb +38 -40
  80. data/script/dump_time_zones +1 -1
  81. metadata +25 -27
  82. data/lib/elastic_graph/graphql/resolvers/graphql_adapter.rb +0 -114
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 901a1ac5d9fe88204d9f9b63d6db558c2cd8f7ef49f442343f1dadd7ec37aa24
4
- data.tar.gz: 3c4656b4061d9945fba22aabca8362ca9ad0299225d439a35424ba7598d2be5b
3
+ metadata.gz: c406c0dd4109d2b92b834f989f5819fc6342696b9c0b231f38b0bb4fc0d8f858
4
+ data.tar.gz: 95b206f183b6ccfc644ffab571e43232be92123d8c0998bd4add03b4d8571404
5
5
  SHA512:
6
- metadata.gz: 1a244b8226108ae8b5080630c01ae6fd1ff2e8647a9ac41a4e6da5f4f455c9e47d70cc04744468a21ece403ee4c2354996be56f435e4aa1e5b403f9367567409
7
- data.tar.gz: 62f158c296f883dfdb59c302efbc5ef087f933175b042ca49c76e47b71aa70e109a05170b357ec7a81844226586044b6e912882e2b5363adedc543a04d3906ee
6
+ metadata.gz: 5a1474ca90d5ec6a4c65996491e3570d3b4fef1bd40c9550b0b850bb663018dd9cd690e262af080212019ab12c05651aeca3f79f22ba1007a676422578e8787e
7
+ data.tar.gz: 8d7f2aff08c7ea4f6038943682f1231d4371c67003acf2f78321b749d4838550eef4b53df7a37b02ee134a10075e0f382251c06d9d18ce7f8bada9838579f201
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2024 Block, Inc.
3
+ Copyright (c) 2024 - 2025 Block, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -124,7 +124,7 @@ module ElasticGraph
124
124
  # a time breaker to ensure deterministic results, but don't particularly care which buckets
125
125
  # come first.
126
126
  [-b.fetch("doc_count"), b.fetch("key_values").map(&:to_s)]
127
- end.first(size)
127
+ end.first(size + 1) # We add 1 so `page_info.has_next_page` detection works.
128
128
  end
129
129
 
130
130
  def missing_bucket_path_from(buckets_path)
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -22,7 +22,7 @@ module ElasticGraph
22
22
 
23
23
  new(
24
24
  name_in_graphql_query: ast_node.alias || ast_node.name,
25
- name_in_index: field&.name_in_index&.to_s
25
+ name_in_index: field&.name_in_index
26
26
  )
27
27
  end
28
28
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -24,12 +24,39 @@ module ElasticGraph
24
24
  # Responsible for taking in the incoming GraphQL request context, arguments, and the GraphQL
25
25
  # schema and directives and populating the `aggregations` portion of `query`.
26
26
  class QueryAdapter < Support::MemoizableData.define(:schema, :config, :filter_args_translator, :runtime_metadata, :sub_aggregation_grouping_adapter)
27
+ # Partially applied `QueryAdapter` -- essentially the `QueryAdapter` without the schema,
28
+ # so that it can be instantiated before the `Schema` instance exists, instead providing it from
29
+ # `context` at query time.
30
+ class WithoutSchema
31
+ def initialize(config:, filter_args_translator:, runtime_metadata:, sub_aggregation_grouping_adapter:)
32
+ @build_adapter = ->(schema) do
33
+ QueryAdapter.new(
34
+ schema: schema,
35
+ config: config,
36
+ filter_args_translator: filter_args_translator,
37
+ runtime_metadata: runtime_metadata,
38
+ sub_aggregation_grouping_adapter: sub_aggregation_grouping_adapter
39
+ )
40
+ end
41
+ end
42
+
43
+ def call(query:, lookahead:, args:, field:, context:)
44
+ return query unless field.type.unwrap_fully.indexed_aggregation?
45
+
46
+ @build_adapter.call(context.fetch(:elastic_graph_schema)).call(
47
+ query: query,
48
+ lookahead: lookahead,
49
+ args: args,
50
+ field: field,
51
+ context: context
52
+ )
53
+ end
54
+ end
55
+
27
56
  # @dynamic element_names
28
57
  attr_reader :element_names
29
58
 
30
59
  def call(query:, lookahead:, args:, field:, context:)
31
- return query unless field.type.unwrap_fully.indexed_aggregation?
32
-
33
60
  aggregations_node = extract_aggregation_node(lookahead, field, context.query)
34
61
  return query unless aggregations_node
35
62
 
@@ -165,7 +192,7 @@ module ElasticGraph
165
192
 
166
193
  Aggregation::Computation.new(
167
194
  source_field_path: field_path,
168
- computed_index_field_name: computed_field.name_in_index.to_s,
195
+ computed_index_field_name: computed_field.name_in_index,
169
196
  detail: computation_detail
170
197
  )
171
198
  end
@@ -186,7 +213,7 @@ module ElasticGraph
186
213
  elsif !field.type.object?
187
214
  case field.type.name
188
215
  # Legacy date grouping API
189
- when :Date
216
+ when "Date"
190
217
  legacy_date_histogram_groupings_from(
191
218
  field_path: field_path,
192
219
  node: node,
@@ -194,7 +221,7 @@ module ElasticGraph
194
221
  get_offset: ->(args) { args[element_names.offset_days]&.then { |days| "#{days}d" } }
195
222
  )
196
223
  # Legacy datetime grouping API
197
- when :DateTime
224
+ when "DateTime"
198
225
  legacy_date_histogram_groupings_from(
199
226
  field_path: field_path,
200
227
  node: node,
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -15,17 +15,13 @@ module ElasticGraph
15
15
  module Aggregation
16
16
  module Resolvers
17
17
  class AggregatedValues < ::Data.define(:aggregation_name, :bucket, :field_path)
18
- def can_resolve?(field:, object:)
19
- true
20
- end
21
-
22
18
  def resolve(field:, object:, args:, context:, lookahead:)
23
19
  return with(field_path: field_path + [PathSegment.for(field: field, lookahead: lookahead)]) if field.type.object?
24
20
 
25
21
  key = Key::AggregatedValue.new(
26
22
  aggregation_name: aggregation_name,
27
23
  field_path: field_path.map(&:name_in_graphql_query),
28
- function_name: field.name_in_index.to_s
24
+ function_name: field.name_in_index
29
25
  )
30
26
 
31
27
  result = Support::HashUtil.verbose_fetch(bucket, key.encode)
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -14,16 +14,36 @@ module ElasticGraph
14
14
  module Aggregation
15
15
  module Resolvers
16
16
  class GroupedBy < ::Data.define(:bucket, :field_path)
17
- def can_resolve?(field:, object:)
18
- true
19
- end
20
-
21
17
  def resolve(field:, object:, args:, context:, lookahead:)
22
18
  new_field_path = field_path + [PathSegment.for(field: field, lookahead: lookahead)]
23
19
  return with(field_path: new_field_path) if field.type.object?
24
20
 
25
21
  bucket_entry = Support::HashUtil.verbose_fetch(bucket, "key")
26
- Support::HashUtil.verbose_fetch(bucket_entry, FieldPathEncoder.encode(new_field_path.map(&:name_in_graphql_query)))
22
+ value = Support::HashUtil.verbose_fetch(bucket_entry, FieldPathEncoder.encode(new_field_path.map(&:name_in_graphql_query)))
23
+
24
+ if field.type.unwrap_fully.name == "Boolean"
25
+ work_around_terms_aggregation_boolean_value(value)
26
+ else
27
+ value
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Elasticsearch/OpenSearch generally return `true`/`false` for Boolean fields. However, there's one exception to that[^1]:
34
+ #
35
+ # > Aggregations like the terms aggregation use 1 and 0 for the key, and the strings "true" and "false" for the key_as_string.
36
+ #
37
+ # Since we get 0/1 in _only_ this one case, we translate it back to false/true here. While a bit hacky, there isn't a widespread
38
+ # need to handle Booleans like this in other places. It would be nice to apply this logic in the `NonCompositeGroupingAdapter`
39
+ # (since the `composite` aggregation used by the `CompositeGroupingAdapter` does not suffer from this issue!) but we don't have
40
+ # ready access to the field type there to know that 0/1 mean false/true.
41
+ #
42
+ # [^1]: https://www.elastic.co/guide/en/elasticsearch/reference/8.17/boolean.html
43
+ def work_around_terms_aggregation_boolean_value(value)
44
+ return false if value == 0
45
+ return true if value == 1
46
+ value
27
47
  end
28
48
  end
29
49
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -55,17 +55,16 @@ module ElasticGraph
55
55
  end
56
56
 
57
57
  private_class_method def self.extract_buckets_from(search_response, for_query:)
58
- search_response.raw_data.dig(
59
- "aggregations",
58
+ search_response.aggregations.dig(
60
59
  for_query.name,
61
60
  "buckets"
62
- ) || [build_bucket(for_query, search_response.raw_data)]
61
+ ) || [build_bucket(for_query, search_response)]
63
62
  end
64
63
 
65
64
  private_class_method def self.build_bucket(query, response)
66
65
  defaults = {
67
66
  "key" => query.groupings.to_h { |g| [g.key, nil] },
68
- "doc_count" => response.dig("hits", "total", "value") || 0
67
+ "doc_count" => response.total_document_count(default: 0)
69
68
  }
70
69
 
71
70
  empty_bucket_computations = query.computations.to_h do |computation|
@@ -74,7 +73,7 @@ module ElasticGraph
74
73
 
75
74
  defaults
76
75
  .merge(empty_bucket_computations)
77
- .merge(response["aggregations"] || {})
76
+ .merge(response.aggregations)
78
77
  end
79
78
  end
80
79
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -20,10 +20,6 @@ module ElasticGraph
20
20
  module Aggregation
21
21
  module Resolvers
22
22
  class SubAggregations < ::Data.define(:schema_element_names, :sub_aggregations, :parent_queries, :sub_aggs_by_agg_key, :field_path)
23
- def can_resolve?(field:, object:)
24
- true
25
- end
26
-
27
23
  def resolve(field:, object:, args:, context:, lookahead:)
28
24
  path_segment = PathSegment.for(field: field, lookahead: lookahead)
29
25
  new_field_path = field_path + [path_segment]
@@ -69,15 +65,21 @@ module ElasticGraph
69
65
  sub_agg
70
66
  end
71
67
 
72
- # When we have a single ungrouped bucket, we never have any error on the `doc_count`.
73
- # Our resolver logic expects it to be present, though.
74
- [singleton_bucket.merge({"doc_count_error_upper_bound" => 0})]
68
+ [SINGLETON_BUCKET_DEFAULTS.merge(singleton_bucket)]
75
69
  end
76
70
  end
77
71
 
78
72
  BUCKET_ADAPTERS = [CompositeGroupingAdapter, NonCompositeGroupingAdapter].to_h do |adapter|
79
73
  [adapter.meta_name, adapter]
80
74
  end
75
+
76
+ SINGLETON_BUCKET_DEFAULTS = {
77
+ # When we have a single ungrouped bucket, we never have any error on the `doc_count`.
78
+ # Our resolver logic expects it to be present, though.
79
+ "doc_count_error_upper_bound" => 0,
80
+ # Our resolver logic expects a `key` as it gets used by the `PageInfo` resolver when that's requested.
81
+ "key" => {}
82
+ }
81
83
  end
82
84
  end
83
85
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -33,7 +33,7 @@ module ElasticGraph
33
33
  clause_value = work_around_elasticsearch_bug(terms_subclause)
34
34
  {
35
35
  "terms" => clause_value.merge({
36
- "size" => query.paginator.desired_page_size,
36
+ "size" => query.paginator.requested_page_size,
37
37
  "show_term_doc_count_error" => query.needs_doc_count_error
38
38
  })
39
39
  }
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -20,6 +20,12 @@ module ElasticGraph
20
20
  :max_page_size,
21
21
  # Queries that take longer than this configured threshold will have a sanitized version logged.
22
22
  :slow_query_latency_warning_threshold_in_ms,
23
+ # How to resolve nested relationships:
24
+ #
25
+ # - `optimized` (default): uses the new (in ElasticGraph 0.19.2.0) optimized resolver logic.
26
+ # - `original`: uses the resolver logic from ElasticGraph v0.19.1.1 and before.
27
+ # - `comparison`: runs both versions of the logic in serial, to compare them for correctness and performance. Results are logged.
28
+ :nested_relationship_resolver_mode,
23
29
  # Object used to identify the client of a GraphQL query based on the HTTP request.
24
30
  :client_resolver,
25
31
  # Array of modules that will be extended onto the `GraphQL` instance to support extension libraries.
@@ -38,17 +44,24 @@ module ElasticGraph
38
44
 
39
45
  extension_loader = SchemaArtifacts::RuntimeMetadata::ExtensionLoader.new(::Module.new)
40
46
  extension_mods = parsed_yaml.fetch("extension_modules", []).map do |mod_hash|
41
- extension_loader.load(mod_hash.fetch("extension_name"), from: mod_hash.fetch("require_path"), config: {}).extension_class.tap do |mod|
47
+ extension_loader.load(mod_hash.fetch("name"), from: mod_hash.fetch("require_path"), config: {}).extension_class.tap do |mod|
42
48
  unless mod.instance_of?(::Module)
43
- raise Errors::ConfigError, "`#{mod_hash.fetch("extension_name")}` is not a module, but all application extension modules must be modules."
49
+ raise Errors::ConfigError, "`#{mod_hash.fetch("name")}` is not a module, but all application extension modules must be modules."
44
50
  end
45
51
  end
46
52
  end
47
53
 
54
+ nested_relationship_resolver_mode = parsed_yaml["nested_relationship_resolver_mode"]&.to_sym || :optimized
55
+ unless VALID_NESTED_RELATIONSHIP_RESOLVER_MODES.include?(nested_relationship_resolver_mode)
56
+ raise Errors::ConfigError, "Invalid value for `nested_relationship_resolver_mode`: #{nested_relationship_resolver_mode}. " \
57
+ "Valid values: #{VALID_NESTED_RELATIONSHIP_RESOLVER_MODES.join(", ")}."
58
+ end
59
+
48
60
  new(
49
61
  default_page_size: parsed_yaml.fetch("default_page_size"),
50
62
  max_page_size: parsed_yaml.fetch("max_page_size"),
51
63
  slow_query_latency_warning_threshold_in_ms: parsed_yaml["slow_query_latency_warning_threshold_in_ms"] || 5000,
64
+ nested_relationship_resolver_mode: nested_relationship_resolver_mode,
52
65
  client_resolver: load_client_resolver(parsed_yaml),
53
66
  extension_modules: extension_mods,
54
67
  extension_settings: entire_parsed_yaml.except(*ELASTICGRAPH_CONFIG_KEYS)
@@ -61,6 +74,8 @@ module ElasticGraph
61
74
  # The standard ElasticGraph root config setting keys; anything else is assumed to be extension settings.
62
75
  ELASTICGRAPH_CONFIG_KEYS = %w[graphql indexer logger datastore schema_artifacts]
63
76
 
77
+ VALID_NESTED_RELATIONSHIP_RESOLVER_MODES = [:optimized, :original, :comparison]
78
+
64
79
  private_class_method def self.load_client_resolver(parsed_yaml)
65
80
  config = parsed_yaml.fetch("client_resolver") do
66
81
  return Client::DefaultResolver.new({})
@@ -68,13 +83,13 @@ module ElasticGraph
68
83
 
69
84
  client_resolver_loader = SchemaArtifacts::RuntimeMetadata::ExtensionLoader.new(Client::DefaultResolver)
70
85
  extension = client_resolver_loader.load(
71
- config.fetch("extension_name"),
86
+ config.fetch("name"),
72
87
  from: config.fetch("require_path"),
73
- config: config.except("extension_name", "require_path")
88
+ config: config.except("name", "require_path")
74
89
  )
75
90
  extension_class = extension.extension_class # : ::Class
76
91
 
77
- __skip__ = extension_class.new(extension.extension_config)
92
+ __skip__ = extension_class.new(extension.config)
78
93
  end
79
94
  end
80
95
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -22,7 +22,9 @@ module ElasticGraph
22
22
  :individual_docs_needed,
23
23
  # `total_document_count_needed`: when false, `track_total_hits` will be 0 in our datastore query.
24
24
  # This will prevent the datastore from doing extra work to get an accurate count
25
- :total_document_count_needed
25
+ :total_document_count_needed,
26
+ :size_multiplier,
27
+ :max_effective_size
26
28
  )
27
29
  # Builds a hash containing the portions of a datastore search body related to pagination.
28
30
  def to_datastore_body
@@ -57,12 +59,15 @@ module ElasticGraph
57
59
  end
58
60
  end
59
61
 
60
- private
61
-
62
62
  def effective_size
63
- individual_docs_needed ? paginator.requested_page_size : 0
63
+ @effective_size ||= begin
64
+ uncapped_size = (individual_docs_needed ? paginator.requested_page_size : 0) * size_multiplier
65
+ (uncapped_size > max_effective_size) ? max_effective_size : uncapped_size
66
+ end
64
67
  end
65
68
 
69
+ private
70
+
66
71
  def effective_sort
67
72
  return [] unless effective_size > 0
68
73
  paginator.search_in_reverse? ? reverse_sort : sort
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -142,8 +142,7 @@ module ElasticGraph
142
142
  private_constant :IndexExpressionBuilder
143
143
 
144
144
  # Steep is complaining that it can't find some `Query` but they are not in this file...
145
- # @dynamic aggregations, shard_routing_values, search_index_definitions, merge_with, search_index_expression
146
- # @dynamic with, to_datastore_msearch_header_and_body, document_paginator
145
+ # @dynamic shard_routing_values, effective_size, merge_with, search_index_expression, with, to_datastore_msearch_header_and_body
147
146
  end
148
147
  end
149
148
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Block, Inc.
1
+ # Copyright 2024 - 2025 Block, Inc.
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -168,8 +168,7 @@ module ElasticGraph
168
168
  private_constant :RoutingValueSet
169
169
 
170
170
  # Steep is complaining that it can't find some `Query` but they are not in this file...
171
- # @dynamic aggregations, shard_routing_values, search_index_definitions, merge_with, search_index_expression
172
- # @dynamic with, to_datastore_msearch_header_and_body, document_paginator
171
+ # @dynamic shard_routing_values, effective_size, merge_with, search_index_expression, with, to_datastore_msearch_header_and_body
173
172
  end
174
173
  end
175
174
  end