Skip to content

Commit fd1d61a

Browse files
committed
Rename fetchable field option to returnable
1 parent 07364a8 commit fd1d61a

File tree

6 files changed

+118
-49
lines changed

6 files changed

+118
-49
lines changed

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,11 @@ def mappings
327327
hash["_routing"] = {"required" => true} if uses_custom_routing?
328328
hash["_size"] = {"enabled" => true} if schema_def_state.index_document_sizes?
329329

330-
# Exclude non-fetchable fields from `_source` to save storage. These fields are still
330+
# Exclude non-returnable fields from `_source` to save storage. These fields are still
331331
# indexed (in the inverted index and/or doc_values) for filtering, sorting, and aggregation,
332332
# but their values are not stored in the compressed `_source` blob.
333-
if indexed_type.respond_to?(:non_fetchable_field_paths)
334-
source_excludes = indexed_type.non_fetchable_field_paths
335-
hash["_source"] = {"excludes" => source_excludes} if source_excludes.any?
336-
end
333+
source_excludes = indexed_type.source_excludes_paths
334+
hash["_source"] = {"excludes" => source_excludes} if source_excludes.any?
337335
end
338336
end
339337

elasticgraph-schema_definition/lib/elastic_graph/schema_definition/mixins/has_indices.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,28 @@ def fields_with_sources
292292
indexing_fields_by_name_in_index.values.reject { |f| f.source.nil? }
293293
end
294294

295+
# Returns the list of `_source.excludes` paths for non-returnable fields.
296+
#
297+
# Uses `indexing_fields_by_name_in_index` for traversal (same as
298+
# `index_field_runtime_metadata_tuples`) to avoid infinite recursion
299+
# through interface/union subtype cycles.
300+
#
301+
# @private
302+
def source_excludes_paths(path_prefix: "")
303+
indexing_fields_by_name_in_index.flat_map do |name, field|
304+
path = path_prefix + name
305+
object_type = field.type.fully_unwrapped.as_object_type
306+
307+
if !field.returnable?
308+
[object_type ? "#{path}.*" : path]
309+
elsif object_type
310+
object_type.source_excludes_paths(path_prefix: "#{path}.")
311+
else
312+
[]
313+
end
314+
end
315+
end
316+
295317
private
296318

297319
def initialize_has_indices

elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/field.rb

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ module SchemaElements
7373
# @private
7474
# @!attribute [rw] highlightable
7575
# @private
76+
# @!attribute [rw] returnable
77+
# @private
7678
# @!attribute [rw] source
7779
# @private
7880
# @!attribute [rw] runtime_field_script
@@ -91,7 +93,7 @@ class Field < Struct.new(
9193
:name, :original_type, :parent_type, :original_type_for_derived_types, :schema_def_state, :accuracy_confidence,
9294
:filter_customizations, :grouped_by_customizations, :highlights_customizations, :sub_aggregations_customizations,
9395
:aggregated_values_customizations, :sort_order_enum_value_customizations, :args,
94-
:sortable, :filterable, :aggregatable, :groupable, :highlightable, :fetchable,
96+
:sortable, :filterable, :aggregatable, :groupable, :highlightable, :returnable,
9597
:graphql_only, :source, :runtime_field_script, :relationship, :singular_name,
9698
:computation_detail, :non_nullable_in_json_schema, :as_input,
9799
:name_in_index, :resolver
@@ -106,7 +108,7 @@ def initialize(
106108
name:, type:, parent_type:, schema_def_state:,
107109
accuracy_confidence: :high, name_in_index: name,
108110
type_for_derived_types: nil, graphql_only: nil, singular: nil,
109-
sortable: nil, filterable: nil, aggregatable: nil, groupable: nil, highlightable: nil, fetchable: nil,
111+
sortable: nil, filterable: nil, aggregatable: nil, groupable: nil, highlightable: nil, returnable: nil,
110112
as_input: false, resolver: nil
111113
)
112114
type_ref = schema_def_state.type_ref(type)
@@ -129,7 +131,7 @@ def initialize(
129131
aggregatable: aggregatable,
130132
groupable: groupable,
131133
highlightable: highlightable,
132-
fetchable: fetchable,
134+
returnable: returnable,
133135
graphql_only: graphql_only,
134136
source: nil,
135137
runtime_field_script: nil,
@@ -744,14 +746,14 @@ def highlightable?
744746
type_for_derived_types.fully_unwrapped.as_object_type&.supports?(&:highlightable?)
745747
end
746748

747-
# Indicates if this field is fetchable in GraphQL query responses. When `false`, the field will
749+
# Indicates if this field is returnable in GraphQL query responses. When `false`, the field will
748750
# still be available for filtering, sorting, grouping, and aggregation, but will not appear in the
749751
# GraphQL output type and its data will be excluded from `_source` in the datastore for storage savings.
750752
#
751-
# @return [Boolean] true if this field's data can be fetched (default: true)
752-
def fetchable?
753-
return true if fetchable.nil?
754-
fetchable
753+
# @return [Boolean] true if this field's data can be returned (default: true)
754+
def returnable?
755+
return true if returnable.nil?
756+
returnable
755757
end
756758

757759
# Defines an argument on the field.
@@ -905,8 +907,8 @@ def to_filter_field(parent_type:, for_single_value: !type_for_derived_types.list
905907
type_for_derived_types: nil,
906908
resolver: nil,
907909
# Filter fields should always appear in their parent input type's SDL regardless
908-
# of the source field's fetchability.
909-
fetchable: nil
910+
# of the source field's returnability.
911+
returnable: true
910912
)
911913

912914
schema_def_state.factory.new_field(**params).tap do |f|

elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def name
137137
# ElasticGraph will infer field sortability based on the field's GraphQL type and mapping type.
138138
# @option options [Boolean] highlightable force-enables or disables the ability to request search highlights for this field. When
139139
# not provided, ElasticGraph will infer field highlightable based on the field's mapping type.
140-
# @option options [Boolean] fetchable when set to `false`, the field will not appear in the GraphQL output type and its data
140+
# @option options [Boolean] returnable when set to `false`, the field will not appear in the GraphQL output type and its data
141141
# will be excluded from `_source` in the datastore for storage savings. The field will still be available for filtering,
142142
# sorting, grouping, and aggregation. Defaults to `true`.
143143
# @yield [Field] the field for further customization
@@ -485,27 +485,6 @@ def to_indexing_field_type
485485
)
486486
end
487487

488-
# Returns the list of field paths (in dotted notation) for fields that have `fetchable: false`.
489-
# These paths are used to populate `_source.excludes` in the datastore mapping so that
490-
# non-fetchable field data is not stored in `_source`, saving storage space.
491-
#
492-
# Uses `indexing_fields_by_name_in_index` for traversal (same as `index_field_runtime_metadata_tuples`)
493-
# to avoid infinite recursion through interface/union subtype cycles.
494-
#
495-
# @private
496-
def non_fetchable_field_paths(path_prefix: "")
497-
indexing_fields_by_name_in_index.flat_map do |name, field|
498-
path = path_prefix + name
499-
if !field.fetchable?
500-
[path]
501-
elsif (object_type = field.type.fully_unwrapped.as_object_type) && object_type.respond_to?(:non_fetchable_field_paths)
502-
object_type.non_fetchable_field_paths(path_prefix: "#{path}.")
503-
else
504-
[]
505-
end
506-
end
507-
end
508-
509488
# @private
510489
def current_sources
511490
indexing_fields_by_name_in_index.values.flat_map do |field|
@@ -555,7 +534,7 @@ def index_field_runtime_metadata_tuples(
555534

556535
def fields_sdl(&arg_selector)
557536
graphql_fields_by_name.values
558-
.select(&:fetchable?)
537+
.select(&:returnable?)
559538
.map { |f| f.to_sdl(&arg_selector) }
560539
.flat_map { |sdl| sdl.split("\n") }
561540
.join("\n ")

elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/datastore_config/index_mappings/miscellaneous_spec.rb

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,22 +117,90 @@ module SchemaDefinition
117117
})
118118
end
119119

120-
it "includes `_source.excludes` for `fetchable: false` fields" do
120+
it "adds `name_in_index` to `_source.excludes` for `returnable: false` fields" do
121121
mapping = index_mapping_for "my_type" do |s|
122122
s.object_type "MyType" do |t|
123123
t.field "id", "ID"
124124
t.field "name", "String"
125-
t.field "internal_code", "String", fetchable: false
125+
t.field "internal_code_gql", "String", name_in_index: "internal_code", returnable: false
126126
t.index "my_type"
127127
end
128128
end
129129

130-
expect(mapping).to include("_source" => {"excludes" => ["internal_code"]})
130+
expect(mapping.dig("_source", "excludes")).to contain_exactly("internal_code")
131131
# The field should still appear in properties (it's indexed, just not in _source)
132132
expect(mapping.dig("properties", "internal_code")).to eq({"type" => "keyword"})
133133
end
134134

