elasticgraph-graphql 0.18.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|