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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/lib/elastic_graph/graphql/aggregation/composite_grouping_adapter.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/computation.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/date_histogram_grouping.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/field_path_encoder.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/field_term_grouping.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/key.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/nested_sub_aggregation.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/non_composite_grouping_adapter.rb +2 -2
- data/lib/elastic_graph/graphql/aggregation/path_segment.rb +2 -2
- data/lib/elastic_graph/graphql/aggregation/query.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/query_adapter.rb +33 -6
- data/lib/elastic_graph/graphql/aggregation/query_optimizer.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/resolvers/aggregated_values.rb +2 -6
- data/lib/elastic_graph/graphql/aggregation/resolvers/count_detail.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/resolvers/grouped_by.rb +26 -6
- data/lib/elastic_graph/graphql/aggregation/resolvers/node.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/resolvers/relay_connection_builder.rb +5 -6
- data/lib/elastic_graph/graphql/aggregation/resolvers/sub_aggregations.rb +10 -8
- data/lib/elastic_graph/graphql/aggregation/script_term_grouping.rb +1 -1
- data/lib/elastic_graph/graphql/aggregation/term_grouping.rb +2 -2
- data/lib/elastic_graph/graphql/client.rb +1 -1
- data/lib/elastic_graph/graphql/config.rb +21 -6
- data/lib/elastic_graph/graphql/datastore_query/document_paginator.rb +10 -5
- data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +2 -3
- data/lib/elastic_graph/graphql/datastore_query/paginator.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +2 -3
- data/lib/elastic_graph/graphql/datastore_query.rb +66 -74
- data/lib/elastic_graph/graphql/datastore_response/document.rb +1 -1
- data/lib/elastic_graph/graphql/datastore_response/search_response.rb +83 -9
- data/lib/elastic_graph/graphql/datastore_search_router.rb +19 -4
- data/lib/elastic_graph/graphql/decoded_cursor.rb +1 -1
- data/lib/elastic_graph/graphql/filtering/boolean_query.rb +1 -1
- data/lib/elastic_graph/graphql/filtering/field_path.rb +1 -1
- data/lib/elastic_graph/graphql/filtering/filter_args_translator.rb +2 -2
- data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +10 -5
- data/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +2 -2
- data/lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb +17 -2
- data/lib/elastic_graph/graphql/filtering/range_query.rb +1 -1
- data/lib/elastic_graph/graphql/http_endpoint.rb +2 -2
- data/lib/elastic_graph/graphql/monkey_patches/schema_field.rb +1 -1
- data/lib/elastic_graph/graphql/monkey_patches/schema_object.rb +1 -1
- data/lib/elastic_graph/graphql/query_adapter/filters.rb +1 -1
- data/lib/elastic_graph/graphql/query_adapter/pagination.rb +1 -1
- data/lib/elastic_graph/graphql/query_adapter/requested_fields.rb +18 -3
- data/lib/elastic_graph/graphql/query_adapter/sort.rb +1 -1
- data/lib/elastic_graph/graphql/query_details_tracker.rb +13 -4
- data/lib/elastic_graph/graphql/query_executor.rb +12 -5
- data/lib/elastic_graph/graphql/resolvers/get_record_field_value.rb +6 -12
- data/lib/elastic_graph/graphql/resolvers/graphql_adapter_builder.rb +123 -0
- data/lib/elastic_graph/graphql/resolvers/list_records.rb +4 -4
- data/lib/elastic_graph/graphql/resolvers/nested_relationships.rb +57 -27
- data/lib/elastic_graph/graphql/resolvers/nested_relationships_source.rb +324 -0
- data/lib/elastic_graph/graphql/resolvers/object.rb +36 -0
- data/lib/elastic_graph/graphql/resolvers/query_adapter.rb +2 -2
- data/lib/elastic_graph/graphql/resolvers/query_source.rb +6 -3
- data/lib/elastic_graph/graphql/resolvers/relay_connection/array_adapter.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/relay_connection/generic_adapter.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/relay_connection/page_info.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/relay_connection.rb +1 -1
- data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +2 -7
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/cursor.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/date.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/date_time.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/local_time.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/longs.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/no_op.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/time_zone.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/untyped.rb +1 -1
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones.rb +1 -1
- data/lib/elastic_graph/graphql/schema/arguments.rb +1 -1
- data/lib/elastic_graph/graphql/schema/enum_value.rb +1 -1
- data/lib/elastic_graph/graphql/schema/field.rb +12 -27
- data/lib/elastic_graph/graphql/schema/relation_join.rb +17 -9
- data/lib/elastic_graph/graphql/schema/type.rb +15 -7
- data/lib/elastic_graph/graphql/schema.rb +11 -31
- data/lib/elastic_graph/graphql.rb +38 -40
- data/script/dump_time_zones +1 -1
- metadata +25 -27
- data/lib/elastic_graph/graphql/resolvers/graphql_adapter.rb +0 -114
@@ -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
|
@@ -18,12 +18,13 @@ module ElasticGraph
|
|
18
18
|
# @dynamic schema
|
19
19
|
attr_reader :schema
|
20
20
|
|
21
|
-
def initialize(schema:, monotonic_clock:, logger:, slow_query_threshold_ms:, datastore_search_router:)
|
21
|
+
def initialize(schema:, monotonic_clock:, logger:, slow_query_threshold_ms:, datastore_search_router:, config:)
|
22
22
|
@schema = schema
|
23
23
|
@monotonic_clock = monotonic_clock
|
24
24
|
@logger = logger
|
25
25
|
@slow_query_threshold_ms = slow_query_threshold_ms
|
26
26
|
@datastore_search_router = datastore_search_router
|
27
|
+
@config = config
|
27
28
|
end
|
28
29
|
|
29
30
|
# Executes the given `query_string` using the provided `variables`.
|
@@ -60,11 +61,13 @@ module ElasticGraph
|
|
60
61
|
operation_name: operation_name,
|
61
62
|
client: client,
|
62
63
|
context: context.merge({
|
64
|
+
logger: @logger,
|
63
65
|
monotonic_clock_deadline: timeout_in_ms&.+(start_time_in_ms),
|
64
66
|
elastic_graph_schema: @schema,
|
65
67
|
schema_element_names: @schema.element_names,
|
66
68
|
elastic_graph_query_tracker: query_tracker,
|
67
|
-
datastore_search_router: @datastore_search_router
|
69
|
+
datastore_search_router: @datastore_search_router,
|
70
|
+
nested_relationship_resolver_mode: @config.nested_relationship_resolver_mode
|
68
71
|
}.compact)
|
69
72
|
)
|
70
73
|
|
@@ -101,12 +104,16 @@ module ElasticGraph
|
|
101
104
|
"query_fingerprint" => fingerprint_for(query),
|
102
105
|
"query_name" => query.operation_name,
|
103
106
|
"duration_ms" => duration,
|
104
|
-
#
|
107
|
+
# How long the datastore queries took according to what the datastore itself reported.
|
105
108
|
"datastore_server_duration_ms" => query_tracker.datastore_query_server_duration_ms,
|
106
|
-
#
|
109
|
+
# An estimate for how much overhead ElasticGraph added on top of how long the datastore took.
|
107
110
|
# This is based on the duration, excluding how long the datastore calls took from the client side
|
108
111
|
# (e.g. accounting for network latency, serialization time, etc)
|
109
112
|
"elasticgraph_overhead_ms" => duration - query_tracker.datastore_query_client_duration_ms,
|
113
|
+
# An estimate for the time spent on transport (network latency, JSON serialization, etc).
|
114
|
+
"datastore_request_transport_duration_ms" => query_tracker.datastore_request_transport_duration_ms,
|
115
|
+
# How many datastore shards were queried, in total. This is a measure of how much load the query caused on the datastore.
|
116
|
+
"queried_shard_count" => query_tracker.queried_shard_count,
|
110
117
|
# According to https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#metric-filters-extract-json,
|
111
118
|
# > Value nodes can be strings or numbers...If a property selector points to an array or object, the metric filter won't match the log format.
|
112
119
|
# So, to allow flexibility to deal with cloud watch metric filters, we coerce these values to a string here.
|
@@ -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,16 +15,11 @@ module ElasticGraph
|
|
15
15
|
module Resolvers
|
16
16
|
# Responsible for fetching a single field value from a document.
|
17
17
|
class GetRecordFieldValue
|
18
|
-
def initialize(
|
19
|
-
@schema_element_names = schema_element_names
|
18
|
+
def initialize(elasticgraph_graphql:, config:)
|
19
|
+
@schema_element_names = elasticgraph_graphql.runtime_metadata.schema_element_names
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
object.is_a?(DatastoreResponse::Document) || object.is_a?(::Hash)
|
24
|
-
end
|
25
|
-
|
26
|
-
def resolve(field:, object:, args:, context:, lookahead:)
|
27
|
-
field_name = field.name_in_index.to_s
|
22
|
+
def resolve(field:, object:, args:, context:)
|
28
23
|
data =
|
29
24
|
case object
|
30
25
|
when DatastoreResponse::Document
|
@@ -33,9 +28,8 @@ module ElasticGraph
|
|
33
28
|
object
|
34
29
|
end
|
35
30
|
|
36
|
-
value = Support::HashUtil.fetch_value_at_path(data,
|
37
|
-
|
38
|
-
end
|
31
|
+
value = Support::HashUtil.fetch_value_at_path(data, field.path_in_index) { nil }
|
32
|
+
value = [] if value.nil? && field.type.list?
|
39
33
|
|
40
34
|
if field.type.relay_connection?
|
41
35
|
RelayConnection::ArrayAdapter.build(value, args, @schema_element_names, context)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Copyright 2024 - 2025 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/errors"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
class GraphQL
|
13
|
+
module Resolvers
|
14
|
+
# Provides an adapter to the GraphQL gem by building a resolver implementation hash as documented here:
|
15
|
+
#
|
16
|
+
# https://graphql-ruby.org/schema/sdl.html
|
17
|
+
class GraphQLAdapterBuilder
|
18
|
+
def initialize(runtime_metadata:, named_resolvers:, query_adapter:)
|
19
|
+
@runtime_metadata = runtime_metadata
|
20
|
+
@named_resolvers = named_resolvers
|
21
|
+
@query_adapter = query_adapter
|
22
|
+
end
|
23
|
+
|
24
|
+
def build
|
25
|
+
scalar_type_hash
|
26
|
+
.merge(object_type_hash)
|
27
|
+
.merge({"resolve_type" => _ = ->(supertype, obj, ctx) { resolve_type(supertype, obj, ctx) }})
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def scalar_type_hash
|
33
|
+
@runtime_metadata.scalar_types_by_name.transform_values do |scalar_type|
|
34
|
+
adapter = (_ = scalar_type.load_coercion_adapter.extension_class) # : SchemaArtifacts::_ScalarCoercionAdapter[untyped, untyped]
|
35
|
+
{
|
36
|
+
"coerce_input" => ->(value, ctx) { adapter.coerce_input(value, ctx) },
|
37
|
+
"coerce_result" => ->(value, ctx) { adapter.coerce_result(value, ctx) }
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def object_type_hash
|
43
|
+
@runtime_metadata.object_types_by_name.filter_map do |type_name, type|
|
44
|
+
fields_hash = type.graphql_fields_by_name.filter_map do |field_name, field|
|
45
|
+
if (resolver_name = field.resolver)
|
46
|
+
resolver = @named_resolvers.fetch(resolver_name) do
|
47
|
+
raise Errors::SchemaError, "Resolver `#{resolver_name}` (for `#{type_name}.#{field_name}`) cannot be found."
|
48
|
+
end
|
49
|
+
|
50
|
+
resolver_lambda =
|
51
|
+
if resolver.method(:resolve).parameters.include?([:keyreq, :lookahead])
|
52
|
+
lambda do |object, args, context|
|
53
|
+
schema_field = context.fetch(:elastic_graph_schema).field_named(type_name, field_name)
|
54
|
+
|
55
|
+
# Extract the `:lookahead` extra that we have configured all fields to provide.
|
56
|
+
# See https://graphql-ruby.org/api-doc/1.10.8/GraphQL/Execution/Lookahead.html for more info.
|
57
|
+
# It is not a "real" arg in the schema and breaks `args_to_schema_form` when we call that
|
58
|
+
# so we need to peel it off here.
|
59
|
+
lookahead = args[:lookahead]
|
60
|
+
|
61
|
+
# Convert args to the form they were defined in the schema, undoing the normalization
|
62
|
+
# the GraphQL gem does to convert them to Ruby keyword args form.
|
63
|
+
args = schema_field.args_to_schema_form(args.except(:lookahead))
|
64
|
+
|
65
|
+
result = resolver.resolve(field: schema_field, object: object, args: args, context: context, lookahead: lookahead) do
|
66
|
+
@query_adapter.build_query_from(field: schema_field, args: args, lookahead: lookahead, context: context)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Give the field a chance to coerce the result before returning it. Initially, this is only used to deal with
|
70
|
+
# enum value overrides (e.g. so that if `DayOfWeek.MONDAY` has been overridden to `DayOfWeek.MON`, we can coerce
|
71
|
+
# a `MONDAY` value being returned by a painless script to `MON`), but this is designed to be general purpose
|
72
|
+
# and we may use it for other coercions in the future.
|
73
|
+
#
|
74
|
+
# Note that coercion of scalar values is handled by the `coerce_result` callback below.
|
75
|
+
schema_field.coerce_result(result)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
lambda do |object, args, context|
|
79
|
+
schema_field = context.fetch(:elastic_graph_schema).field_named(type_name, field_name)
|
80
|
+
# Convert args to the form they were defined in the schema, undoing the normalization
|
81
|
+
# the GraphQL gem does to convert them to Ruby keyword args form.
|
82
|
+
args = schema_field.args_to_schema_form(args)
|
83
|
+
|
84
|
+
result = resolver.resolve(field: schema_field, object: object, args: args, context: context)
|
85
|
+
|
86
|
+
# Give the field a chance to coerce the result before returning it. Initially, this is only used to deal with
|
87
|
+
# enum value overrides (e.g. so that if `DayOfWeek.MONDAY` has been overridden to `DayOfWeek.MON`, we can coerce
|
88
|
+
# a `MONDAY` value being returned by a painless script to `MON`), but this is designed to be general purpose
|
89
|
+
# and we may use it for other coercions in the future.
|
90
|
+
#
|
91
|
+
# Note that coercion of scalar values is handled by the `coerce_result` callback below.
|
92
|
+
schema_field.coerce_result(result)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
[field_name, resolver_lambda]
|
97
|
+
end
|
98
|
+
end.to_h
|
99
|
+
|
100
|
+
unless fields_hash.empty?
|
101
|
+
[type_name, fields_hash]
|
102
|
+
end
|
103
|
+
end.to_h
|
104
|
+
end
|
105
|
+
|
106
|
+
# In order to support unions and interfaces, we must implement `resolve_type`.
|
107
|
+
def resolve_type(supertype, object, context)
|
108
|
+
schema = context.fetch(:elastic_graph_schema)
|
109
|
+
# If `__typename` is available, use that to resolve. It should be available on any embedded abstract types...
|
110
|
+
# (See `Inventor` in `config/schema.graphql` for an example of this kind of type union.)
|
111
|
+
if (typename = object["__typename"])
|
112
|
+
schema.graphql_schema.possible_types(supertype).find { |t| t.graphql_name == typename }
|
113
|
+
else
|
114
|
+
# ...otherwise infer the type based on what index the object came from. This is the case
|
115
|
+
# with unions/interfaces of individually indexed types.
|
116
|
+
# (See `Part` in `config/schema/widgets.rb` for an example of this kind of type union.)
|
117
|
+
schema.document_type_stored_in(object.index_definition_name).graphql_type
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
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
|
@@ -14,11 +14,11 @@ module ElasticGraph
|
|
14
14
|
module Resolvers
|
15
15
|
# Responsible for fetching a a list of records of a particular type
|
16
16
|
class ListRecords
|
17
|
-
def
|
18
|
-
|
17
|
+
def initialize(elasticgraph_graphql:, config:)
|
18
|
+
# Nothing to initialize, but needs to be defined to satisfy the resolver interface.
|
19
19
|
end
|
20
20
|
|
21
|
-
def resolve(field:, context:, lookahead
|
21
|
+
def resolve(field:, object:, args:, context:, lookahead:)
|
22
22
|
query = yield
|
23
23
|
response = QuerySource.execute_one(query, for_context: context)
|
24
24
|
RelayConnection.maybe_wrap(response, field: field, context: context, lookahead: lookahead, query: query)
|
@@ -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
|
@@ -6,7 +6,9 @@
|
|
6
6
|
#
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
+
require "elastic_graph/graphql/resolvers/nested_relationships_source"
|
9
10
|
require "elastic_graph/graphql/resolvers/relay_connection"
|
11
|
+
require "elastic_graph/graphql/datastore_response/search_response"
|
10
12
|
|
11
13
|
module ElasticGraph
|
12
14
|
class GraphQL
|
@@ -17,31 +19,32 @@ module ElasticGraph
|
|
17
19
|
#
|
18
20
|
# Most of the logic for this lives in ElasticGraph::Schema::RelationJoin.
|
19
21
|
class NestedRelationships
|
20
|
-
def initialize(
|
21
|
-
@schema_element_names = schema_element_names
|
22
|
-
@logger = logger
|
22
|
+
def initialize(elasticgraph_graphql:, config:)
|
23
|
+
@schema_element_names = elasticgraph_graphql.runtime_metadata.schema_element_names
|
24
|
+
@logger = elasticgraph_graphql.logger
|
25
|
+
@monotonic_clock = elasticgraph_graphql.monotonic_clock
|
26
|
+
@resolver_mode = elasticgraph_graphql.config.nested_relationship_resolver_mode
|
23
27
|
end
|
24
28
|
|
25
|
-
def
|
26
|
-
!!field.relation_join
|
27
|
-
end
|
28
|
-
|
29
|
-
def resolve(object:, field:, context:, lookahead:, **)
|
29
|
+
def resolve(field:, object:, args:, context:, lookahead:)
|
30
30
|
log_warning = ->(**options) { log_field_problem_warning(field: field, **options) }
|
31
31
|
join = field.relation_join
|
32
32
|
id_or_ids = join.extract_id_or_ids_from(object, log_warning)
|
33
|
-
|
34
|
-
build_filter(join.filter_id_field_name, nil, join.foreign_key_nested_paths, Array(id_or_ids)),
|
35
|
-
join.additional_filter
|
36
|
-
].reject(&:empty?)
|
37
|
-
query = yield.merge_with(filters: filters)
|
33
|
+
query = yield
|
38
34
|
|
39
35
|
response =
|
40
36
|
case id_or_ids
|
41
37
|
when nil, []
|
42
38
|
join.blank_value
|
43
39
|
else
|
44
|
-
initial_response =
|
40
|
+
initial_response = try_synthesize_response_from_ids(field, id_or_ids, query) ||
|
41
|
+
NestedRelationshipsSource.execute_one(
|
42
|
+
Array(id_or_ids).to_set,
|
43
|
+
query:, join:, context:,
|
44
|
+
monotonic_clock: @monotonic_clock,
|
45
|
+
mode: @resolver_mode
|
46
|
+
)
|
47
|
+
|
45
48
|
join.normalize_documents(initial_response) do |problem|
|
46
49
|
log_warning.call(document: {"id" => id_or_ids}, problem: "got #{problem} from the datastore search query")
|
47
50
|
end
|
@@ -52,22 +55,49 @@ module ElasticGraph
|
|
52
55
|
|
53
56
|
private
|
54
57
|
|
58
|
+
ONLY_ID = ["id"]
|
59
|
+
|
60
|
+
# When a client requests only the `id` from a nested relationship, and we already have those ids,
|
61
|
+
# we want to avoid querying the datastore, and synthesize a response instead.
|
62
|
+
def try_synthesize_response_from_ids(field, id_or_ids, query)
|
63
|
+
# This optimization can only be used on a relationship with an outbound foreign key.
|
64
|
+
return nil if field.relation.direction == :in
|
65
|
+
|
66
|
+
# If the client is requesting any fields besides `id`, we can't do this.
|
67
|
+
return nil unless (query.requested_fields - ONLY_ID).empty?
|
68
|
+
|
69
|
+
pagination = query.document_paginator.to_datastore_body
|
70
|
+
search_after = pagination.dig(:search_after, 0)
|
71
|
+
ids = Array(id_or_ids)
|
72
|
+
|
73
|
+
sorted_ids =
|
74
|
+
case pagination.dig(:sort, 0, "id", "order")
|
75
|
+
when "asc"
|
76
|
+
ids.sort.select { |id| search_after.nil? || id > search_after }
|
77
|
+
when "desc"
|
78
|
+
ids.sort.reverse.select { |id| search_after.nil? || id < search_after }
|
79
|
+
else
|
80
|
+
if ids.size < 2
|
81
|
+
ids
|
82
|
+
else
|
83
|
+
# The client is sorting by something other than `id` and we have multiple ids.
|
84
|
+
# We aren't able to determine the correct order for the ids, so we can't synthesize
|
85
|
+
# a response.
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
DatastoreResponse::SearchResponse.synthesize_from_ids(
|
91
|
+
query.search_index_expression,
|
92
|
+
sorted_ids.first(pagination.fetch(:size)),
|
93
|
+
decoded_cursor_factory: query.send(:decoded_cursor_factory)
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
55
97
|
def log_field_problem_warning(field:, document:, problem:)
|
56
98
|
id = document.fetch("id", "<no id>")
|
57
99
|
@logger.warn "#{field.parent_type.name}(id: #{id}).#{field.name} had a problem: #{problem}"
|
58
100
|
end
|
59
|
-
|
60
|
-
def build_filter(path, previous_nested_path, nested_paths, ids)
|
61
|
-
if nested_paths.empty?
|
62
|
-
path = path.delete_prefix("#{previous_nested_path}.") if previous_nested_path
|
63
|
-
{path => {@schema_element_names.equal_to_any_of => ids}}
|
64
|
-
else
|
65
|
-
next_nested_path, *rest_nested_paths = nested_paths
|
66
|
-
sub_filter = build_filter(path, next_nested_path, rest_nested_paths, ids)
|
67
|
-
next_nested_path = next_nested_path.delete_prefix("#{previous_nested_path}.") if previous_nested_path
|
68
|
-
{next_nested_path => {@schema_element_names.any_satisfy => sub_filter}}
|
69
|
-
end
|
70
|
-
end
|
71
101
|
end
|
72
102
|
end
|
73
103
|
end
|