135-
it "does not include `_source` config when all fields are fetchable" do
135+
it "adds `.*` to `_source.excludes` for `returnable: false` object fields" do
136+
mapping = index_mapping_for "my_type" do |s|
137+
s.object_type "InternalMetadata" do |t|
138+
t.field "internal_code", "String"
139+
end
140+
141+
s.object_type "MyType" do |t|
142+
t.field "id", "ID"
143+
t.field "internal_metadata", "InternalMetadata", returnable: false
144+
t.index "my_type"
145+
end
146+
end
147+
148+
expect(mapping).to include("_source" => {"excludes" => ["internal_metadata.*"]})
149+
expect(mapping.dig("properties", "internal_metadata", "properties", "internal_code")).to eq({"type" => "keyword"})
150+
end
151+
152+
it "adds `returnable: false` indexing-only fields to `_source.excludes` but not `graphql_only` fields" do
153+
mapping = index_mapping_for "my_type" do |s|
154+
s.object_type "MyType" do |t|
155+
t.field "id", "ID"
156+
t.field "name", "String"
157+
t.field "legacy_name", "String", graphql_only: true, name_in_index: "name", returnable: false
158+
t.field "internal_code", "String", indexing_only: true, returnable: false
159+
t.index "my_type"
160+
end
161+
end
162+
163+
expect(mapping.dig("_source", "excludes")).to contain_exactly("internal_code")
164+
expect(mapping.fetch("properties")).to include(
165+
"name" => {"type" => "keyword"},
166+
"internal_code" => {"type" => "keyword"}
167+
)
168+
expect(mapping.fetch("properties")).not_to include("legacy_name")
169+
end
170+
171+
it "adds full indexed paths to `_source.excludes` for `returnable: false` fields under nested mappings" do
172+
mapping = index_mapping_for "my_type" do |s|
173+
s.object_type "Parent" do |t|
174+
t.field "child", "String", name_in_index: "child_in_index", returnable: false
175+
end
176+
177+
s.object_type "Grandparent" do |t|
178+
t.field "parent", "Parent!", name_in_index: "parent_in_index"
179+
end
180+
181+
s.object_type "MyType" do |t|
182+
t.field "id", "ID!"
183+
t.field "grandparents", "[Grandparent!]!", name_in_index: "grandparents_in_index" do |f|
184+
f.mapping type: "nested"
185+
end
186+
t.index "my_type"
187+
end
188+
end
189+
190+
expect(mapping.dig("_source", "excludes")).to contain_exactly("grandparents_in_index.parent_in_index.child_in_index")
191+
expect(mapping.dig("properties", "grandparents_in_index")).to include(
192+
"type" => "nested",
193+
"properties" => {
194+
"parent_in_index" => {
195+
"properties" => {
196+
"child_in_index" => {"type" => "keyword"}
197+
}
198+
}
199+
}
200+
)
201+
end
202+
203+
it "does not add `_source` config when all fields are returnable" do
136204
mapping = index_mapping_for "my_type" do |s|
137205
s.object_type "MyType" do |t|
138206
t.field "id", "ID"

elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/object_type_spec.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,12 +643,12 @@ module SchemaDefinition
643643
end
644644
end
645645

646-
it "excludes `fetchable: false` fields from the output type but keeps them in filter, sort, grouped_by, aggregated_values, and highlights types" do
646+
it "excludes `returnable: false` fields from the output type but keeps them in filter, sort, grouped_by, aggregated_values, and highlights types" do
647647
result = define_schema do |api|
648648
api.object_type "Widget" do |t|
649649
t.field "id", "ID"
650650
t.field "name", "String"
651-
t.field "internal_code", "String", fetchable: false
651+
t.field "internal_code", "String", returnable: false
652652
t.index "widgets"
653653
end
654654
end
@@ -660,19 +660,19 @@ module SchemaDefinition
660660
}
661661
EOS
662662

663-
# fetchable: false field should still appear in filter input
663+
# returnable: false field should still appear in filter input
664664
expect(filter_type_from(result, "Widget")).to include("internal_code: StringFilterInput")
665665

666-
# fetchable: false field should still appear in sort order
666+
# returnable: false field should still appear in sort order
667667
expect(sort_order_type_from(result, "Widget")).to include("internal_code_ASC")
668668

669-
# fetchable: false field should still appear in grouped_by
669+
# returnable: false field should still appear in grouped_by
670670
expect(grouped_by_type_from(result, "Widget")).to include("internal_code: String")
671671

672-
# fetchable: false field should still appear in aggregated_values
672+
# returnable: false field should still appear in aggregated_values
673673
expect(aggregated_values_type_from(result, "Widget")).to include("internal_code: NonNumericAggregatedValues")
674674

675-
# fetchable: false field should still appear in highlights
675+
# returnable: false field should still appear in highlights
676676
expect(highlights_type_from(result, "Widget")).to include("internal_code: [String!]!")
677677
end
678678

0 commit comments

Comments
 (0)