elasticgraph-schema_definition 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 +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
|