Skip to content

Commit 2e925b5

Browse files
Allow indexing_only relationships to reference non-indexed source types
1 parent 4ed32e4 commit 2e925b5

3 files changed

Lines changed: 62 additions & 6 deletions

File tree

elasticgraph-schema_definition/lib/elastic_graph/schema_definition/indexing/relationship_resolver.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ def resolve
3636
end
3737

3838
[nil, "#{relationship_error_prefix} #{issue}"]
39-
elsif !related_type.root_document_type?
40-
[nil, "#{relationship_error_prefix} references a type which is not a root document type: `#{related_type.name}`. Only root document types can be used in relations."]
39+
elsif !related_type.root_document_type? && !relationship.indexing_only
40+
[nil, "#{relationship_error_prefix} references a type which is not a root document type: `#{related_type.name}`. " \
41+
"Only root document types can be used in relations exposed to GraphQL. To relate to a non-indexed source type " \
42+
"purely for indexing purposes (e.g. `sourced_from`), declare the relationship with `indexing_only: true`."]
43+
elsif (id_field_error = validate_non_indexed_related_type_id_field(related_type))
44+
[nil, id_field_error]
4145
else
4246
relation_metadata = relationship.runtime_metadata # : SchemaArtifacts::RuntimeMetadata::Relation
4347
foreign_key_parent_type = (relation_metadata.direction == :in) ? related_type : object_type
@@ -67,6 +71,18 @@ def relationship_error_prefix
6771
"`#{relationship_description}`#{sourced_fields_description}"
6872
end
6973

74+
# A non-indexed related type never goes through the `id` validation that `t.index` performs on indexed
75+
# types, but relationships join on `id`, so we must verify it here.
76+
def validate_non_indexed_related_type_id_field(related_type)
77+
return nil if related_type.root_document_type?
78+
79+
id_field = schema_def_state.field_path_resolver.resolve_public_path(related_type, "id") { true }
80+
return nil if id_field
81+
82+
"#{relationship_error_prefix} references `#{related_type.name}`, which lacks an `id` field. Related types " \
83+
"must define an `id` field since relationships join on it."
84+
end
85+
7086
def validate_foreign_key(foreign_key_parent_type, relation_metadata)
7187
foreign_key_field = schema_def_state.field_path_resolver.resolve_public_path(foreign_key_parent_type, relation_metadata.foreign_key) { true }
7288
# If it's an inbound foreign key, verify that the foreign key exists on the related type.

elasticgraph-schema_definition/sig/elastic_graph/schema_definition/indexing/relationship_resolver.rbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module ElasticGraph
1919
attr_reader sourced_fields: ::Array[SchemaElements::Field]
2020

2121
def relationship_error_prefix: () -> ::String
22+
def validate_non_indexed_related_type_id_field: (indexableType) -> ::String?
2223
def validate_foreign_key: (
2324
indexableType,
2425
SchemaArtifacts::RuntimeMetadata::Relation

elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/runtime_metadata/object_types_by_name/update_targets_spec.rb

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ def expect_widget_update_target_with(
732732
)
733733
end
734734

735-
it "raises an error if the related type exists but is a non-indexed object type, regardless of whether there are any `sourced_from` fields or not" do
735+
it "raises an error if the related type exists but is a non-indexed object type on a GraphQL-exposed relationship, regardless of whether there are any `sourced_from` fields or not" do
736736
expect {
737737
update_targets_for("Widget", index_widget_workspaces: false) do |t|
738738
t.relates_to_one "workspace", "WidgetWorkspace", via: "widget_ids", dir: :in
@@ -746,7 +746,7 @@ def expect_widget_update_target_with(
746746
end
747747
end
748748
}.to raise_error_about_workspace_relationship(
749-
"references a type which is not a root document type: `WidgetWorkspace`. Only root document types can be used in relations."
749+
"references a type which is not a root document type: `WidgetWorkspace`. Only root document types can be used in relations exposed to GraphQL."
750750
)
751751

752752
expect {
@@ -755,11 +755,49 @@ def expect_widget_update_target_with(
755755
expect(t.fields_with_sources).to be_empty
756756
end
757757
}.to raise_error_about_workspace_relationship(
758-
"references a type which is not a root document type: `WidgetWorkspace`. Only root document types can be used in relations.",
758+
"references a type which is not a root document type: `WidgetWorkspace`. Only root document types can be used in relations exposed to GraphQL.",
759759
sourced_fields: false
760760
)
761761
end
762762

763+
it "allows an `indexing_only: true` relationship to reference a non-indexed object type, so `sourced_from` source types need not be indexed" do
764+
update_targets = update_targets_for("WidgetWorkspace", index_widget_workspaces: false) do |t|
765+
t.relates_to_one "workspace", "WidgetWorkspace", via: "widget_ids", dir: :in, indexing_only: true
766+
767+
t.field "workspace_name", "String" do |f|
768+
f.sourced_from "workspace", "name"
769+
end
770+
end
771+
772+
expect_widget_update_target_with(
773+
update_targets,
774+
id_source: "widget_ids",
775+
routing_value_source: nil,
776+
relationship: "workspace",
777+
top_level_fields_params: {
778+
"workspace_name" => dynamic_param_with(source_path: "name", cardinality: :one)
779+
}
780+
)
781+
end
782+
783+
it "raises an error if a non-indexed related type lacks an `id` field, since relationships join on `id` and non-indexed types skip the `id` validation `t.index` performs" do
784+
expect {
785+
update_targets_for("Widget", index_widget_workspaces: false, define_widget_workspace_id: false) do |t|
786+
t.relates_to_one "workspace", "WidgetWorkspace", via: "widget_ids", dir: :in, indexing_only: true
787+
788+
t.field "workspace_name", "String" do |f|
789+
f.sourced_from "workspace", "name"
790+
end
791+
792+
t.field "workspace_created_at", "DateTime" do |f|
793+
f.sourced_from "workspace", "created_at"
794+
end
795+
end
796+
}.to raise_error_about_workspace_relationship(
797+
"references `WidgetWorkspace`, which lacks an `id` field. Related types must define an `id` field since relationships join on it."
798+
)
799+
end
800+
763801
it "raises an error if an inbound foreign key field does not exist on the related type, regardless of whether there are any `sourced_from` fields or not" do
764802
expect {
765803
update_targets_for("Widget") do |t|
@@ -2039,6 +2077,7 @@ def update_targets_for(
20392077
on_widgets_index: nil,
20402078
on_widget_workspace_type: nil,
20412079
index_widget_workspaces: true,
2080+
define_widget_workspace_id: true,
20422081
type_name_overrides: {},
20432082
&define_relation_and_sourced_from_fields
20442083
)
@@ -2086,7 +2125,7 @@ def update_targets_for(
20862125
end
20872126

20882127
s.object_type "WidgetWorkspace" do |t|
2089-
t.field "id", "ID!"
2128+
t.field "id", "ID!" if define_widget_workspace_id
20902129
t.field "workspace_owner_id", "ID!"
20912130
t.field "name", widget_workspace_name, **widget_workspace_name_opts
20922131
t.field "created_at", widget_workspace_created_at

0 commit comments

Comments
 (0)