elasticgraph-graphql 1.1.0 → 1.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/README.md +0 -4
- data/lib/elastic_graph/graphql/client.rb +8 -4
- data/lib/elastic_graph/graphql/datastore_search_router.rb +5 -1
- data/lib/elastic_graph/graphql/query_adapter/abstract_type_filter.rb +59 -0
- data/lib/elastic_graph/graphql/query_adapter/requested_fields.rb +5 -2
- data/lib/elastic_graph/graphql/query_executor.rb +5 -8
- data/lib/elastic_graph/graphql/resolvers/graphql_adapter_builder.rb +6 -3
- data/lib/elastic_graph/graphql/resolvers/{list_records.rb → indexed_type_root_fields_resolver.rb} +2 -2
- data/lib/elastic_graph/graphql/resolvers/query_source.rb +23 -3
- data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb +10 -1
- data/lib/elastic_graph/graphql/schema/type.rb +38 -10
- data/lib/elastic_graph/graphql/schema.rb +12 -9
- data/lib/elastic_graph/graphql.rb +5 -10
- metadata +23 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aff2847d08949b4bdcd6b6d5ad8d4bdd093998f5e0117545b061336c68c79b2c
|
|
4
|
+
data.tar.gz: 26bad3e27afa0239ac27540a2a1783b51ed6790dfe60db1b6ca8f750dc0f45bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2e1e76a48dea32a3b3f7e0fd7b8b4f71c94f4a9a0595f9c4890c126d1de05666334095715f0fc680b238f465068918af06844a4bd7f18f0912abbba3406b51f
|
|
7
|
+
data.tar.gz: a66d894a2098b9109eeeb71faf42290fc349a90af3116b3a1baa7f2fdedaf64baa6c1e4572179b7ea6c4bfb77f2c13b5c1c71f660732f4b3f9ecc714d30fea33
|
data/README.md
CHANGED
|
@@ -23,9 +23,6 @@ graph LR;
|
|
|
23
23
|
graphql["graphql"];
|
|
24
24
|
elasticgraph-graphql --> graphql;
|
|
25
25
|
class graphql externalGemStyle;
|
|
26
|
-
graphql-c_parser["graphql-c_parser"];
|
|
27
|
-
elasticgraph-graphql --> graphql-c_parser;
|
|
28
|
-
class graphql-c_parser externalGemStyle;
|
|
29
26
|
elasticgraph-apollo["elasticgraph-apollo"];
|
|
30
27
|
elasticgraph-apollo --> elasticgraph-graphql;
|
|
31
28
|
class elasticgraph-apollo otherEgGemStyle;
|
|
@@ -52,7 +49,6 @@ graph LR;
|
|
|
52
49
|
class elasticgraph-schema_definition otherEgGemStyle;
|
|
53
50
|
click base64 href "https://rubygems.org/gems/base64" "Open on RubyGems.org" _blank;
|
|
54
51
|
click graphql href "https://rubygems.org/gems/graphql" "Open on RubyGems.org" _blank;
|
|
55
|
-
click graphql-c_parser href "https://rubygems.org/gems/graphql-c_parser" "Open on RubyGems.org" _blank;
|
|
56
52
|
```
|
|
57
53
|
|
|
58
54
|
## Usage
|
|
@@ -13,12 +13,13 @@ module ElasticGraph
|
|
|
13
13
|
# meant to be a friendly/human readable string (such as a service name)
|
|
14
14
|
# where as `source_description` is meant to be an opaque string describing
|
|
15
15
|
# where `name` came from.
|
|
16
|
-
class Client < Data.define(:source_description, :name)
|
|
16
|
+
class Client < Data.define(:source_description, :name, :extra_opaque_id_parts)
|
|
17
17
|
# `Data.define` provides the following methods:
|
|
18
|
-
# @dynamic
|
|
18
|
+
# @dynamic name, source_description, extra_opaque_id_parts, with
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
def initialize(source_description:, name:, extra_opaque_id_parts: [])
|
|
21
|
+
super
|
|
22
|
+
end
|
|
22
23
|
|
|
23
24
|
def description
|
|
24
25
|
if source_description == name
|
|
@@ -38,6 +39,9 @@ module ElasticGraph
|
|
|
38
39
|
Client::ANONYMOUS
|
|
39
40
|
end
|
|
40
41
|
end
|
|
42
|
+
|
|
43
|
+
ANONYMOUS = new("(anonymous)", "(anonymous)")
|
|
44
|
+
ELASTICGRAPH_INTERNAL = new("(ElasticGraphInternal)", "(ElasticGraphInternal)")
|
|
41
45
|
end
|
|
42
46
|
end
|
|
43
47
|
end
|
|
@@ -10,6 +10,7 @@ require "elastic_graph/constants"
|
|
|
10
10
|
require "elastic_graph/errors"
|
|
11
11
|
require "elastic_graph/graphql/datastore_response/search_response"
|
|
12
12
|
require "elastic_graph/graphql/query_details_tracker"
|
|
13
|
+
require "elastic_graph/support/opaque_id"
|
|
13
14
|
require "elastic_graph/support/threading"
|
|
14
15
|
|
|
15
16
|
module ElasticGraph
|
|
@@ -32,7 +33,7 @@ module ElasticGraph
|
|
|
32
33
|
|
|
33
34
|
# Sends the datastore a multi-search request based on the given queries.
|
|
34
35
|
# Returns a hash of responses keyed by the query.
|
|
35
|
-
def msearch(queries, query_tracker: QueryDetailsTracker.empty)
|
|
36
|
+
def msearch(queries, query_tracker: QueryDetailsTracker.empty, opaque_id_parts: ["elasticgraph-graphql"])
|
|
36
37
|
DatastoreQuery.perform(queries) do |header_body_tuples_by_query|
|
|
37
38
|
# Here we set a client-side timeout, which causes the client to give up and close the connection.
|
|
38
39
|
# According to [1]--"We have a new way to cancel search requests efficiently from the client
|
|
@@ -64,6 +65,9 @@ module ElasticGraph
|
|
|
64
65
|
# even though Faraday (the underlying HTTP client) does. To work around this, we pass our desired
|
|
65
66
|
# timeout in a specific header that the `SupportTimeouts` Faraday middleware will use.
|
|
66
67
|
headers = {TIMEOUT_MS_HEADER => msearch_request_timeout_from(queries)&.to_s}.compact
|
|
68
|
+
if (opaque_id = Support::OpaqueID.build_header(opaque_id_parts))
|
|
69
|
+
headers[OPAQUE_ID_HEADER] = opaque_id
|
|
70
|
+
end
|
|
67
71
|
|
|
68
72
|
queries_and_header_body_tuples_by_datastore_client = header_body_tuples_by_query.group_by do |(query, header_body_tuples)|
|
|
69
73
|
@datastore_clients_by_name.fetch(query.cluster_name)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2024 - 2026 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
|
+
module ElasticGraph
|
|
10
|
+
class GraphQL
|
|
11
|
+
class QueryAdapter
|
|
12
|
+
# Query adapter that injects a `__typename` filter when querying an abstract type (interface
|
|
13
|
+
# or union) that shares an index with types that fall outside the set of its subtypes. Without
|
|
14
|
+
# this filter, documents belonging to those other types would incorrectly appear in results.
|
|
15
|
+
#
|
|
16
|
+
# For example, given this hierarchy:
|
|
17
|
+
#
|
|
18
|
+
# DistributionChannel (abstract interface, index: distribution_channels)
|
|
19
|
+
# ├── Wholesale (abstract interface, distribution_channels index)
|
|
20
|
+
# │ ├── DirectWholesaler (concrete, distribution_channels index)
|
|
21
|
+
# │ └── BrokerWholesaler (concrete, distribution_channels index)
|
|
22
|
+
# └── Retail (abstract interface, distribution_channels index)
|
|
23
|
+
# └── Store (abstract interface, distribution_channels index)
|
|
24
|
+
# ├── OnlineStore (concrete, distribution_channels index)
|
|
25
|
+
# └── PhysicalStore (concrete, physical_stores index — dedicated)
|
|
26
|
+
#
|
|
27
|
+
# A query for `retailers` (i.e. the `Retail` interface) searches both `distribution_channels`
|
|
28
|
+
# and `physical_stores`. Without a `__typename` filter, `DirectWholesaler` and
|
|
29
|
+
# `BrokerWholesaler` documents from `distribution_channels` would appear in results.
|
|
30
|
+
# So we inject:
|
|
31
|
+
#
|
|
32
|
+
# __typename: { equal_to_any_of: ["OnlineStore", "PhysicalStore"] }
|
|
33
|
+
#
|
|
34
|
+
# `PhysicalStore` has a dedicated index with `__typename` stored as a `constant_keyword`,
|
|
35
|
+
# so it matches the `equal_to_any_of` filter just like documents in the shared index.
|
|
36
|
+
class AbstractTypeFilter
|
|
37
|
+
def initialize(schema_element_names)
|
|
38
|
+
@equal_to_any_of = schema_element_names.equal_to_any_of
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call(field:, query:, args:, lookahead:, context:)
|
|
42
|
+
type = field.type.unwrap_fully
|
|
43
|
+
|
|
44
|
+
# For derived types (e.g. indexed aggregations), resolve the underlying document type so we can
|
|
45
|
+
# apply the same __typename scoping as we do for document queries.
|
|
46
|
+
doc_type = type.source_type || type
|
|
47
|
+
|
|
48
|
+
return query unless doc_type.shares_index_with_non_subtypes?
|
|
49
|
+
|
|
50
|
+
subtypes = doc_type.subtypes # Note: subtypes returns all concrete subtypes at any depth
|
|
51
|
+
typename_values = subtypes.map(&:name)
|
|
52
|
+
query.merge_with(internal_filters: [{
|
|
53
|
+
"__typename" => {@equal_to_any_of => typename_values}
|
|
54
|
+
}])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -55,7 +55,7 @@ module ElasticGraph
|
|
|
55
55
|
.selection(@schema.element_names.highlights)
|
|
56
56
|
|
|
57
57
|
{
|
|
58
|
-
individual_docs_needed: pagination_fields_need_individual_docs?(lookahead)
|
|
58
|
+
individual_docs_needed: pagination_fields_need_individual_docs?(lookahead) || relay_connection_node_from(lookahead).selected?,
|
|
59
59
|
requested_fields: requested_fields_under(relay_connection_node_from(lookahead), index_field_paths),
|
|
60
60
|
request_all_highlights: requesting_all_highlights?(lookahead),
|
|
61
61
|
requested_highlights: requested_fields_under(highlights, index_field_paths)
|
|
@@ -86,7 +86,10 @@ module ElasticGraph
|
|
|
86
86
|
requested_fields_for(child, index_field_paths, path_prefix: path_prefix)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
# For abstract types (unions/interfaces), we need __typename to resolve the concrete type.
|
|
90
|
+
# We must fully unwrap the type to check the innermost type, since the field type could be
|
|
91
|
+
# wrapped in non-null or list wrappers (e.g., `[NamedInventor!]!` on a `nodes` relay connection field).
|
|
92
|
+
fields << "#{path_prefix}__typename" if field_for(node.field)&.type&.unwrap_fully&.abstract?
|
|
90
93
|
fields
|
|
91
94
|
end
|
|
92
95
|
|
|
@@ -59,7 +59,8 @@ module ElasticGraph
|
|
|
59
59
|
client: client,
|
|
60
60
|
context: context.merge({
|
|
61
61
|
monotonic_clock_deadline: timeout_in_ms&.+(start_time_in_ms),
|
|
62
|
-
elastic_graph_query_tracker: query_tracker
|
|
62
|
+
elastic_graph_query_tracker: query_tracker,
|
|
63
|
+
elastic_graph_client: client
|
|
63
64
|
}.compact)
|
|
64
65
|
)
|
|
65
66
|
|
|
@@ -89,7 +90,7 @@ module ElasticGraph
|
|
|
89
90
|
@logger.info({
|
|
90
91
|
"message_type" => "ElasticGraphQueryExecutorQueryDuration",
|
|
91
92
|
"client" => client.name,
|
|
92
|
-
"query_fingerprint" =>
|
|
93
|
+
"query_fingerprint" => query.fingerprint,
|
|
93
94
|
"query_name" => query.selected_operation_name,
|
|
94
95
|
"duration_ms" => duration,
|
|
95
96
|
# How long the datastore queries took according to what the datastore itself reported.
|
|
@@ -142,7 +143,7 @@ module ElasticGraph
|
|
|
142
143
|
def execute_query(query, client:)
|
|
143
144
|
# Log the query before starting to execute it, in case there's a lambda timeout, in which case
|
|
144
145
|
# we won't get any other logged messages for the query.
|
|
145
|
-
@logger.info "Starting to execute query #{
|
|
146
|
+
@logger.info "Starting to execute query #{query.fingerprint} for client #{client.description}."
|
|
146
147
|
|
|
147
148
|
query.result
|
|
148
149
|
rescue => ex
|
|
@@ -163,11 +164,7 @@ module ElasticGraph
|
|
|
163
164
|
# the fingerprint to make sure that we at least have some identification information
|
|
164
165
|
# about the query.
|
|
165
166
|
def full_description_of(query)
|
|
166
|
-
"#{
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def fingerprint_for(query)
|
|
170
|
-
query.query_string ? query.fingerprint : "(no query string)"
|
|
167
|
+
"#{query.fingerprint} #{query.sanitized_query_string}"
|
|
171
168
|
end
|
|
172
169
|
|
|
173
170
|
def slo_result_for(query, duration)
|
|
@@ -110,8 +110,9 @@ module ElasticGraph
|
|
|
110
110
|
# In order to support unions and interfaces, we must implement `resolve_type`.
|
|
111
111
|
def resolve_type(supertype, object, context)
|
|
112
112
|
schema = context.fetch(:elastic_graph_schema)
|
|
113
|
-
# If `__typename` is available, use that to resolve. It
|
|
114
|
-
#
|
|
113
|
+
# If `__typename` is available, use that to resolve. It will be present on embedded abstract
|
|
114
|
+
# types, and also on root documents indexed in a shared interface/union index.
|
|
115
|
+
# (See `Inventor` in `config/schema/widgets.rb` for an example of an embedded abstract type.)
|
|
115
116
|
if (typename = object["__typename"])
|
|
116
117
|
schema
|
|
117
118
|
.graphql_schema
|
|
@@ -121,7 +122,9 @@ module ElasticGraph
|
|
|
121
122
|
# ...otherwise infer the type based on what index the object came from. This is the case
|
|
122
123
|
# with unions/interfaces of individually indexed types.
|
|
123
124
|
# (See `Part` in `config/schema/widgets.rb` for an example of this kind of type union.)
|
|
124
|
-
|
|
125
|
+
# This branch is only reached for individually-indexed types (no `__typename`
|
|
126
|
+
# in the document), so the set always contains exactly one type.
|
|
127
|
+
schema.document_types_stored_in(object.index_definition_name).first.graphql_type
|
|
125
128
|
end
|
|
126
129
|
end
|
|
127
130
|
end
|
data/lib/elastic_graph/graphql/resolvers/{list_records.rb → indexed_type_root_fields_resolver.rb}
RENAMED
|
@@ -12,8 +12,8 @@ require "elastic_graph/graphql/resolvers/relay_connection"
|
|
|
12
12
|
module ElasticGraph
|
|
13
13
|
class GraphQL
|
|
14
14
|
module Resolvers
|
|
15
|
-
# Responsible for
|
|
16
|
-
class
|
|
15
|
+
# Responsible for resolving the list and aggregation root fields generated for each indexed type.
|
|
16
|
+
class IndexedTypeRootFieldsResolver
|
|
17
17
|
def initialize(elasticgraph_graphql:, config:)
|
|
18
18
|
# Nothing to initialize, but needs to be defined to satisfy the resolver interface.
|
|
19
19
|
end
|
|
@@ -20,28 +20,48 @@ module ElasticGraph
|
|
|
20
20
|
# Note: `NestedRelationshipsSource` implements further optimizations on top of this, and should
|
|
21
21
|
# be used rather than this class when applicable.
|
|
22
22
|
class QuerySource < ::GraphQL::Dataloader::Source
|
|
23
|
-
def initialize(datastore_router, query_tracker)
|
|
23
|
+
def initialize(datastore_router, query_tracker, opaque_id_parts = [])
|
|
24
24
|
@datastore_router = datastore_router
|
|
25
25
|
@query_tracker = query_tracker
|
|
26
|
+
@opaque_id_parts = opaque_id_parts
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def fetch(queries)
|
|
29
|
-
responses_by_query = @datastore_router.msearch(
|
|
30
|
+
responses_by_query = @datastore_router.msearch(
|
|
31
|
+
queries,
|
|
32
|
+
query_tracker: @query_tracker,
|
|
33
|
+
opaque_id_parts: @opaque_id_parts
|
|
34
|
+
)
|
|
30
35
|
queries.map { |q| responses_by_query.fetch(q) }
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def self.execute_many(queries, for_context:)
|
|
34
39
|
datastore_router = for_context.fetch(:datastore_search_router)
|
|
35
40
|
query_tracker = for_context.fetch(:elastic_graph_query_tracker)
|
|
41
|
+
opaque_id_parts = datastore_opaque_id_parts_for(for_context)
|
|
36
42
|
dataloader = for_context.dataloader
|
|
37
43
|
|
|
38
|
-
responses = dataloader.with(self, datastore_router, query_tracker).load_all(queries)
|
|
44
|
+
responses = dataloader.with(self, datastore_router, query_tracker, opaque_id_parts).load_all(queries)
|
|
39
45
|
queries.zip(responses).to_h
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
def self.execute_one(query, for_context:)
|
|
43
49
|
execute_many([query], for_context: for_context).fetch(query)
|
|
44
50
|
end
|
|
51
|
+
|
|
52
|
+
# `QueryExecutor` adds `:elastic_graph_client` to the GraphQL context before
|
|
53
|
+
# resolver execution begins, so resolver-side datastore queries can reuse the
|
|
54
|
+
# same client identity in their datastore `X-Opaque-Id` headers.
|
|
55
|
+
private_class_method def self.datastore_opaque_id_parts_for(for_context)
|
|
56
|
+
client = for_context.fetch(:elastic_graph_client)
|
|
57
|
+
graphql_query = for_context.query
|
|
58
|
+
[
|
|
59
|
+
"elasticgraph-graphql",
|
|
60
|
+
"client=#{client.name}",
|
|
61
|
+
*client.extra_opaque_id_parts,
|
|
62
|
+
"query=#{graphql_query.fingerprint}"
|
|
63
|
+
]
|
|
64
|
+
end
|
|
45
65
|
end
|
|
46
66
|
end
|
|
47
67
|
end
|
data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb
CHANGED
|
@@ -47,7 +47,16 @@ module ElasticGraph
|
|
|
47
47
|
|
|
48
48
|
def all_highlights
|
|
49
49
|
@all_highlights ||= begin
|
|
50
|
-
document_type =
|
|
50
|
+
document_type =
|
|
51
|
+
if (typename = node["__typename"])
|
|
52
|
+
# When multiple types share an index, documents carry a `__typename` field
|
|
53
|
+
# that identifies the concrete type; use it to resolve the concrete type directly.
|
|
54
|
+
schema.type_named(typename)
|
|
55
|
+
else
|
|
56
|
+
# Index inheritance always injects `__typename` into any shared index mapping,
|
|
57
|
+
# so a missing `__typename` means no sharing — the set contains exactly one type.
|
|
58
|
+
schema.document_types_stored_in(node.index_definition_name).first # : Schema::Type
|
|
59
|
+
end
|
|
51
60
|
|
|
52
61
|
node.highlights.filter_map do |path_string, snippets|
|
|
53
62
|
if (path = path_from(path_string, document_type))
|
|
@@ -61,12 +61,12 @@ module ElasticGraph
|
|
|
61
61
|
# List of index definitions that should be searched for this type.
|
|
62
62
|
def search_index_definitions
|
|
63
63
|
@search_index_definitions ||=
|
|
64
|
-
if
|
|
65
|
-
#
|
|
66
|
-
# dumping index definitions in the runtime metadata
|
|
67
|
-
# because of abstract (interface/union) types. The source document
|
|
68
|
-
# there is a supertype/subtype relationship on the document types)
|
|
69
|
-
# does not exist on
|
|
64
|
+
if (st = source_type)
|
|
65
|
+
# When a type has a source type (a prime example being indexed aggregations), we delegate
|
|
66
|
+
# to the source type. This works better than dumping index definitions in the runtime metadata
|
|
67
|
+
# of the derived type itself because of abstract (interface/union) types. The source document
|
|
68
|
+
# type handles that (since there is a supertype/subtype relationship on the document types)
|
|
69
|
+
# but that relationship does not exist on derived types.
|
|
70
70
|
#
|
|
71
71
|
# For example, assume we have these indexed document types:
|
|
72
72
|
# - type Person {}
|
|
@@ -76,9 +76,15 @@ module ElasticGraph
|
|
|
76
76
|
# We can go from `Inventor` to its subtypes to find the search indexes. However, `InventorAggregation`
|
|
77
77
|
# is NOT a union of `PersonAggregation` and `CompanyAggregation`, so we can't do the same thing on the
|
|
78
78
|
# indexed aggregation types. Delegating to the source type solves this case.
|
|
79
|
-
|
|
79
|
+
st.search_index_definitions
|
|
80
|
+
elsif abstract?
|
|
81
|
+
# For abstract types, derive search indexes purely from concrete subtypes. This correctly
|
|
82
|
+
# handles cases where subtypes override the abstract type's declared index with a dedicated
|
|
83
|
+
# one — only indexes that actually contain documents for this type are searched.
|
|
84
|
+
# Note: subtypes returns all concrete subtypes at any depth, so no explicit recursion is needed.
|
|
85
|
+
subtypes.flat_map(&:search_index_definitions).to_set
|
|
80
86
|
else
|
|
81
|
-
@index_definitions
|
|
87
|
+
@index_definitions
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
|
|
@@ -117,13 +123,35 @@ module ElasticGraph
|
|
|
117
123
|
end
|
|
118
124
|
end
|
|
119
125
|
|
|
120
|
-
# Returns
|
|
126
|
+
# Returns all concrete subtypes, at any depth. This is like `#possible_types` provided by the
|
|
121
127
|
# GraphQL gem, but that includes a type itself when you ask for the possible types of a non-abstract type.
|
|
122
128
|
def subtypes
|
|
123
129
|
@subtypes ||= @schema
|
|
124
130
|
.graphql_schema
|
|
125
131
|
.possible_types(graphql_type, visibility_profile: :boot)
|
|
126
|
-
.map { |t| @schema.type_from(t) }
|
|
132
|
+
.map { |t| @schema.type_from(t) }
|
|
133
|
+
.reject { |t| t == self }
|
|
134
|
+
.to_set
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# For derived types (e.g. indexed aggregations), returns the underlying source document type.
|
|
138
|
+
# Returns `nil` for non-derived types.
|
|
139
|
+
def source_type
|
|
140
|
+
return @source_type if defined?(@source_type)
|
|
141
|
+
@source_type = @object_runtime_metadata&.source_type&.then { |st| @schema.type_named(st) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Returns true if any of this type's search indexes contain any concrete document types
|
|
145
|
+
# that are not subtypes of this type. Used to determine whether a `__typename` filter is
|
|
146
|
+
# needed when querying an abstract type.
|
|
147
|
+
def shares_index_with_non_subtypes?
|
|
148
|
+
return @shares_index_with_non_subtypes if defined?(@shares_index_with_non_subtypes)
|
|
149
|
+
@shares_index_with_non_subtypes =
|
|
150
|
+
search_index_definitions.any? do |index_def|
|
|
151
|
+
@schema.document_types_stored_in(index_def.name).any? do |t|
|
|
152
|
+
t != self && !subtypes.include?(t) && !t.abstract?
|
|
153
|
+
end
|
|
154
|
+
end
|
|
127
155
|
end
|
|
128
156
|
|
|
129
157
|
def field_named(field_name)
|
|
@@ -120,7 +120,12 @@ module ElasticGraph
|
|
|
120
120
|
raise Errors::NotFoundError, msg
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
# Returns all indexed document types stored in the named index definition.
|
|
124
|
+
# The returned set includes both abstract and concrete types. Multiple types may be returned
|
|
125
|
+
# when abstract types share an index with their concrete subtypes via index inheritance.
|
|
126
|
+
# @raise [Errors::NotFoundError] if the index definition name is not recognized
|
|
127
|
+
# @raise [ArgumentError] if given the name of a rollover index instead of the parent index definition name
|
|
128
|
+
def document_types_stored_in(index_definition_name)
|
|
124
129
|
indexed_document_types_by_index_definition_name.fetch(index_definition_name) do
|
|
125
130
|
if index_definition_name.include?(ROLLOVER_INDEX_INFIX_MARKER)
|
|
126
131
|
raise ArgumentError, "`#{index_definition_name}` is the name of a rollover index; pass the name of the parent index definition instead."
|
|
@@ -190,16 +195,14 @@ module ElasticGraph
|
|
|
190
195
|
end
|
|
191
196
|
end
|
|
192
197
|
|
|
198
|
+
# Intentionally private: public callers should use `document_types_stored_in` instead.
|
|
193
199
|
def indexed_document_types_by_index_definition_name
|
|
194
|
-
@indexed_document_types_by_index_definition_name ||=
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
@indexed_document_types_by_index_definition_name ||=
|
|
201
|
+
indexed_document_types.each_with_object(::Hash.new { |h, k| h[k] = ::Set.new }) do |type, hash|
|
|
202
|
+
type.index_definitions.each do |index_def|
|
|
203
|
+
hash[index_def.name] << type
|
|
198
204
|
end
|
|
199
|
-
|
|
200
|
-
hash[index_def.name] = type
|
|
201
|
-
end
|
|
202
|
-
end.freeze
|
|
205
|
+
end.freeze
|
|
203
206
|
end
|
|
204
207
|
|
|
205
208
|
def log_hidden_types
|
|
@@ -10,6 +10,7 @@ require "elastic_graph/datastore_core"
|
|
|
10
10
|
require "elastic_graph/graphql/config"
|
|
11
11
|
require "elastic_graph/constants"
|
|
12
12
|
require "elastic_graph/support/from_yaml_file"
|
|
13
|
+
require "elastic_graph/support/graphql_gem_loader"
|
|
13
14
|
|
|
14
15
|
module ElasticGraph
|
|
15
16
|
# The main entry point for ElasticGraph GraphQL handling. Instantiate this to get access to the
|
|
@@ -132,13 +133,7 @@ module ElasticGraph
|
|
|
132
133
|
# @private
|
|
133
134
|
def graphql_gem_plugins
|
|
134
135
|
@graphql_gem_plugins ||= begin
|
|
135
|
-
|
|
136
|
-
# As per https://graphql-ruby.org/language_tools/c_parser.html, loading the
|
|
137
|
-
# C parser causes the faster parser to be assigned as the `::GraphQL.default_parser`,
|
|
138
|
-
# providing greater efficiency.
|
|
139
|
-
#
|
|
140
|
-
# We load it here since this is where we load the GraphQL gem.
|
|
141
|
-
require "graphql/c_parser"
|
|
136
|
+
Support::GraphQLGemLoader.load
|
|
142
137
|
|
|
143
138
|
{
|
|
144
139
|
# We depend on this to avoid N+1 calls to the datastore.
|
|
@@ -197,6 +192,7 @@ module ElasticGraph
|
|
|
197
192
|
def datastore_query_adapters
|
|
198
193
|
@datastore_query_adapters ||= begin
|
|
199
194
|
require "elastic_graph/graphql/aggregation/query_adapter"
|
|
195
|
+
require "elastic_graph/graphql/query_adapter/abstract_type_filter"
|
|
200
196
|
require "elastic_graph/graphql/query_adapter/filters"
|
|
201
197
|
require "elastic_graph/graphql/query_adapter/pagination"
|
|
202
198
|
require "elastic_graph/graphql/query_adapter/sort"
|
|
@@ -205,6 +201,7 @@ module ElasticGraph
|
|
|
205
201
|
schema_element_names = runtime_metadata.schema_element_names
|
|
206
202
|
|
|
207
203
|
[
|
|
204
|
+
GraphQL::QueryAdapter::AbstractTypeFilter.new(schema_element_names),
|
|
208
205
|
GraphQL::QueryAdapter::Pagination.new(schema_element_names: schema_element_names),
|
|
209
206
|
GraphQL::QueryAdapter::Filters.new(
|
|
210
207
|
schema_element_names: schema_element_names,
|
|
@@ -268,9 +265,7 @@ module ElasticGraph
|
|
|
268
265
|
# at boot time instead of deferring dependency loading until we handle the first query. In other environments (such as tests),
|
|
269
266
|
# it's nice to load dependencies when needed.
|
|
270
267
|
def load_dependencies_eagerly
|
|
271
|
-
|
|
272
|
-
require "graphql/c_parser"
|
|
273
|
-
|
|
268
|
+
Support::GraphQLGemLoader.load
|
|
274
269
|
::GraphQL.eager_load!
|
|
275
270
|
|
|
276
271
|
# run a simple GraphQL query to force load any dependencies needed to handle GraphQL queries
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: elasticgraph-graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Myron Marston
|
|
@@ -31,132 +31,112 @@ dependencies:
|
|
|
31
31
|
requirements:
|
|
32
32
|
- - '='
|
|
33
33
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: 1.
|
|
34
|
+
version: 1.2.0
|
|
35
35
|
type: :runtime
|
|
36
36
|
prerelease: false
|
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
38
|
requirements:
|
|
39
39
|
- - '='
|
|
40
40
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: 1.
|
|
41
|
+
version: 1.2.0
|
|
42
42
|
- !ruby/object:Gem::Dependency
|
|
43
43
|
name: elasticgraph-schema_artifacts
|
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
|
45
45
|
requirements:
|
|
46
46
|
- - '='
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: 1.
|
|
48
|
+
version: 1.2.0
|
|
49
49
|
type: :runtime
|
|
50
50
|
prerelease: false
|
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
52
|
requirements:
|
|
53
53
|
- - '='
|
|
54
54
|
- !ruby/object:Gem::Version
|
|
55
|
-
version: 1.
|
|
55
|
+
version: 1.2.0
|
|
56
56
|
- !ruby/object:Gem::Dependency
|
|
57
57
|
name: graphql
|
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
|
59
59
|
requirements:
|
|
60
60
|
- - "~>"
|
|
61
61
|
- !ruby/object:Gem::Version
|
|
62
|
-
version: 2.
|
|
62
|
+
version: 2.6.2
|
|
63
63
|
type: :runtime
|
|
64
64
|
prerelease: false
|
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
66
66
|
requirements:
|
|
67
67
|
- - "~>"
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
|
-
version: 2.
|
|
70
|
-
- !ruby/object:Gem::Dependency
|
|
71
|
-
name: graphql-c_parser
|
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
|
73
|
-
requirements:
|
|
74
|
-
- - "~>"
|
|
75
|
-
- !ruby/object:Gem::Version
|
|
76
|
-
version: '1.1'
|
|
77
|
-
- - ">="
|
|
78
|
-
- !ruby/object:Gem::Version
|
|
79
|
-
version: 1.1.3
|
|
80
|
-
type: :runtime
|
|
81
|
-
prerelease: false
|
|
82
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
83
|
-
requirements:
|
|
84
|
-
- - "~>"
|
|
85
|
-
- !ruby/object:Gem::Version
|
|
86
|
-
version: '1.1'
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: 1.1.3
|
|
69
|
+
version: 2.6.2
|
|
90
70
|
- !ruby/object:Gem::Dependency
|
|
91
71
|
name: elasticgraph-admin
|
|
92
72
|
requirement: !ruby/object:Gem::Requirement
|
|
93
73
|
requirements:
|
|
94
74
|
- - '='
|
|
95
75
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 1.
|
|
76
|
+
version: 1.2.0
|
|
97
77
|
type: :development
|
|
98
78
|
prerelease: false
|
|
99
79
|
version_requirements: !ruby/object:Gem::Requirement
|
|
100
80
|
requirements:
|
|
101
81
|
- - '='
|
|
102
82
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: 1.
|
|
83
|
+
version: 1.2.0
|
|
104
84
|
- !ruby/object:Gem::Dependency
|
|
105
85
|
name: elasticgraph-elasticsearch
|
|
106
86
|
requirement: !ruby/object:Gem::Requirement
|
|
107
87
|
requirements:
|
|
108
88
|
- - '='
|
|
109
89
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: 1.
|
|
90
|
+
version: 1.2.0
|
|
111
91
|
type: :development
|
|
112
92
|
prerelease: false
|
|
113
93
|
version_requirements: !ruby/object:Gem::Requirement
|
|
114
94
|
requirements:
|
|
115
95
|
- - '='
|
|
116
96
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 1.
|
|
97
|
+
version: 1.2.0
|
|
118
98
|
- !ruby/object:Gem::Dependency
|
|
119
99
|
name: elasticgraph-opensearch
|
|
120
100
|
requirement: !ruby/object:Gem::Requirement
|
|
121
101
|
requirements:
|
|
122
102
|
- - '='
|
|
123
103
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: 1.
|
|
104
|
+
version: 1.2.0
|
|
125
105
|
type: :development
|
|
126
106
|
prerelease: false
|
|
127
107
|
version_requirements: !ruby/object:Gem::Requirement
|
|
128
108
|
requirements:
|
|
129
109
|
- - '='
|
|
130
110
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: 1.
|
|
111
|
+
version: 1.2.0
|
|
132
112
|
- !ruby/object:Gem::Dependency
|
|
133
113
|
name: elasticgraph-indexer
|
|
134
114
|
requirement: !ruby/object:Gem::Requirement
|
|
135
115
|
requirements:
|
|
136
116
|
- - '='
|
|
137
117
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: 1.
|
|
118
|
+
version: 1.2.0
|
|
139
119
|
type: :development
|
|
140
120
|
prerelease: false
|
|
141
121
|
version_requirements: !ruby/object:Gem::Requirement
|
|
142
122
|
requirements:
|
|
143
123
|
- - '='
|
|
144
124
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: 1.
|
|
125
|
+
version: 1.2.0
|
|
146
126
|
- !ruby/object:Gem::Dependency
|
|
147
127
|
name: elasticgraph-schema_definition
|
|
148
128
|
requirement: !ruby/object:Gem::Requirement
|
|
149
129
|
requirements:
|
|
150
130
|
- - '='
|
|
151
131
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: 1.
|
|
132
|
+
version: 1.2.0
|
|
153
133
|
type: :development
|
|
154
134
|
prerelease: false
|
|
155
135
|
version_requirements: !ruby/object:Gem::Requirement
|
|
156
136
|
requirements:
|
|
157
137
|
- - '='
|
|
158
138
|
- !ruby/object:Gem::Version
|
|
159
|
-
version: 1.
|
|
139
|
+
version: 1.2.0
|
|
160
140
|
email:
|
|
161
141
|
- myron@squareup.com
|
|
162
142
|
executables: []
|
|
@@ -205,6 +185,7 @@ files:
|
|
|
205
185
|
- lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb
|
|
206
186
|
- lib/elastic_graph/graphql/filtering/range_query.rb
|
|
207
187
|
- lib/elastic_graph/graphql/http_endpoint.rb
|
|
188
|
+
- lib/elastic_graph/graphql/query_adapter/abstract_type_filter.rb
|
|
208
189
|
- lib/elastic_graph/graphql/query_adapter/filters.rb
|
|
209
190
|
- lib/elastic_graph/graphql/query_adapter/pagination.rb
|
|
210
191
|
- lib/elastic_graph/graphql/query_adapter/requested_fields.rb
|
|
@@ -213,7 +194,7 @@ files:
|
|
|
213
194
|
- lib/elastic_graph/graphql/query_executor.rb
|
|
214
195
|
- lib/elastic_graph/graphql/resolvers/get_record_field_value.rb
|
|
215
196
|
- lib/elastic_graph/graphql/resolvers/graphql_adapter_builder.rb
|
|
216
|
-
- lib/elastic_graph/graphql/resolvers/
|
|
197
|
+
- lib/elastic_graph/graphql/resolvers/indexed_type_root_fields_resolver.rb
|
|
217
198
|
- lib/elastic_graph/graphql/resolvers/nested_relationships.rb
|
|
218
199
|
- lib/elastic_graph/graphql/resolvers/nested_relationships_source.rb
|
|
219
200
|
- lib/elastic_graph/graphql/resolvers/object.rb
|
|
@@ -247,10 +228,10 @@ licenses:
|
|
|
247
228
|
- MIT
|
|
248
229
|
metadata:
|
|
249
230
|
bug_tracker_uri: https://github.com/block/elasticgraph/issues
|
|
250
|
-
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v1.
|
|
251
|
-
documentation_uri: https://block.github.io/elasticgraph/api-docs/v1.
|
|
231
|
+
changelog_uri: https://github.com/block/elasticgraph/releases/tag/v1.2.0
|
|
232
|
+
documentation_uri: https://block.github.io/elasticgraph/api-docs/v1.2.0/
|
|
252
233
|
homepage_uri: https://block.github.io/elasticgraph/
|
|
253
|
-
source_code_uri: https://github.com/block/elasticgraph/tree/v1.
|
|
234
|
+
source_code_uri: https://github.com/block/elasticgraph/tree/v1.2.0/elasticgraph-graphql
|
|
254
235
|
gem_category: core
|
|
255
236
|
rdoc_options: []
|
|
256
237
|
require_paths:
|
|
@@ -269,7 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
269
250
|
- !ruby/object:Gem::Version
|
|
270
251
|
version: '0'
|
|
271
252
|
requirements: []
|
|
272
|
-
rubygems_version: 4.0.
|
|
253
|
+
rubygems_version: 4.0.6
|
|
273
254
|
specification_version: 4
|
|
274
255
|
summary: Provides the ElasticGraph GraphQL query engine.
|
|
275
256
|
test_files: []
|