elasticgraph-graphql 0.18.0.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +3 -0
- data/elasticgraph-graphql.gemspec +23 -0
- data/lib/elastic_graph/graphql/aggregation/composite_grouping_adapter.rb +79 -0
- data/lib/elastic_graph/graphql/aggregation/computation.rb +39 -0
- data/lib/elastic_graph/graphql/aggregation/date_histogram_grouping.rb +83 -0
- data/lib/elastic_graph/graphql/aggregation/field_path_encoder.rb +47 -0
- data/lib/elastic_graph/graphql/aggregation/field_term_grouping.rb +26 -0
- data/lib/elastic_graph/graphql/aggregation/key.rb +87 -0
- data/lib/elastic_graph/graphql/aggregation/nested_sub_aggregation.rb +37 -0
- data/lib/elastic_graph/graphql/aggregation/non_composite_grouping_adapter.rb +129 -0
- data/lib/elastic_graph/graphql/aggregation/path_segment.rb +31 -0
- data/lib/elastic_graph/graphql/aggregation/query.rb +172 -0
- data/lib/elastic_graph/graphql/aggregation/query_adapter.rb +345 -0
- data/lib/elastic_graph/graphql/aggregation/query_optimizer.rb +187 -0
- data/lib/elastic_graph/graphql/aggregation/resolvers/aggregated_values.rb +41 -0
- data/lib/elastic_graph/graphql/aggregation/resolvers/count_detail.rb +44 -0
- data/lib/elastic_graph/graphql/aggregation/resolvers/grouped_by.rb +30 -0
- data/lib/elastic_graph/graphql/aggregation/resolvers/node.rb +64 -0
- data/lib/elastic_graph/graphql/aggregation/resolvers/relay_connection_builder.rb +83 -0
- data/lib/elastic_graph/graphql/aggregation/resolvers/sub_aggregations.rb +82 -0
- data/lib/elastic_graph/graphql/aggregation/script_term_grouping.rb +32 -0
- data/lib/elastic_graph/graphql/aggregation/term_grouping.rb +118 -0
- data/lib/elastic_graph/graphql/client.rb +43 -0
- data/lib/elastic_graph/graphql/config.rb +81 -0
- data/lib/elastic_graph/graphql/datastore_query/document_paginator.rb +100 -0
- data/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +142 -0
- data/lib/elastic_graph/graphql/datastore_query/paginator.rb +199 -0
- data/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +239 -0
- data/lib/elastic_graph/graphql/datastore_query.rb +372 -0
- data/lib/elastic_graph/graphql/datastore_response/document.rb +78 -0
- data/lib/elastic_graph/graphql/datastore_response/search_response.rb +79 -0
- data/lib/elastic_graph/graphql/datastore_search_router.rb +151 -0
- data/lib/elastic_graph/graphql/decoded_cursor.rb +120 -0
- data/lib/elastic_graph/graphql/filtering/boolean_query.rb +45 -0
- data/lib/elastic_graph/graphql/filtering/field_path.rb +81 -0
- data/lib/elastic_graph/graphql/filtering/filter_args_translator.rb +58 -0
- data/lib/elastic_graph/graphql/filtering/filter_interpreter.rb +526 -0
- data/lib/elastic_graph/graphql/filtering/filter_value_set_extractor.rb +148 -0
- data/lib/elastic_graph/graphql/filtering/range_query.rb +56 -0
- data/lib/elastic_graph/graphql/http_endpoint.rb +229 -0
- data/lib/elastic_graph/graphql/monkey_patches/schema_field.rb +56 -0
- data/lib/elastic_graph/graphql/monkey_patches/schema_object.rb +48 -0
- data/lib/elastic_graph/graphql/query_adapter/filters.rb +161 -0
- data/lib/elastic_graph/graphql/query_adapter/pagination.rb +27 -0
- data/lib/elastic_graph/graphql/query_adapter/requested_fields.rb +124 -0
- data/lib/elastic_graph/graphql/query_adapter/sort.rb +32 -0
- data/lib/elastic_graph/graphql/query_details_tracker.rb +60 -0
- data/lib/elastic_graph/graphql/query_executor.rb +200 -0
- data/lib/elastic_graph/graphql/resolvers/get_record_field_value.rb +49 -0
- data/lib/elastic_graph/graphql/resolvers/graphql_adapter.rb +114 -0
- data/lib/elastic_graph/graphql/resolvers/list_records.rb +29 -0
- data/lib/elastic_graph/graphql/resolvers/nested_relationships.rb +74 -0
- data/lib/elastic_graph/graphql/resolvers/query_adapter.rb +85 -0
- data/lib/elastic_graph/graphql/resolvers/query_source.rb +46 -0
- data/lib/elastic_graph/graphql/resolvers/relay_connection/array_adapter.rb +71 -0
- data/lib/elastic_graph/graphql/resolvers/relay_connection/generic_adapter.rb +65 -0
- data/lib/elastic_graph/graphql/resolvers/relay_connection/page_info.rb +82 -0
- data/lib/elastic_graph/graphql/resolvers/relay_connection/search_response_adapter_builder.rb +40 -0
- data/lib/elastic_graph/graphql/resolvers/relay_connection.rb +42 -0
- data/lib/elastic_graph/graphql/resolvers/resolvable_value.rb +56 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/cursor.rb +35 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/date.rb +64 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/date_time.rb +60 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/local_time.rb +30 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/longs.rb +47 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/no_op.rb +24 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/time_zone.rb +44 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/untyped.rb +32 -0
- data/lib/elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones.rb +634 -0
- data/lib/elastic_graph/graphql/schema/arguments.rb +78 -0
- data/lib/elastic_graph/graphql/schema/enum_value.rb +30 -0
- data/lib/elastic_graph/graphql/schema/field.rb +147 -0
- data/lib/elastic_graph/graphql/schema/relation_join.rb +103 -0
- data/lib/elastic_graph/graphql/schema/type.rb +263 -0
- data/lib/elastic_graph/graphql/schema.rb +164 -0
- data/lib/elastic_graph/graphql.rb +253 -0
- data/script/dump_time_zones +81 -0
- data/script/dump_time_zones.java +17 -0
- metadata +503 -0
|
@@ -0,0 +1,164 @@
|
|
|
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 "digest/md5"
|
|
10
|
+
require "forwardable"
|
|
11
|
+
require "graphql"
|
|
12
|
+
require "elastic_graph/constants"
|
|
13
|
+
require "elastic_graph/error"
|
|
14
|
+
require "elastic_graph/graphql/monkey_patches/schema_field"
|
|
15
|
+
require "elastic_graph/graphql/monkey_patches/schema_object"
|
|
16
|
+
require "elastic_graph/graphql/schema/field"
|
|
17
|
+
require "elastic_graph/graphql/schema/type"
|
|
18
|
+
require "elastic_graph/support/hash_util"
|
|
19
|
+
|
|
20
|
+
module ElasticGraph
|
|
21
|
+
# Wraps a GraphQL::Schema object in order to provide higher-level, more convenient APIs
|
|
22
|
+
# on top of that. The schema is assumed to be immutable, so this class memoizes many
|
|
23
|
+
# computations it does, ensuring we never need to traverse the schema graph multiple times.
|
|
24
|
+
class GraphQL
|
|
25
|
+
class Schema
|
|
26
|
+
BUILT_IN_TYPE_NAMES = (
|
|
27
|
+
scalar_types = ::GraphQL::Schema::BUILT_IN_TYPES.keys # Int, ID, String, etc
|
|
28
|
+
introspection_types = ::GraphQL::Schema.types.keys # __Type, __Schema, etc
|
|
29
|
+
scalar_types.to_set.union(introspection_types)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
attr_reader :element_names, :defined_types, :config, :graphql_schema, :runtime_metadata
|
|
33
|
+
|
|
34
|
+
def initialize(
|
|
35
|
+
graphql_schema_string:,
|
|
36
|
+
config:,
|
|
37
|
+
runtime_metadata:,
|
|
38
|
+
index_definitions_by_graphql_type:,
|
|
39
|
+
graphql_gem_plugins:,
|
|
40
|
+
&build_resolver
|
|
41
|
+
)
|
|
42
|
+
@element_names = runtime_metadata.schema_element_names
|
|
43
|
+
@config = config
|
|
44
|
+
@runtime_metadata = runtime_metadata
|
|
45
|
+
|
|
46
|
+
@types_by_graphql_type = Hash.new do |hash, key|
|
|
47
|
+
hash[key] = Type.new(
|
|
48
|
+
self,
|
|
49
|
+
key,
|
|
50
|
+
index_definitions_by_graphql_type[key.graphql_name] || [],
|
|
51
|
+
runtime_metadata.object_types_by_name[key.graphql_name],
|
|
52
|
+
runtime_metadata.enum_types_by_name[key.graphql_name]
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@types_by_name = Hash.new { |hash, key| hash[key] = lookup_type_by_name(key) }
|
|
57
|
+
@build_resolver = build_resolver
|
|
58
|
+
|
|
59
|
+
# Note: as part of loading the schema, the GraphQL gem may use the resolver (such
|
|
60
|
+
# when a directive has a custom scalar) so we must wait to instantiate the schema
|
|
61
|
+
# as late as possible here. If we do this before initializing some of the instance
|
|
62
|
+
# variables above we'll get `NoMethodError` on `nil`.
|
|
63
|
+
@graphql_schema = ::GraphQL::Schema.from_definition(
|
|
64
|
+
graphql_schema_string,
|
|
65
|
+
default_resolve: LazyResolverAdapter.new(method(:resolver)),
|
|
66
|
+
using: graphql_gem_plugins
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Pre-load all defined types so that all field extras can get configured as part
|
|
70
|
+
# of loading the schema, before we execute the first query.
|
|
71
|
+
@defined_types = build_defined_types_array(@graphql_schema)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def type_from(graphql_type)
|
|
75
|
+
@types_by_graphql_type[graphql_type]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Note: this does not support "wrapped" types (e.g. `Int!` or `[Int]` compared to `Int`),
|
|
79
|
+
# as the graphql schema object does not give us an index of those by name. You can still
|
|
80
|
+
# get type objects for wrapped types, but you need to get it from a field object of that
|
|
81
|
+
# type.
|
|
82
|
+
def type_named(type_name)
|
|
83
|
+
@types_by_name[type_name.to_s]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def document_type_stored_in(index_definition_name)
|
|
87
|
+
indexed_document_types_by_index_definition_name.fetch(index_definition_name) do
|
|
88
|
+
if index_definition_name.include?(ROLLOVER_INDEX_INFIX_MARKER)
|
|
89
|
+
raise ArgumentError, "`#{index_definition_name}` is the name of a rollover index; pass the name of the parent index definition instead."
|
|
90
|
+
else
|
|
91
|
+
raise NotFoundError, "The index definition `#{index_definition_name}` does not appear to exist. Is it misspelled?"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def field_named(type_name, field_name)
|
|
97
|
+
type_named(type_name).field_named(field_name)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def enum_value_named(type_name, enum_value_name)
|
|
101
|
+
type_named(type_name).enum_value_named(enum_value_name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The list of user-defined types that are indexed document types. (Indexed aggregation types will not be included in this.)
|
|
105
|
+
def indexed_document_types
|
|
106
|
+
@indexed_document_types ||= defined_types.select(&:indexed_document?)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_s
|
|
110
|
+
"#<#{self.class.name} 0x#{__id__.to_s(16)} indexed_document_types=#{indexed_document_types.map(&:name).sort.to_s.delete(":")}>"
|
|
111
|
+
end
|
|
112
|
+
alias_method :inspect, :to_s
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
# Adapter class to allow us to lazily load the resolver instance.
|
|
117
|
+
#
|
|
118
|
+
# Necessary because the resolver must be provided to `GraphQL::Schema.from_definition`,
|
|
119
|
+
# but the resolver logic itself depends upon the loaded schema to know how to resolve.
|
|
120
|
+
# To work around the circular dependency, we build the schema with this lazy adapter,
|
|
121
|
+
# then build the resolver with the schema, and then the lazy resolver lazily loads the resolver.
|
|
122
|
+
LazyResolverAdapter = Struct.new(:builder) do
|
|
123
|
+
def resolver
|
|
124
|
+
@resolver ||= builder.call
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
extend Forwardable
|
|
128
|
+
def_delegators :resolver, :call, :resolve_type, :coerce_input, :coerce_result
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def lookup_type_by_name(type_name)
|
|
132
|
+
type_from(@graphql_schema.types.fetch(type_name))
|
|
133
|
+
rescue KeyError => e
|
|
134
|
+
msg = "No type named #{type_name} could be found"
|
|
135
|
+
msg += "; Possible alternatives: [#{e.corrections.join(", ").delete('"')}]." if e.corrections.any?
|
|
136
|
+
raise NotFoundError, msg
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def resolver
|
|
140
|
+
@resolver ||= @build_resolver.call(self)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def build_defined_types_array(graphql_schema)
|
|
144
|
+
graphql_schema
|
|
145
|
+
.types
|
|
146
|
+
.values
|
|
147
|
+
.reject { |t| BUILT_IN_TYPE_NAMES.include?(t.graphql_name) }
|
|
148
|
+
.map { |t| type_named(t.graphql_name) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def indexed_document_types_by_index_definition_name
|
|
152
|
+
@indexed_document_types_by_index_definition_name ||= indexed_document_types.each_with_object({}) do |type, hash|
|
|
153
|
+
type.index_definitions.each do |index_def|
|
|
154
|
+
if hash.key?(index_def.name)
|
|
155
|
+
raise SchemaError, "DatastoreCore::IndexDefinition #{index_def.name} is used multiple times: #{type} vs #{hash[index_def.name]}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
hash[index_def.name] = type
|
|
159
|
+
end
|
|
160
|
+
end.freeze
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,253 @@
|
|
|
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/datastore_core"
|
|
10
|
+
require "elastic_graph/graphql/config"
|
|
11
|
+
require "elastic_graph/support/from_yaml_file"
|
|
12
|
+
|
|
13
|
+
module ElasticGraph
|
|
14
|
+
# The main entry point for ElasticGraph GraphQL handling. Instantiate this to get access to the
|
|
15
|
+
# different parts of this library.
|
|
16
|
+
class GraphQL
|
|
17
|
+
extend Support::FromYamlFile
|
|
18
|
+
|
|
19
|
+
# @private
|
|
20
|
+
# @dynamic config, logger, runtime_metadata, graphql_schema_string, datastore_core, clock
|
|
21
|
+
attr_reader :config, :logger, :runtime_metadata, :graphql_schema_string, :datastore_core, :clock
|
|
22
|
+
|
|
23
|
+
# @private
|
|
24
|
+
# A factory method that builds a GraphQL instance from the given parsed YAML config.
|
|
25
|
+
# `from_yaml_file(file_name, &block)` is also available (via `Support::FromYamlFile`).
|
|
26
|
+
def self.from_parsed_yaml(parsed_yaml, &datastore_client_customization_block)
|
|
27
|
+
new(
|
|
28
|
+
config: GraphQL::Config.from_parsed_yaml(parsed_yaml),
|
|
29
|
+
datastore_core: DatastoreCore.from_parsed_yaml(parsed_yaml, for_context: :graphql, &datastore_client_customization_block)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @private
|
|
34
|
+
def initialize(
|
|
35
|
+
config:,
|
|
36
|
+
datastore_core:,
|
|
37
|
+
graphql_adapter: nil,
|
|
38
|
+
datastore_search_router: nil,
|
|
39
|
+
filter_interpreter: nil,
|
|
40
|
+
sub_aggregation_grouping_adapter: nil,
|
|
41
|
+
monotonic_clock: nil,
|
|
42
|
+
clock: ::Time
|
|
43
|
+
)
|
|
44
|
+
@config = config
|
|
45
|
+
@datastore_core = datastore_core
|
|
46
|
+
@graphql_adapter = graphql_adapter
|
|
47
|
+
@datastore_search_router = datastore_search_router
|
|
48
|
+
@filter_interpreter = filter_interpreter
|
|
49
|
+
@sub_aggregation_grouping_adapter = sub_aggregation_grouping_adapter
|
|
50
|
+
@monotonic_clock = monotonic_clock
|
|
51
|
+
@clock = clock
|
|
52
|
+
@logger = @datastore_core.logger
|
|
53
|
+
@runtime_metadata = @datastore_core.schema_artifacts.runtime_metadata
|
|
54
|
+
@graphql_schema_string = @datastore_core.schema_artifacts.graphql_schema_string
|
|
55
|
+
|
|
56
|
+
# Apply any extension modules that have been configured.
|
|
57
|
+
@config.extension_modules.each { |mod| extend mod }
|
|
58
|
+
@runtime_metadata.graphql_extension_modules.each { |ext_mod| extend ext_mod.extension_class }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @private
|
|
62
|
+
def graphql_http_endpoint
|
|
63
|
+
@graphql_http_endpoint ||= begin
|
|
64
|
+
require "elastic_graph/graphql/http_endpoint"
|
|
65
|
+
HTTPEndpoint.new(
|
|
66
|
+
query_executor: graphql_query_executor,
|
|
67
|
+
monotonic_clock: monotonic_clock,
|
|
68
|
+
client_resolver: config.client_resolver
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @private
|
|
74
|
+
def graphql_query_executor
|
|
75
|
+
@graphql_query_executor ||= begin
|
|
76
|
+
require "elastic_graph/graphql/query_executor"
|
|
77
|
+
QueryExecutor.new(
|
|
78
|
+
schema: schema,
|
|
79
|
+
monotonic_clock: monotonic_clock,
|
|
80
|
+
logger: logger,
|
|
81
|
+
slow_query_threshold_ms: @config.slow_query_latency_warning_threshold_in_ms,
|
|
82
|
+
datastore_search_router: datastore_search_router
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @private
|
|
88
|
+
def schema
|
|
89
|
+
@schema ||= begin
|
|
90
|
+
require "elastic_graph/graphql/schema"
|
|
91
|
+
|
|
92
|
+
Schema.new(
|
|
93
|
+
graphql_schema_string: graphql_schema_string,
|
|
94
|
+
config: config,
|
|
95
|
+
runtime_metadata: runtime_metadata,
|
|
96
|
+
index_definitions_by_graphql_type: @datastore_core.index_definitions_by_graphql_type,
|
|
97
|
+
graphql_gem_plugins: graphql_gem_plugins
|
|
98
|
+
) do |schema|
|
|
99
|
+
@graphql_adapter || begin
|
|
100
|
+
@schema = schema # assign this so that `#schema` returns the schema when `datastore_query_adapters` is called below
|
|
101
|
+
require "elastic_graph/graphql/resolvers/graphql_adapter"
|
|
102
|
+
Resolvers::GraphQLAdapter.new(
|
|
103
|
+
schema: schema,
|
|
104
|
+
datastore_query_builder: datastore_query_builder,
|
|
105
|
+
datastore_query_adapters: datastore_query_adapters,
|
|
106
|
+
runtime_metadata: runtime_metadata,
|
|
107
|
+
resolvers: graphql_resolvers
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @private
|
|
115
|
+
def datastore_search_router
|
|
116
|
+
@datastore_search_router ||= begin
|
|
117
|
+
require "elastic_graph/graphql/datastore_search_router"
|
|
118
|
+
DatastoreSearchRouter.new(
|
|
119
|
+
datastore_clients_by_name: @datastore_core.clients_by_name,
|
|
120
|
+
logger: logger,
|
|
121
|
+
monotonic_clock: monotonic_clock,
|
|
122
|
+
config: @config
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @private
|
|
128
|
+
def datastore_query_builder
|
|
129
|
+
@datastore_query_builder ||= begin
|
|
130
|
+
require "elastic_graph/graphql/datastore_query"
|
|
131
|
+
DatastoreQuery::Builder.with(
|
|
132
|
+
filter_interpreter: filter_interpreter,
|
|
133
|
+
runtime_metadata: runtime_metadata,
|
|
134
|
+
logger: logger,
|
|
135
|
+
default_page_size: @config.default_page_size,
|
|
136
|
+
max_page_size: @config.max_page_size
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @private
|
|
142
|
+
def graphql_gem_plugins
|
|
143
|
+
@graphql_gem_plugins ||= begin
|
|
144
|
+
require "graphql"
|
|
145
|
+
{::GraphQL::Dataloader => {}}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @private
|
|
150
|
+
def graphql_resolvers
|
|
151
|
+
@graphql_resolvers ||= begin
|
|
152
|
+
require "elastic_graph/graphql/resolvers/get_record_field_value"
|
|
153
|
+
require "elastic_graph/graphql/resolvers/list_records"
|
|
154
|
+
require "elastic_graph/graphql/resolvers/nested_relationships"
|
|
155
|
+
|
|
156
|
+
nested_relationships = Resolvers::NestedRelationships.new(
|
|
157
|
+
schema_element_names: runtime_metadata.schema_element_names,
|
|
158
|
+
logger: logger
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
list_records = Resolvers::ListRecords.new
|
|
162
|
+
|
|
163
|
+
get_record_field_value = Resolvers::GetRecordFieldValue.new(
|
|
164
|
+
schema_element_names: runtime_metadata.schema_element_names
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
[nested_relationships, list_records, get_record_field_value]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @private
|
|
172
|
+
def datastore_query_adapters
|
|
173
|
+
@datastore_query_adapters ||= begin
|
|
174
|
+
require "elastic_graph/graphql/aggregation/non_composite_grouping_adapter"
|
|
175
|
+
require "elastic_graph/graphql/aggregation/query_adapter"
|
|
176
|
+
require "elastic_graph/graphql/query_adapter/filters"
|
|
177
|
+
require "elastic_graph/graphql/query_adapter/pagination"
|
|
178
|
+
require "elastic_graph/graphql/query_adapter/sort"
|
|
179
|
+
require "elastic_graph/graphql/query_adapter/requested_fields"
|
|
180
|
+
|
|
181
|
+
schema_element_names = runtime_metadata.schema_element_names
|
|
182
|
+
|
|
183
|
+
[
|
|
184
|
+
GraphQL::QueryAdapter::Pagination.new(schema_element_names: schema_element_names),
|
|
185
|
+
GraphQL::QueryAdapter::Filters.new(schema_element_names: schema_element_names, filter_args_translator: filter_args_translator),
|
|
186
|
+
GraphQL::QueryAdapter::Sort.new(order_by_arg_name: schema_element_names.order_by),
|
|
187
|
+
Aggregation::QueryAdapter.new(
|
|
188
|
+
schema: schema,
|
|
189
|
+
config: config,
|
|
190
|
+
filter_args_translator: filter_args_translator,
|
|
191
|
+
runtime_metadata: runtime_metadata,
|
|
192
|
+
sub_aggregation_grouping_adapter: sub_aggregation_grouping_adapter
|
|
193
|
+
),
|
|
194
|
+
GraphQL::QueryAdapter::RequestedFields.new(schema)
|
|
195
|
+
]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @private
|
|
200
|
+
def filter_interpreter
|
|
201
|
+
@filter_interpreter ||= begin
|
|
202
|
+
require "elastic_graph/graphql/filtering/filter_interpreter"
|
|
203
|
+
Filtering::FilterInterpreter.new(runtime_metadata: runtime_metadata, logger: logger)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @private
|
|
208
|
+
def filter_args_translator
|
|
209
|
+
@filter_args_translator ||= begin
|
|
210
|
+
require "elastic_graph/graphql/filtering/filter_args_translator"
|
|
211
|
+
Filtering::FilterArgsTranslator.new(schema_element_names: runtime_metadata.schema_element_names)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @private
|
|
216
|
+
def sub_aggregation_grouping_adapter
|
|
217
|
+
@sub_aggregation_grouping_adapter ||= begin
|
|
218
|
+
require "elastic_graph/graphql/aggregation/non_composite_grouping_adapter"
|
|
219
|
+
Aggregation::NonCompositeGroupingAdapter
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @private
|
|
224
|
+
def monotonic_clock
|
|
225
|
+
@monotonic_clock ||= begin
|
|
226
|
+
require "elastic_graph/support/monotonic_clock"
|
|
227
|
+
Support::MonotonicClock.new
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# @private
|
|
232
|
+
# Loads dependencies eagerly. In some environments (such as in an AWS Lambda) this is desirable as we to load all dependencies
|
|
233
|
+
# at boot time instead of deferring dependency loading until we handle the first query. In other environments (such as tests),
|
|
234
|
+
# it's nice to load dependencies when needed.
|
|
235
|
+
def load_dependencies_eagerly
|
|
236
|
+
# run a simple GraphQL query to force load any dependencies needed to handle GraphQL queries
|
|
237
|
+
graphql_query_executor.execute(EAGER_LOAD_QUERY, client: Client::ELASTICGRAPH_INTERNAL)
|
|
238
|
+
graphql_http_endpoint # force load this too.
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
private
|
|
242
|
+
|
|
243
|
+
EAGER_LOAD_QUERY = <<~EOS.strip
|
|
244
|
+
query ElasticGraphEagerLoadBootQuery {
|
|
245
|
+
__schema {
|
|
246
|
+
types {
|
|
247
|
+
kind
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
EOS
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
updated_code_filename = "#{__dir__}/../../tmp/updated_valid_time_zones.rb"
|
|
6
|
+
|
|
7
|
+
# Note: CI does not appear to have java 14 or 17 available, so we use java 11 here.
|
|
8
|
+
java_time_zones = `java --source 11 #{__dir__}/dump_time_zones.java`.split("\n")
|
|
9
|
+
|
|
10
|
+
::File.write(updated_code_filename, <<~EOS)
|
|
11
|
+
# Copyright 2024 Block, Inc.
|
|
12
|
+
#
|
|
13
|
+
# Use of this source code is governed by an MIT-style
|
|
14
|
+
# license that can be found in the LICENSE file or at
|
|
15
|
+
# https://opensource.org/licenses/MIT.
|
|
16
|
+
#
|
|
17
|
+
# frozen_string_literal: true
|
|
18
|
+
|
|
19
|
+
module ElasticGraph
|
|
20
|
+
class GraphQL
|
|
21
|
+
module ScalarCoercionAdapters
|
|
22
|
+
# The set of all valid time zones. We expect this set to align with the official IANA
|
|
23
|
+
# list[^1][^2][^3], but ultimately we pass these time zones to the datastore and need
|
|
24
|
+
# to enumerate all time zones it supports. Since Elasticsearch and OpenSearch run on the JVM and the
|
|
25
|
+
# date format docs[^4] link to Java's `java.time.format.DateTimeFormatter` class, we
|
|
26
|
+
# can conclude that they support the time zones that the java class supports. This set
|
|
27
|
+
# is generated by `script/dump_time_zones`, which queries Java's `java.time.ZoneId`[^5]
|
|
28
|
+
# class to get the set of time zones supported on the JVM.
|
|
29
|
+
#
|
|
30
|
+
# DO NOT EDIT BY HAND.
|
|
31
|
+
#
|
|
32
|
+
# [^1]: https://www.iana.org/time-zones
|
|
33
|
+
# [^2]: https://en.wikipedia.org/wiki/Tz_database
|
|
34
|
+
# [^3]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
|
35
|
+
# [^4]: https://www.elastic.co/guide/en/elasticsearch/reference/7.10/mapping-date-format.html#custom-date-formats
|
|
36
|
+
# [^5]: https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html
|
|
37
|
+
VALID_TIME_ZONES = %w[
|
|
38
|
+
#{java_time_zones.join("\n ")}
|
|
39
|
+
].to_set
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
EOS
|
|
44
|
+
|
|
45
|
+
verify_code_command = %(ruby -r#{updated_code_filename} -e "puts ElasticGraph::GraphQL::ScalarCoercionAdapters::VALID_TIME_ZONES.size")
|
|
46
|
+
verify_code_output = `#{verify_code_command} 2>&1`
|
|
47
|
+
|
|
48
|
+
if verify_code_output.to_i < 600 # As of Nov 2022 there are 601 time zones.
|
|
49
|
+
abort <<~EOS.strip
|
|
50
|
+
It appears that the generated code is invalid. Check `#{updated_code_filename}` to see what was generated.
|
|
51
|
+
|
|
52
|
+
Output from `#{verify_code_command}`:
|
|
53
|
+
|
|
54
|
+
#{verify_code_output}
|
|
55
|
+
EOS
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if ARGV.include?("--print")
|
|
59
|
+
puts ::File.read(updated_code_filename)
|
|
60
|
+
else
|
|
61
|
+
filename = "#{__dir__}/../lib/elastic_graph/graphql/scalar_coercion_adapters/valid_time_zones.rb"
|
|
62
|
+
::FileUtils.cp(updated_code_filename, filename)
|
|
63
|
+
puts "Timezones have been written to `#{filename}`"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# if ARGV.include?("--verify")
|
|
67
|
+
# existing_contents = ::File.exist?(filename) ? ::File.read(filename) : ""
|
|
68
|
+
#
|
|
69
|
+
# if existing_contents == valid_timezones_ruby_code
|
|
70
|
+
# puts "`#{filename}` is up to date!"
|
|
71
|
+
# else
|
|
72
|
+
# diff = `git diff --no-index #{"--color" if $stdout.tty?} --binary #{updated_code_filename} #{filename}`
|
|
73
|
+
#
|
|
74
|
+
# abort <<~EOS.strip
|
|
75
|
+
# `#{filename}` is not up to date! Rerun `script/dump_time_zones` to correct.
|
|
76
|
+
#
|
|
77
|
+
# #{diff}
|
|
78
|
+
# EOS
|
|
79
|
+
# end
|
|
80
|
+
# else
|
|
81
|
+
# end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import java.time.ZoneId;
|
|
2
|
+
import java.util.Set;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Run this java file via `java --source 11 script/dump_time_zones.java`.
|
|
6
|
+
* `script/dump_time_zones` is a higher level wrapper that delegates to this
|
|
7
|
+
* and applies additional logic.
|
|
8
|
+
*/
|
|
9
|
+
public class DumpTimeZones {
|
|
10
|
+
public static void main(String[] args) {
|
|
11
|
+
Set<String> availableZones = ZoneId.getAvailableZoneIds();
|
|
12
|
+
|
|
13
|
+
availableZones.stream()
|
|
14
|
+
.sorted()
|
|
15
|
+
.forEach(it -> System.out.println(it));
|
|
16
|
+
}
|
|
17
|
+
}
|