elasticgraph-schema_definition 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 +7 -0
- data/elasticgraph-schema_definition.gemspec +26 -0
- data/lib/elastic_graph/schema_definition/api.rb +359 -0
- data/lib/elastic_graph/schema_definition/factory.rb +506 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/append_only_set.rb +79 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/field_initializer_support.rb +59 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/immutable_value.rb +99 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_fields/min_or_max_value.rb +62 -0
- data/lib/elastic_graph/schema_definition/indexing/derived_indexed_type.rb +346 -0
- data/lib/elastic_graph/schema_definition/indexing/event_envelope.rb +74 -0
- data/lib/elastic_graph/schema_definition/indexing/field.rb +181 -0
- data/lib/elastic_graph/schema_definition/indexing/field_reference.rb +51 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/enum.rb +65 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/object.rb +113 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/scalar.rb +51 -0
- data/lib/elastic_graph/schema_definition/indexing/field_type/union.rb +70 -0
- data/lib/elastic_graph/schema_definition/indexing/index.rb +318 -0
- data/lib/elastic_graph/schema_definition/indexing/json_schema_field_metadata.rb +34 -0
- data/lib/elastic_graph/schema_definition/indexing/json_schema_with_metadata.rb +234 -0
- data/lib/elastic_graph/schema_definition/indexing/list_counts_mapping.rb +53 -0
- data/lib/elastic_graph/schema_definition/indexing/relationship_resolver.rb +96 -0
- data/lib/elastic_graph/schema_definition/indexing/rollover_config.rb +25 -0
- data/lib/elastic_graph/schema_definition/indexing/update_target_factory.rb +54 -0
- data/lib/elastic_graph/schema_definition/indexing/update_target_resolver.rb +195 -0
- data/lib/elastic_graph/schema_definition/json_schema_pruner.rb +61 -0
- data/lib/elastic_graph/schema_definition/mixins/can_be_graphql_only.rb +31 -0
- data/lib/elastic_graph/schema_definition/mixins/has_derived_graphql_type_customizations.rb +119 -0
- data/lib/elastic_graph/schema_definition/mixins/has_directives.rb +65 -0
- data/lib/elastic_graph/schema_definition/mixins/has_documentation.rb +74 -0
- data/lib/elastic_graph/schema_definition/mixins/has_indices.rb +281 -0
- data/lib/elastic_graph/schema_definition/mixins/has_readable_to_s_and_inspect.rb +46 -0
- data/lib/elastic_graph/schema_definition/mixins/has_subtypes.rb +116 -0
- data/lib/elastic_graph/schema_definition/mixins/has_type_info.rb +181 -0
- data/lib/elastic_graph/schema_definition/mixins/implements_interfaces.rb +122 -0
- data/lib/elastic_graph/schema_definition/mixins/supports_default_value.rb +47 -0
- data/lib/elastic_graph/schema_definition/mixins/supports_filtering_and_aggregation.rb +267 -0
- data/lib/elastic_graph/schema_definition/mixins/verifies_graphql_name.rb +38 -0
- data/lib/elastic_graph/schema_definition/rake_tasks.rb +190 -0
- data/lib/elastic_graph/schema_definition/results.rb +404 -0
- data/lib/elastic_graph/schema_definition/schema_artifact_manager.rb +482 -0
- data/lib/elastic_graph/schema_definition/schema_elements/argument.rb +56 -0
- data/lib/elastic_graph/schema_definition/schema_elements/built_in_types.rb +1541 -0
- data/lib/elastic_graph/schema_definition/schema_elements/deprecated_element.rb +21 -0
- data/lib/elastic_graph/schema_definition/schema_elements/directive.rb +40 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +189 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enum_value.rb +73 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enum_value_namer.rb +89 -0
- data/lib/elastic_graph/schema_definition/schema_elements/enums_for_indexed_types.rb +82 -0
- data/lib/elastic_graph/schema_definition/schema_elements/field.rb +1085 -0
- data/lib/elastic_graph/schema_definition/schema_elements/field_path.rb +112 -0
- data/lib/elastic_graph/schema_definition/schema_elements/field_source.rb +16 -0
- data/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb +113 -0
- data/lib/elastic_graph/schema_definition/schema_elements/input_field.rb +31 -0
- data/lib/elastic_graph/schema_definition/schema_elements/input_type.rb +60 -0
- data/lib/elastic_graph/schema_definition/schema_elements/interface_type.rb +72 -0
- data/lib/elastic_graph/schema_definition/schema_elements/list_counts_state.rb +40 -0
- data/lib/elastic_graph/schema_definition/schema_elements/object_type.rb +53 -0
- data/lib/elastic_graph/schema_definition/schema_elements/relationship.rb +218 -0
- data/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb +310 -0
- data/lib/elastic_graph/schema_definition/schema_elements/sort_order_enum_value.rb +36 -0
- data/lib/elastic_graph/schema_definition/schema_elements/sub_aggregation_path.rb +66 -0
- data/lib/elastic_graph/schema_definition/schema_elements/type_namer.rb +237 -0
- data/lib/elastic_graph/schema_definition/schema_elements/type_reference.rb +353 -0
- data/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb +579 -0
- data/lib/elastic_graph/schema_definition/schema_elements/union_type.rb +157 -0
- data/lib/elastic_graph/schema_definition/scripting/file_system_repository.rb +77 -0
- data/lib/elastic_graph/schema_definition/scripting/script.rb +48 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/field/as_day_of_week.painless +24 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/field/as_time_of_day.painless +41 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/filter/by_time_of_day.painless +22 -0
- data/lib/elastic_graph/schema_definition/scripting/scripts/update/index_data.painless +93 -0
- data/lib/elastic_graph/schema_definition/state.rb +212 -0
- data/lib/elastic_graph/schema_definition/test_support.rb +113 -0
- metadata +513 -0
@@ -0,0 +1,96 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
module SchemaDefinition
|
11
|
+
module Indexing
|
12
|
+
# @private
|
13
|
+
class RelationshipResolver
|
14
|
+
def initialize(schema_def_state:, object_type:, relationship_name:, sourced_fields:, field_path_resolver:)
|
15
|
+
@schema_def_state = schema_def_state
|
16
|
+
@object_type = object_type
|
17
|
+
@relationship_name = relationship_name
|
18
|
+
@sourced_fields = sourced_fields
|
19
|
+
@field_path_resolver = field_path_resolver
|
20
|
+
end
|
21
|
+
|
22
|
+
def resolve
|
23
|
+
relation_field = object_type.graphql_fields_by_name[relationship_name]
|
24
|
+
|
25
|
+
if relation_field.nil?
|
26
|
+
[nil, "#{relationship_error_prefix} is not defined. Is it misspelled?"]
|
27
|
+
elsif (relationship = relation_field.relationship).nil?
|
28
|
+
[nil, "#{relationship_error_prefix} is not a relationship. It must be defined using `relates_to_one` or `relates_to_many`."]
|
29
|
+
elsif (related_type = schema_def_state.object_types_by_name[relationship.related_type.unwrap_non_null.name]).nil?
|
30
|
+
issue =
|
31
|
+
if schema_def_state.types_by_name.key?(relationship.related_type.fully_unwrapped.name)
|
32
|
+
"references a type which is not an object type: `#{relationship.related_type.name}`. Only object types can be used in relations."
|
33
|
+
else
|
34
|
+
"references an unknown type: `#{relationship.related_type.name}`. Is it misspelled?"
|
35
|
+
end
|
36
|
+
|
37
|
+
[nil, "#{relationship_error_prefix} #{issue}"]
|
38
|
+
elsif !related_type.indexed?
|
39
|
+
[nil, "#{relationship_error_prefix} references a type which is not indexed: `#{related_type.name}`. Only indexed types can be used in relations."]
|
40
|
+
else
|
41
|
+
relation_metadata = relation_field.runtime_metadata_graphql_field.relation # : SchemaArtifacts::RuntimeMetadata::Relation
|
42
|
+
foreign_key_parent_type = (relation_metadata.direction == :in) ? related_type : object_type
|
43
|
+
|
44
|
+
if (foreign_key_error = validate_foreign_key(foreign_key_parent_type, relation_metadata))
|
45
|
+
[nil, foreign_key_error]
|
46
|
+
else
|
47
|
+
[ResolvedRelationship.new(relationship_name, relation_field, relationship, related_type, relation_metadata), nil]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# @dynamic schema_def_state, object_type, relationship_name, sourced_fields, field_path_resolver
|
55
|
+
attr_reader :schema_def_state, :object_type, :relationship_name, :sourced_fields, :field_path_resolver
|
56
|
+
|
57
|
+
# Helper method for building the prefix of relationship-related error messages.
|
58
|
+
def relationship_error_prefix
|
59
|
+
sourced_fields_description =
|
60
|
+
if sourced_fields.empty?
|
61
|
+
""
|
62
|
+
else
|
63
|
+
" (referenced from `sourced_from` on field(s): #{sourced_fields.map { |f| "`#{f.name}`" }.join(", ")})"
|
64
|
+
end
|
65
|
+
|
66
|
+
"`#{relationship_description}`#{sourced_fields_description}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_foreign_key(foreign_key_parent_type, relation_metadata)
|
70
|
+
foreign_key_field = field_path_resolver.resolve_public_path(foreign_key_parent_type, relation_metadata.foreign_key) { true }
|
71
|
+
# If its an inbound foreign key, verify that the foreign key exists on the related type.
|
72
|
+
# Note: we don't verify this for outbound foreign keys, because when we define a relationship with an outbound foreign
|
73
|
+
# key, we automatically define an indexing only field for the foreign key (since it exists on the same type). We don't
|
74
|
+
# do that for an inbound foreign key, though (since the foreign key exists on another type). Allowing a relationship
|
75
|
+
# definition on type A to add a field to type B's schema would be weird and surprising.
|
76
|
+
if relation_metadata.direction == :in && foreign_key_field.nil?
|
77
|
+
"#{relationship_error_prefix} uses `#{foreign_key_parent_type.name}.#{relation_metadata.foreign_key}` as the foreign key, " \
|
78
|
+
"but that field does not exist as an indexing field. To continue, define it, define a relationship on `#{foreign_key_parent_type.name}` " \
|
79
|
+
"that uses it as the foreign key, use another field as the foreign key, or remove the `#{relationship_description}` definition."
|
80
|
+
elsif foreign_key_field && foreign_key_field.type.fully_unwrapped.name != "ID"
|
81
|
+
"#{relationship_error_prefix} uses `#{foreign_key_field.fully_qualified_path}` as the foreign key, " \
|
82
|
+
"but that field is not an `ID` field as expected. To continue, change it's type, use another field " \
|
83
|
+
"as the foreign key, or remove the `#{relationship_description}` definition."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def relationship_description
|
88
|
+
"#{object_type.name}.#{relationship_name}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @private
|
93
|
+
ResolvedRelationship = ::Data.define(:relationship_name, :relationship_field, :relationship, :related_type, :relation_metadata)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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/schema_artifacts/runtime_metadata/index_definition"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaDefinition
|
13
|
+
module Indexing
|
14
|
+
# @private
|
15
|
+
class RolloverConfig < ::Data.define(:frequency, :timestamp_field_path)
|
16
|
+
def runtime_metadata
|
17
|
+
SchemaArtifacts::RuntimeMetadata::IndexDefinition::Rollover.new(
|
18
|
+
frequency: frequency,
|
19
|
+
timestamp_field_path: timestamp_field_path.path_in_index
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,54 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
module SchemaDefinition
|
11
|
+
module Indexing
|
12
|
+
# Helper class that contains common logic for instantiating `UpdateTargets`.
|
13
|
+
# @private
|
14
|
+
module UpdateTargetFactory
|
15
|
+
def self.new_normal_indexing_update_target(
|
16
|
+
type:,
|
17
|
+
relationship:,
|
18
|
+
id_source:,
|
19
|
+
data_params:,
|
20
|
+
routing_value_source:,
|
21
|
+
rollover_timestamp_value_source:
|
22
|
+
)
|
23
|
+
SchemaArtifacts::RuntimeMetadata::UpdateTarget.new(
|
24
|
+
type: type,
|
25
|
+
relationship: relationship,
|
26
|
+
script_id: INDEX_DATA_UPDATE_SCRIPT_ID,
|
27
|
+
id_source: id_source,
|
28
|
+
metadata_params: standard_metadata_params.merge({
|
29
|
+
"relationship" => SchemaArtifacts::RuntimeMetadata::StaticParam.new(value: relationship)
|
30
|
+
}),
|
31
|
+
data_params: data_params,
|
32
|
+
routing_value_source: routing_value_source,
|
33
|
+
rollover_timestamp_value_source: rollover_timestamp_value_source
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
private_class_method def self.standard_metadata_params
|
38
|
+
@standard_metadata_params ||= {
|
39
|
+
"sourceId" => single_value_param_from("id"),
|
40
|
+
"sourceType" => single_value_param_from("type"),
|
41
|
+
"version" => single_value_param_from("version")
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method def self.single_value_param_from(source_path)
|
46
|
+
SchemaArtifacts::RuntimeMetadata::DynamicParam.new(
|
47
|
+
source_path: source_path,
|
48
|
+
cardinality: :one
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,195 @@
|
|
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/schema_artifacts/runtime_metadata/params"
|
10
|
+
require "elastic_graph/schema_definition/indexing/update_target_factory"
|
11
|
+
|
12
|
+
module ElasticGraph
|
13
|
+
module SchemaDefinition
|
14
|
+
module Indexing
|
15
|
+
# Responsible for resolving a relationship and a set of `sourced_from` fields into an `UpdateTarget`
|
16
|
+
# that contains the instructions for how the primary type should be updated from the related type's
|
17
|
+
# source events.
|
18
|
+
#
|
19
|
+
# @private
|
20
|
+
class UpdateTargetResolver
|
21
|
+
def initialize(
|
22
|
+
object_type:,
|
23
|
+
resolved_relationship:,
|
24
|
+
sourced_fields:,
|
25
|
+
field_path_resolver:
|
26
|
+
)
|
27
|
+
@object_type = object_type
|
28
|
+
@resolved_relationship = resolved_relationship
|
29
|
+
@sourced_fields = sourced_fields
|
30
|
+
@field_path_resolver = field_path_resolver
|
31
|
+
end
|
32
|
+
|
33
|
+
# Resolves the `object_type`, `resolved_relationship`, and `sourced_fields` into an `UpdateTarget`, validating
|
34
|
+
# that everything is defined correctly.
|
35
|
+
#
|
36
|
+
# Returns a tuple of the `update_target` (if valid), and a list of errors.
|
37
|
+
def resolve
|
38
|
+
relationship_errors = validate_relationship
|
39
|
+
data_params, data_params_errors = resolve_data_params
|
40
|
+
routing_value_source, routing_error = resolve_field_source(RoutingSourceAdapter)
|
41
|
+
rollover_timestamp_value_source, rollover_timestamp_error = resolve_field_source(RolloverTimestampSourceAdapter)
|
42
|
+
equivalent_field_errors = resolved_relationship.relationship.validate_equivalent_fields(field_path_resolver)
|
43
|
+
|
44
|
+
all_errors = relationship_errors + data_params_errors + equivalent_field_errors + [routing_error, rollover_timestamp_error].compact
|
45
|
+
|
46
|
+
if all_errors.empty?
|
47
|
+
update_target = UpdateTargetFactory.new_normal_indexing_update_target(
|
48
|
+
type: object_type.name,
|
49
|
+
relationship: resolved_relationship.relationship_name,
|
50
|
+
id_source: resolved_relationship.relation_metadata.foreign_key,
|
51
|
+
data_params: data_params,
|
52
|
+
routing_value_source: routing_value_source,
|
53
|
+
rollover_timestamp_value_source: rollover_timestamp_value_source
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
[update_target, all_errors]
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @dynamic object_type, resolved_relationship, sourced_fields, field_path_resolver
|
63
|
+
attr_reader :object_type, :resolved_relationship, :sourced_fields, :field_path_resolver
|
64
|
+
|
65
|
+
# Applies additional validations (beyond what `RelationshipResolver` applies) on relationships that are
|
66
|
+
# used by `sourced_from` fields.
|
67
|
+
def validate_relationship
|
68
|
+
errors = [] # : ::Array[::String]
|
69
|
+
|
70
|
+
if resolved_relationship.relationship.many?
|
71
|
+
errors << "#{relationship_error_prefix} is a `relates_to_many` relationship, but `sourced_from` is only supported on a `relates_to_one` relationship."
|
72
|
+
end
|
73
|
+
|
74
|
+
relation_metadata = resolved_relationship.relationship_field.runtime_metadata_graphql_field.relation # : SchemaArtifacts::RuntimeMetadata::Relation
|
75
|
+
if relation_metadata.direction == :out
|
76
|
+
errors << "#{relationship_error_prefix} has an outbound foreign key (`dir: :out`), but `sourced_from` is only supported via inbound foreign key (`dir: :in`) relationships."
|
77
|
+
end
|
78
|
+
|
79
|
+
unless relation_metadata.additional_filter.empty?
|
80
|
+
errors << "#{relationship_error_prefix} is a `relationship` using an `additional_filter` but `sourced_from` is not supported on relationships with `additional_filter`."
|
81
|
+
end
|
82
|
+
|
83
|
+
errors
|
84
|
+
end
|
85
|
+
|
86
|
+
# Helper method for building the prefix of relationship-related error messages.
|
87
|
+
def relationship_error_prefix
|
88
|
+
sourced_fields_description = "(referenced from `sourced_from` on field(s): #{sourced_fields.map { |f| "`#{f.name}`" }.join(", ")})"
|
89
|
+
"`#{object_type.name}.#{resolved_relationship.relationship_name}` #{sourced_fields_description}"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Resolves the `sourced_fields` into a data params map, validating them along the way.
|
93
|
+
#
|
94
|
+
# Returns a tuple of the data params and a list of any errors that occurred during resolution.
|
95
|
+
def resolve_data_params
|
96
|
+
related_type = resolved_relationship.related_type
|
97
|
+
errors = [] # : ::Array[::String]
|
98
|
+
|
99
|
+
data_params = sourced_fields.filter_map do |field|
|
100
|
+
field_source = field.source # : SchemaElements::FieldSource
|
101
|
+
|
102
|
+
referenced_field_path = field_path_resolver.resolve_public_path(related_type, field_source.field_path) do |parent_field|
|
103
|
+
!parent_field.type.list?
|
104
|
+
end
|
105
|
+
|
106
|
+
if referenced_field_path.nil?
|
107
|
+
explanation =
|
108
|
+
if field_source.field_path.include?(".")
|
109
|
+
"could not be resolved: some parts do not exist on their respective types as non-list fields"
|
110
|
+
else
|
111
|
+
"does not exist as an indexing field"
|
112
|
+
end
|
113
|
+
|
114
|
+
errors << "`#{object_type.name}.#{field.name}` has an invalid `sourced_from` argument: `#{related_type.name}.#{field_source.field_path}` #{explanation}."
|
115
|
+
nil
|
116
|
+
elsif referenced_field_path.type.unwrap_non_null != field.type.unwrap_non_null
|
117
|
+
errors << "The type of `#{object_type.name}.#{field.name}` is `#{field.type}`, but the type of it's source (`#{related_type.name}.#{field_source.field_path}`) is `#{referenced_field_path.type}`. These must agree to use `sourced_from`."
|
118
|
+
nil
|
119
|
+
elsif field.type.non_null?
|
120
|
+
errors << "The type of `#{object_type.name}.#{field.name}` (`#{field.type}`) is not nullable, but this is not allowed for `sourced_from` fields since the value will be `null` before the related type's event is ingested."
|
121
|
+
nil
|
122
|
+
else
|
123
|
+
param = SchemaArtifacts::RuntimeMetadata::DynamicParam.new(
|
124
|
+
source_path: referenced_field_path.path_in_index,
|
125
|
+
cardinality: :one
|
126
|
+
)
|
127
|
+
|
128
|
+
[field.name_in_index, param]
|
129
|
+
end
|
130
|
+
end.to_h
|
131
|
+
|
132
|
+
[data_params, errors]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Helper method that assists with resolving `routing_value_source` and `rollover_timestamp_value_source`.
|
136
|
+
# Uses an `adapter` for the differences in these two cases.
|
137
|
+
#
|
138
|
+
# Returns a tuple of the resolved source (if successful) and an error (if invalid).
|
139
|
+
def resolve_field_source(adapter)
|
140
|
+
# For now we only support one index (so we can use the first index) but someday we may need to support multiple.
|
141
|
+
index = object_type.indices.first # : Index
|
142
|
+
|
143
|
+
field_source_graphql_path_string = adapter.get_field_source(resolved_relationship.relationship, index) do |local_need|
|
144
|
+
relationship_name = resolved_relationship.relationship_name
|
145
|
+
|
146
|
+
error = "Cannot update `#{object_type.name}` documents with data from related `#{relationship_name}` events, " \
|
147
|
+
"because #{adapter.cannot_update_reason(object_type, relationship_name)}. To fix it, add a call like this to the " \
|
148
|
+
"`#{object_type.name}.#{relationship_name}` relationship definition: `rel.equivalent_field " \
|
149
|
+
"\"[#{resolved_relationship.related_type.name} field]\", locally_named: \"#{local_need}\"`."
|
150
|
+
|
151
|
+
return [nil, error]
|
152
|
+
end
|
153
|
+
|
154
|
+
if field_source_graphql_path_string
|
155
|
+
field_path = field_path_resolver.resolve_public_path(resolved_relationship.related_type, field_source_graphql_path_string) do |parent_field|
|
156
|
+
!parent_field.type.list?
|
157
|
+
end
|
158
|
+
|
159
|
+
[field_path&.path_in_index, nil]
|
160
|
+
else
|
161
|
+
[nil, nil]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Adapter for the `routing_value_source` case for use by `resolve_field_source`.
|
166
|
+
#
|
167
|
+
# @private
|
168
|
+
module RoutingSourceAdapter
|
169
|
+
def self.get_field_source(relationship, index, &block)
|
170
|
+
relationship.routing_value_source_for_index(index, &block)
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.cannot_update_reason(object_type, relationship_name)
|
174
|
+
"`#{object_type.name}` uses custom shard routing but we don't know what `#{relationship_name}` field to use " \
|
175
|
+
"to route the `#{object_type.name}` update requests"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Adapter for the `rollover_timestamp_value_source` case for use by `resolve_field_source`.
|
180
|
+
#
|
181
|
+
# @private
|
182
|
+
module RolloverTimestampSourceAdapter
|
183
|
+
def self.get_field_source(relationship, index, &block)
|
184
|
+
relationship.rollover_timestamp_value_source_for_index(index, &block)
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.cannot_update_reason(object_type, relationship_name)
|
188
|
+
"`#{object_type.name}` uses a rollover index but we don't know what `#{relationship_name}` timestamp field to use " \
|
189
|
+
"to select an index for the `#{object_type.name}` update requests"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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/constants"
|
10
|
+
|
11
|
+
module ElasticGraph
|
12
|
+
module SchemaDefinition
|
13
|
+
# Prunes unused type definitions from a given JSON schema.
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
class JSONSchemaPruner
|
17
|
+
def self.prune(original_json_schema)
|
18
|
+
initial_type_names = [EVENT_ENVELOPE_JSON_SCHEMA_NAME] + original_json_schema
|
19
|
+
.dig("$defs", EVENT_ENVELOPE_JSON_SCHEMA_NAME, "properties", "type", "enum")
|
20
|
+
|
21
|
+
types_to_keep = referenced_type_names(initial_type_names, original_json_schema["$defs"])
|
22
|
+
|
23
|
+
# The .select will preserve the sort order of the original hash
|
24
|
+
pruned_defs = original_json_schema["$defs"].select { |k, _v| types_to_keep.include?(k) }
|
25
|
+
|
26
|
+
original_json_schema.merge("$defs" => pruned_defs)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a list of type names indicating all types referenced from any type in source_type_names.
|
30
|
+
private_class_method
|
31
|
+
def self.referenced_type_names(source_type_names, original_defs)
|
32
|
+
return Set.new if source_type_names.empty?
|
33
|
+
|
34
|
+
referenced_type_defs = original_defs.select { |k, _| source_type_names.include?(k) }
|
35
|
+
ref_names = collect_ref_names(referenced_type_defs)
|
36
|
+
|
37
|
+
referenced_type_names(ref_names, original_defs) + source_type_names
|
38
|
+
end
|
39
|
+
|
40
|
+
private_class_method
|
41
|
+
def self.collect_ref_names(hash)
|
42
|
+
hash.flat_map do |key, value|
|
43
|
+
case value
|
44
|
+
when ::Hash
|
45
|
+
collect_ref_names(value)
|
46
|
+
when ::Array
|
47
|
+
value.grep(::Hash).flat_map { |subhash| collect_ref_names(subhash) }
|
48
|
+
when ::String
|
49
|
+
if key == "$ref" && (type = value[%r{\A#/\$defs/(.+)\z}, 1])
|
50
|
+
[type]
|
51
|
+
else
|
52
|
+
[]
|
53
|
+
end
|
54
|
+
else
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
module SchemaDefinition
|
11
|
+
# Namespace for modules that are used as mixins. Mixins are used to offer a consistent API for
|
12
|
+
# schema definition features that apply to multiple types of schema elements.
|
13
|
+
module Mixins
|
14
|
+
# Used to indicate if a type only exists in the GraphQL schema (e.g. it has no indexing component).
|
15
|
+
module CanBeGraphQLOnly
|
16
|
+
# Sets whether or not this type only exists in the GraphQL schema
|
17
|
+
#
|
18
|
+
# @param value [Boolean] whether or not this type only exists in the GraphQL schema
|
19
|
+
# @return [void]
|
20
|
+
def graphql_only(value)
|
21
|
+
@graphql_only = value
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Boolean] whether or not this type only exists in the GraphQL schema
|
25
|
+
def graphql_only?
|
26
|
+
!!@graphql_only
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,119 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
module SchemaDefinition
|
11
|
+
module Mixins
|
12
|
+
# Mixin that supports the customization of derived GraphQL types.
|
13
|
+
#
|
14
|
+
# For each type you define, ElasticGraph generates a number of derived GraphQL types that are needed to facilitate the ElasticGraph
|
15
|
+
# Query API. Methods in this module can be used to customize those derived GraphQL types.
|
16
|
+
module HasDerivedGraphQLTypeCustomizations
|
17
|
+
# Registers a customization block for the named derived graphql types. The provided block will get run on the named derived GraphQL
|
18
|
+
# types, allowing them to be customized.
|
19
|
+
#
|
20
|
+
# @param type_names [Array<String, :all>] names of the derived types to customize, or `:all` to customize all derived types
|
21
|
+
# @return [void]
|
22
|
+
#
|
23
|
+
# @example Customize named derived GraphQL types
|
24
|
+
# ElasticGraph.define_schema do |schema|
|
25
|
+
# schema.object_type "Campaign" do |t|
|
26
|
+
# t.field "id", "ID!"
|
27
|
+
# t.index "campaigns"
|
28
|
+
#
|
29
|
+
# t.customize_derived_types "CampaignFilterInput", "CampaignSortOrderInput" do |dt|
|
30
|
+
# # Add a `@deprecated` directive to two specific derived types.
|
31
|
+
# dt.directive "deprecated"
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @example Customize all derived GraphQL types
|
37
|
+
# ElasticGraph.define_schema do |schema|
|
38
|
+
# schema.object_type "Campaign" do |t|
|
39
|
+
# t.field "id", "ID!"
|
40
|
+
# t.index "campaigns"
|
41
|
+
#
|
42
|
+
# t.customize_derived_types :all do |dt|
|
43
|
+
# # Add a `@deprecated` directive to all derived types.
|
44
|
+
# dt.directive "deprecated"
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
def customize_derived_types(*type_names, &customization_block)
|
49
|
+
if type_names.include?(:all)
|
50
|
+
derived_type_customizations_for_all_types << customization_block
|
51
|
+
else
|
52
|
+
type_names.each do |t|
|
53
|
+
derived_type_customizations_by_name[t.to_s] << customization_block
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Registers a customization block for the named fields on the named derived GraphQL type. The provided block will get run on the
|
59
|
+
# named fields of the named derived GraphQL type, allowing them to be customized.
|
60
|
+
#
|
61
|
+
# @param type_name [String] name of the derived type containing fields you want to customize
|
62
|
+
# @param field_names [Array<String>] names of the fields on the derived types that you wish to customize
|
63
|
+
# @return [void]
|
64
|
+
#
|
65
|
+
# @example Customize named fields of a derived GraphQL type
|
66
|
+
# ElasticGraph.define_schema do |schema|
|
67
|
+
# schema.object_type "Campaign" do |t|
|
68
|
+
# t.field "id", "ID!"
|
69
|
+
# t.index "campaigns"
|
70
|
+
#
|
71
|
+
# t.customize_derived_type_fields "CampaignConnection", "pageInfo", "totalEdgeCount" do |dt|
|
72
|
+
# # Add a `@deprecated` directive to `CampaignConnection.pageInfo` and `CampaignConnection.totalEdgeCount`.
|
73
|
+
# dt.directive "deprecated"
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
def customize_derived_type_fields(type_name, *field_names, &customization_block)
|
78
|
+
customizations_by_field = derived_field_customizations_by_type_and_field_name[type_name]
|
79
|
+
|
80
|
+
field_names.each do |field_name|
|
81
|
+
customizations_by_field[field_name] << customization_block
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @private
|
86
|
+
def derived_type_customizations_for_type(type)
|
87
|
+
derived_type_customizations_by_name[type.name] + derived_type_customizations_for_all_types
|
88
|
+
end
|
89
|
+
|
90
|
+
# @private
|
91
|
+
def derived_field_customizations_by_name_for_type(type)
|
92
|
+
derived_field_customizations_by_type_and_field_name[type.name]
|
93
|
+
end
|
94
|
+
|
95
|
+
# @private
|
96
|
+
def derived_type_customizations_by_name
|
97
|
+
@derived_type_customizations_by_name ||= ::Hash.new do |hash, type_name|
|
98
|
+
hash[type_name] = []
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @private
|
103
|
+
def derived_field_customizations_by_type_and_field_name
|
104
|
+
@derived_field_customizations_by_type_and_field_name ||= ::Hash.new do |outer_hash, type|
|
105
|
+
outer_hash[type] = ::Hash.new do |inner_hash, field_name|
|
106
|
+
inner_hash[field_name] = []
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def derived_type_customizations_for_all_types
|
114
|
+
@derived_type_customizations_for_all_types ||= []
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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
|
+
module ElasticGraph
|
10
|
+
module SchemaDefinition
|
11
|
+
module Mixins
|
12
|
+
# Provides support for annotating any schema element with a GraphQL directive.
|
13
|
+
module HasDirectives
|
14
|
+
# Adds a GraphQL directive to the current schema element.
|
15
|
+
#
|
16
|
+
# @note If you’re using a custom directive rather than a standard GraphQL directive like `@deprecated`, you’ll also need to use
|
17
|
+
# {API#raw_sdl} to define the custom directive.
|
18
|
+
#
|
19
|
+
# @param name [String] name of the directive
|
20
|
+
# @param arguments [Hash<String, Object>] arguments for the directive
|
21
|
+
# @return [void]
|
22
|
+
#
|
23
|
+
# @example Add a standard GraphQL directive to a field
|
24
|
+
# ElasticGraph.define_schema do |schema|
|
25
|
+
# schema.object_type "Campaign" do |t|
|
26
|
+
# t.field "id", "ID" do |f|
|
27
|
+
# f.directive "deprecated"
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @example Define a custom GraphQL directive and add it to an object type
|
33
|
+
# ElasticGraph.define_schema do |schema|
|
34
|
+
# # Define a directive we can use to annotate what system a data type comes from.
|
35
|
+
# schema.raw_sdl "directive @sourcedFrom(system: String!) on OBJECT"
|
36
|
+
#
|
37
|
+
# schema.object_type "Campaign" do |t|
|
38
|
+
# t.field "id", "ID"
|
39
|
+
# t.directive "sourcedFrom", system: "campaigns"
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
def directive(name, arguments = {})
|
43
|
+
directives << schema_def_state.factory.new_directive(name, arguments)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Helper method designed for use by including classes to get the formatted directive SDL.
|
47
|
+
#
|
48
|
+
# @param suffix_with [String] suffix to add on the end of the SDL
|
49
|
+
# @param prefix_with [String] prefix to add to the beginning of the SDL
|
50
|
+
# @return [String] SDL string for the directives
|
51
|
+
# @api private
|
52
|
+
def directives_sdl(suffix_with: "", prefix_with: "")
|
53
|
+
sdl = directives.map(&:to_sdl).join(" ")
|
54
|
+
return sdl if sdl.empty?
|
55
|
+
prefix_with + sdl + suffix_with
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Array<SchemaElements::Directive>] directives attached to this schema element
|
59
|
+
def directives
|
60
|
+
@directives ||= []
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|