elasticgraph-schema_definition 1.1.0 → 1.2.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -4
  3. data/lib/elastic_graph/schema_definition/api.rb +20 -1
  4. data/lib/elastic_graph/schema_definition/factory.rb +5 -5
  5. data/lib/elastic_graph/schema_definition/indexing/derived_fields/min_or_max_value.rb +1 -1
  6. data/lib/elastic_graph/schema_definition/indexing/event_envelope.rb +5 -2
  7. data/lib/elastic_graph/schema_definition/indexing/index.rb +21 -3
  8. data/lib/elastic_graph/schema_definition/indexing/json_schema_with_metadata.rb +8 -8
  9. data/lib/elastic_graph/schema_definition/indexing/relationship_resolver.rb +2 -2
  10. data/lib/elastic_graph/schema_definition/indexing/update_target_resolver.rb +1 -1
  11. data/lib/elastic_graph/schema_definition/jruby_patches.rb +59 -0
  12. data/lib/elastic_graph/schema_definition/mixins/has_indices.rb +122 -18
  13. data/lib/elastic_graph/schema_definition/mixins/has_subtypes.rb +21 -12
  14. data/lib/elastic_graph/schema_definition/mixins/implements_interfaces.rb +32 -1
  15. data/lib/elastic_graph/schema_definition/mixins/supports_default_value.rb +1 -14
  16. data/lib/elastic_graph/schema_definition/mixins/supports_filtering_and_aggregation.rb +16 -5
  17. data/lib/elastic_graph/schema_definition/rake_tasks.rb +13 -11
  18. data/lib/elastic_graph/schema_definition/results.rb +26 -28
  19. data/lib/elastic_graph/schema_definition/schema_artifact_manager.rb +3 -2
  20. data/lib/elastic_graph/schema_definition/schema_elements/built_in_types.rb +13 -8
  21. data/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +0 -5
  22. data/lib/elastic_graph/schema_definition/schema_elements/{enums_for_indexed_types.rb → enums_for_directly_queryable_types.rb} +10 -10
  23. data/lib/elastic_graph/schema_definition/schema_elements/field.rb +22 -5
  24. data/lib/elastic_graph/schema_definition/schema_elements/object_type.rb +11 -1
  25. data/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb +0 -5
  26. data/lib/elastic_graph/schema_definition/schema_elements/sub_aggregation_path.rb +1 -1
  27. data/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb +4 -5
  28. data/lib/elastic_graph/schema_definition/schema_elements/union_type.rb +12 -0
  29. data/lib/elastic_graph/schema_definition/state.rb +17 -5
  30. metadata +29 -48
@@ -33,7 +33,7 @@ module ElasticGraph
33
33
  def derived_graphql_types
34
34
  return [] if graphql_only?
35
35
 
36
- indexed_agg_type = to_indexed_aggregation_type
36
+ indexed_agg_type = to_root_aggregation_type
37
37
  indexed_aggregation_pagination_types =
38
38
  if indexed_agg_type
39
39
  schema_def_state.factory.build_relay_pagination_types(indexed_agg_type.name)
@@ -51,7 +51,7 @@ module ElasticGraph
51
51
  end
52
52
 
53
53
  document_pagination_types =
54
- if indexed?
54
+ if directly_queryable?
55
55
  schema_def_state.factory.build_relay_pagination_types(name, include_total_edge_count: true, derived_indexed_types: (_ = self).derived_indexed_types)
56
56
  elsif schema_def_state.paginated_collection_element_types.include?(name)
57
57
  schema_def_state.factory.build_relay_pagination_types(name, include_total_edge_count: true)
@@ -59,7 +59,7 @@ module ElasticGraph
59
59
  [] # : ::Array[SchemaElements::ObjectType]
60
60
  end
61
61
 
62
- sort_order_enum_type = schema_def_state.enums_for_indexed_types.sort_order_enum_for(self)
62
+ sort_order_enum_type = schema_def_state.enums_for_directly_queryable_types.sort_order_enum_for(self)
63
63
  derived_sort_order_enum_types = [sort_order_enum_type].compact + (sort_order_enum_type&.derived_graphql_types || [])
64
64
 
65
65
  to_input_filters +
@@ -87,6 +87,17 @@ module ElasticGraph
87
87
  return [] if does_not_support?(&:filterable?)
88
88
 
89
89
  schema_def_state.factory.build_standard_filter_input_types_for_index_object_type(name) do |t|
90
+ if abstract?
91
+ t.field schema_def_state.schema_elements._typename, schema_def_state.type_ref("String").as_filter_input.name, name_in_index: "__typename" do |f|
92
+ f.documentation <<~EOS
93
+ Filters `#{name}` records by concrete type. Only concrete type names are valid values —
94
+ filtering on an abstract type name will match nothing, since records only have concrete
95
+ type names for their `__typename` value.
96
+ Analogous to the `__typename` return field.
97
+ EOS
98
+ end
99
+ end
100
+
90
101
  graphql_fields_by_name.values.each do |field|
91
102
  if field.filterable?
92
103
  t.graphql_fields_by_name[field.name] = field.to_filter_field(parent_type: t)
@@ -198,8 +209,8 @@ module ElasticGraph
198
209
  end
199
210
  end
200
211
 
201
- def to_indexed_aggregation_type
202
- return nil unless indexed?
212
+ def to_root_aggregation_type
213
+ return nil unless directly_queryable?
203
214
 
204
215
  schema_def_state.factory.new_object_type type_ref.as_aggregation.name do |t|
205
216
  t.documentation "Return type representing a bucket of `#{name}` documents for an aggregations query."
@@ -171,18 +171,20 @@ module ElasticGraph
171
171
  end
172
172
 
173
173
  def schema_def_api
174
- require "elastic_graph/schema_definition/api"
174
+ @schema_def_api ||= begin
175
+ require "elastic_graph/schema_definition/api"
175
176
 
176
- API.new(
177
- @schema_element_names,
178
- @index_document_sizes,
179
- extension_modules: @extension_modules,
180
- derived_type_name_formats: @derived_type_name_formats,
181
- type_name_overrides: @type_name_overrides,
182
- enum_value_overrides_by_type: @enum_value_overrides_by_type,
183
- output: @output
184
- ).tap do |api|
185
- api.as_active_instance { load @path_to_schema.to_s }
177
+ API.new(
178
+ @schema_element_names,
179
+ @index_document_sizes,
180
+ extension_modules: @extension_modules,
181
+ derived_type_name_formats: @derived_type_name_formats,
182
+ type_name_overrides: @type_name_overrides,
183
+ enum_value_overrides_by_type: @enum_value_overrides_by_type,
184
+ output: @output
185
+ ).tap do |api|
186
+ api.as_active_instance { load @path_to_schema.to_s }
187
+ end
186
188
  end
187
189
  end
188
190
  end
@@ -119,28 +119,28 @@ module ElasticGraph
119
119
  query_type.documentation "The query entry point for the entire schema."
120
120
  query_type.resolve_fields_with nil
121
121
 
122
- state.types_by_name.values.select(&:indexed?).sort_by(&:name).each do |type|
123
- # @type var indexed_type: Mixins::HasIndices & _Type
124
- indexed_type = _ = type
122
+ state.object_types_by_name.values.select(&:directly_queryable?).sort_by(&:name).each do |type|
123
+ # @type var root_doc_type: Mixins::HasIndices & _Type
124
+ root_doc_type = _ = type
125
125
 
126
126
  query_type.relates_to_many(
127
- indexed_type.plural_root_query_field_name,
128
- indexed_type.name,
127
+ root_doc_type.plural_root_query_field_name,
128
+ root_doc_type.name,
129
129
  via: "ignore",
130
130
  dir: :in,
131
- singular: indexed_type.singular_root_query_field_name
131
+ singular: root_doc_type.singular_root_query_field_name
132
132
  ) do |f|
133
- f.documentation "Fetches `#{indexed_type.name}`s based on the provided arguments."
134
- f.resolve_with :list_records
133
+ f.documentation "Fetches `#{root_doc_type.name}`s based on the provided arguments."
134
+ f.resolve_with :indexed_type_root_fields
135
135
  f.hide_relationship_runtime_metadata = true
136
- indexed_type.root_query_fields_customizations&.call(f)
136
+ root_doc_type.root_query_fields_customizations&.call(f)
137
137
  end
138
138
 
139
139
  # Add additional efficiency hints to the aggregation field documentation if we have any such hints.
140
140
  # This needs to be outside the `relates_to_many` block because `relates_to_many` adds its own "suffix" to
141
141
  # the field documentation, and here we add another one.
142
- if (agg_efficiency_hint = aggregation_efficiency_hints_for(indexed_type.derived_indexed_types))
143
- agg_name = state.schema_elements.normalize_case("#{indexed_type.singular_root_query_field_name}_aggregations")
142
+ if (agg_efficiency_hint = aggregation_efficiency_hints_for(root_doc_type.derived_indexed_types))
143
+ agg_name = state.schema_elements.normalize_case("#{root_doc_type.singular_root_query_field_name}_aggregations")
144
144
  agg_field = query_type.graphql_fields_by_name.fetch(agg_name)
145
145
  agg_field.documentation "#{agg_field.doc_comment}\n\n#{agg_efficiency_hint}"
146
146
  end
@@ -181,7 +181,7 @@ module ElasticGraph
181
181
  check_for_circular_dependencies!
182
182
 
183
183
  index_templates, indices = state.object_types_by_name.values
184
- .filter_map(&:index_def)
184
+ .filter_map(&:own_index_def)
185
185
  .sort_by(&:name)
186
186
  .partition(&:rollover_config)
187
187
 
@@ -209,19 +209,19 @@ module ElasticGraph
209
209
 
210
210
  scalar_types_by_name = state.scalar_types_by_name.transform_values(&:runtime_metadata)
211
211
 
212
- enum_generator = state.factory.new_enums_for_indexed_types
212
+ enum_generator = state.factory.new_enums_for_directly_queryable_types
213
213
 
214
- indexed_enum_types_by_name = state.object_types_by_name.values
215
- .select(&:indexed?)
214
+ sort_order_enum_types_by_name = state.object_types_by_name.values
215
+ .select(&:directly_queryable?)
216
216
  .filter_map { |type| enum_generator.sort_order_enum_for(_ = type) }
217
217
  .to_h { |enum_type| [(_ = enum_type).name, (_ = enum_type).runtime_metadata] }
218
218
 
219
219
  enum_types_by_name = all_types
220
220
  .grep(SchemaElements::EnumType) # : ::Array[SchemaElements::EnumType]
221
221
  .to_h { |t| [t.name, t.runtime_metadata] }
222
- .merge(indexed_enum_types_by_name)
222
+ .merge(sort_order_enum_types_by_name)
223
223
 
224
- index_definitions_by_name = state.object_types_by_name.values.filter_map(&:index_def).to_h do |index|
224
+ index_definitions_by_name = state.object_types_by_name.values.filter_map(&:own_index_def).to_h do |index|
225
225
  [index.name, index.runtime_metadata]
226
226
  end
227
227
 
@@ -248,7 +248,7 @@ module ElasticGraph
248
248
  ::Hash.new { |h, k| h[k] = [] } # : ::Hash[untyped, ::Array[SchemaArtifacts::RuntimeMetadata::UpdateTarget]]
249
249
  ) do |object_type, accum|
250
250
  fields_with_sources_by_relationship_name =
251
- if object_type.index_def.nil?
251
+ if object_type.own_index_def.nil?
252
252
  # only indexed types can have `sourced_from` fields, and resolving `fields_with_sources` on an unindexed union type
253
253
  # such as `_Entity` when we are using apollo can lead to exceptions when multiple entity types have the same field name
254
254
  # that use different mapping types.
@@ -276,7 +276,7 @@ module ElasticGraph
276
276
  resolved_relationship, relationship_error = relationship_resolver.resolve
277
277
  relationship_errors << relationship_error if relationship_error
278
278
 
279
- if object_type.index_def && resolved_relationship && sourced_fields.any?
279
+ if object_type.own_index_def && resolved_relationship && sourced_fields.any?
280
280
  update_target_resolver = Indexing::UpdateTargetResolver.new(
281
281
  object_type: object_type,
282
282
  resolved_relationship: resolved_relationship,
@@ -289,7 +289,7 @@ module ElasticGraph
289
289
  sourced_field_errors.concat(errors)
290
290
 
291
291
  # Validate that has_had_multiple_sources! has been called when sourced_from is used
292
- if (index_def = object_type.index_def) && !index_def.has_had_multiple_sources_flag
292
+ if (index_def = object_type.own_index_def) && !index_def.has_had_multiple_sources_flag
293
293
  sourced_field_errors << "Type `#{object_type.name}` uses `sourced_from` fields but its index `#{index_def.name}` " \
294
294
  "has not been configured with `has_had_multiple_sources!`. To resolve this, add `i.has_had_multiple_sources!` within the " \
295
295
  "`t.index \"#{index_def.name}\"` block. This flag is required because indices with multiple sources can contain " \
@@ -335,8 +335,8 @@ module ElasticGraph
335
335
  raise Errors::SchemaError, "`json_schema_version` must be specified in the schema. To resolve, add `schema.json_schema_version 1` in a schema definition block."
336
336
  end
337
337
 
338
- indexed_type_names = state.object_types_by_name.values
339
- .select { |type| type.indexed? && !type.abstract? }
338
+ root_document_type_names = state.object_types_by_name.values
339
+ .select { |type| type.root_document_type? && !type.abstract? }
340
340
  .reject { |type| derived_indexing_type_names.include?(type.name) }
341
341
  .map(&:name)
342
342
 
@@ -348,7 +348,7 @@ module ElasticGraph
348
348
  "$schema" => JSON_META_SCHEMA,
349
349
  JSON_SCHEMA_VERSION_KEY => json_schema_version,
350
350
  "$defs" => {
351
- "ElasticGraphEventEnvelope" => Indexing::EventEnvelope.json_schema(indexed_type_names, json_schema_version)
351
+ "ElasticGraphEventEnvelope" => Indexing::EventEnvelope.json_schema(root_document_type_names, json_schema_version)
352
352
  }.merge(definitions_by_name)
353
353
  }
354
354
  end
@@ -381,16 +381,14 @@ module ElasticGraph
381
381
  end
382
382
  end
383
383
 
384
- unused_resolvers = registered_resolvers.except(*fields_by_resolvers.keys).reject do |name, res|
385
- # Ignore our built-in resolvers.
386
- res.resolver_ref.fetch("require_path").start_with?("elastic_graph/graphql/resolvers/")
387
- end.keys
384
+ unused_resolvers = registered_resolvers.except(*fields_by_resolvers.keys, *state.built_in_graphql_resolvers.to_a).keys
388
385
 
389
386
  unless unused_resolvers.empty?
390
387
  state.output.puts <<~EOS
391
388
  WARNING: #{unused_resolvers.size} GraphQL resolver(s) have been registered but are unused:
392
389
  - #{unused_resolvers.sort.join("\n - ")}
393
- These resolvers can be removed.
390
+ These resolvers can be removed. If you intended for them to be available as built-in/internal
391
+ resolvers, pass `built_in: true` when registering them.
394
392
  EOS
395
393
  end
396
394
 
@@ -9,13 +9,14 @@
9
9
  require "did_you_mean"
10
10
  require "elastic_graph/constants"
11
11
  require "elastic_graph/schema_definition/json_schema_pruner"
12
+ require "elastic_graph/support/graphql_gem_loader"
12
13
  require "elastic_graph/support/memoizable_data"
13
14
  require "fileutils"
14
- require "graphql"
15
- require "graphql/c_parser"
16
15
  require "tempfile"
17
16
  require "yaml"
18
17
 
18
+ ElasticGraph::Support::GraphQLGemLoader.load
19
+
19
20
  module ElasticGraph
20
21
  module SchemaDefinition
21
22
  # Manages schema artifacts. Note: not tested directly. Instead, the `RakeTasks` tests drive this class.
@@ -1425,25 +1425,30 @@ module ElasticGraph
1425
1425
  require(require_path = "elastic_graph/graphql/resolvers/get_record_field_value")
1426
1426
  schema_def_api.register_graphql_resolver :get_record_field_value,
1427
1427
  GraphQL::Resolvers::GetRecordFieldValue,
1428
- defined_at: require_path
1428
+ defined_at: require_path,
1429
+ built_in: true
1429
1430
 
1430
- require(require_path = "elastic_graph/graphql/resolvers/list_records")
1431
- schema_def_api.register_graphql_resolver :list_records,
1432
- GraphQL::Resolvers::ListRecords,
1433
- defined_at: require_path
1431
+ require(require_path = "elastic_graph/graphql/resolvers/indexed_type_root_fields_resolver")
1432
+ schema_def_api.register_graphql_resolver :indexed_type_root_fields,
1433
+ GraphQL::Resolvers::IndexedTypeRootFieldsResolver,
1434
+ defined_at: require_path,
1435
+ built_in: true
1434
1436
 
1435
1437
  require(require_path = "elastic_graph/graphql/resolvers/nested_relationships")
1436
1438
  schema_def_api.register_graphql_resolver :nested_relationships,
1437
1439
  GraphQL::Resolvers::NestedRelationships,
1438
- defined_at: require_path
1440
+ defined_at: require_path,
1441
+ built_in: true
1439
1442
 
1440
1443
  require(require_path = "elastic_graph/graphql/resolvers/object")
1441
1444
  schema_def_api.register_graphql_resolver :object_with_lookahead,
1442
1445
  GraphQL::Resolvers::Object::WithLookahead,
1443
- defined_at: require_path
1446
+ defined_at: require_path,
1447
+ built_in: true
1444
1448
  schema_def_api.register_graphql_resolver :object_without_lookahead,
1445
1449
  GraphQL::Resolvers::Object::WithoutLookahead,
1446
- defined_at: require_path
1450
+ defined_at: require_path,
1451
+ built_in: true
1447
1452
  end
1448
1453
 
1449
1454
  def define_date_grouping_arguments(grouping_field, omit_timezone: false)
@@ -161,11 +161,6 @@ module ElasticGraph
161
161
  Indexing::FieldType::Enum.new(values_by_name.keys)
162
162
  end
163
163
 
164
- # @return [false] enum types are never directly indexed
165
- def indexed?
166
- false
167
- end
168
-
169
164
  # @return [EnumType] converts the enum type to its input form for when different naming is used for input vs output enums.
170
165
  def as_input
171
166
  input_name = type_ref
@@ -12,19 +12,19 @@ require "elastic_graph/schema_artifacts/runtime_metadata/sort_field"
12
12
  module ElasticGraph
13
13
  module SchemaDefinition
14
14
  module SchemaElements
15
- # Responsible for generating enum types based on specific indexed types.
15
+ # Responsible for generating enum types based on specific directly queryable types.
16
16
  #
17
17
  # @private
18
- class EnumsForIndexedTypes
18
+ class EnumsForDirectlyQueryableTypes
19
19
  def initialize(schema_def_state)
20
20
  @schema_def_state = schema_def_state
21
21
  end
22
22
 
23
- # Generates a `SortOrder` enum type for the given indexed type.
24
- def sort_order_enum_for(indexed_type)
25
- return nil unless indexed_type.indexed?
23
+ # Generates a `SortOrder` enum type for the given directly queryable type.
24
+ def sort_order_enum_for(type)
25
+ return nil unless type.directly_queryable?
26
26
 
27
- build_enum(indexed_type, :sort_order, :sortable?, "sorted") do |enum_type, field_path|
27
+ build_enum(type, :sort_order, :sortable?, "sorted") do |enum_type, field_path|
28
28
  value_name_parts = field_path.map(&:name)
29
29
  index_field = field_path.map(&:name_in_index).join(".")
30
30
 
@@ -45,12 +45,12 @@ module ElasticGraph
45
45
 
46
46
  private
47
47
 
48
- def build_enum(indexed_type, category, field_predicate, past_tense_verb, &block)
49
- derived_type_ref = indexed_type.type_ref.as_static_derived_type(category)
48
+ def build_enum(type, category, field_predicate, past_tense_verb, &block)
49
+ derived_type_ref = type.type_ref.as_static_derived_type(category)
50
50
 
51
51
  enum = @schema_def_state.factory.new_enum_type(derived_type_ref.name) do |enum_type|
52
- enum_type.documentation "Enumerates the ways `#{indexed_type.name}`s can be #{past_tense_verb}."
53
- define_enum_values_for_type(enum_type, indexed_type, field_predicate, &block)
52
+ enum_type.documentation "Enumerates the ways `#{type.name}`s can be #{past_tense_verb}."
53
+ define_enum_values_for_type(enum_type, type, field_predicate, &block)
54
54
  end.as_input
55
55
 
56
56
  enum unless enum.values_by_name.empty?
@@ -73,6 +73,8 @@ module ElasticGraph
73
73
  # @private
74
74
  # @!attribute [rw] highlightable
75
75
  # @private
76
+ # @!attribute [rw] returnable
77
+ # @private
76
78
  # @!attribute [rw] source
77
79
  # @private
78
80
  # @!attribute [rw] runtime_field_script
@@ -91,7 +93,7 @@ module ElasticGraph
91
93
  :name, :original_type, :parent_type, :original_type_for_derived_types, :schema_def_state, :accuracy_confidence,
92
94
  :filter_customizations, :grouped_by_customizations, :highlights_customizations, :sub_aggregations_customizations,
93
95
  :aggregated_values_customizations, :sort_order_enum_value_customizations, :args,
94
- :sortable, :filterable, :aggregatable, :groupable, :highlightable,
96
+ :sortable, :filterable, :aggregatable, :groupable, :highlightable, :returnable,
95
97
  :graphql_only, :source, :runtime_field_script, :relationship, :singular_name,
96
98
  :computation_detail, :non_nullable_in_json_schema, :as_input,
97
99
  :name_in_index, :resolver
@@ -106,7 +108,7 @@ module ElasticGraph
106
108
  name:, type:, parent_type:, schema_def_state:,
107
109
  accuracy_confidence: :high, name_in_index: name,
108
110
  type_for_derived_types: nil, graphql_only: nil, singular: nil,
109
- sortable: nil, filterable: nil, aggregatable: nil, groupable: nil, highlightable: nil,
111
+ sortable: nil, filterable: nil, aggregatable: nil, groupable: nil, highlightable: nil, returnable: nil,
110
112
  as_input: false, resolver: nil
111
113
  )
112
114
  type_ref = schema_def_state.type_ref(type)
@@ -129,6 +131,7 @@ module ElasticGraph
129
131
  aggregatable: aggregatable,
130
132
  groupable: groupable,
131
133
  highlightable: highlightable,
134
+ returnable: returnable,
132
135
  graphql_only: graphql_only,
133
136
  source: nil,
134
137
  runtime_field_script: nil,
@@ -677,10 +680,10 @@ module ElasticGraph
677
680
  # If the groupability of the field was specified explicitly when the field was defined, use the specified value.
678
681
  return groupable unless groupable.nil?
679
682
 
680
- # We don't want the `id` field of an indexed type to be available to group by, because it's the unique primary key
683
+ # We don't want the `id` field of a root document type to be available to group by, because it's the unique primary key
681
684
  # and the groupings would each contain one document. It's simpler and more efficient to just query the raw documents
682
685
  # instead.
683
- return false if parent_type.indexed? && name == "id"
686
+ return false if parent_type.root_document_type? && name == "id"
684
687
 
685
688
  return false if relationship || type.fully_unwrapped.as_object_type&.does_not_support?(&:groupable?)
686
689
 
@@ -738,11 +741,22 @@ module ElasticGraph
738
741
  def highlightable?
739
742
  return highlightable unless highlightable.nil?
740
743
  return false if relationship
744
+ return false unless returnable?
741
745
  return true if HIGHLIGHTABLE_MAPPING_TYPES.include?(mapping_type)
742
746
 
743
747
  type_for_derived_types.fully_unwrapped.as_object_type&.supports?(&:highlightable?)
744
748
  end
745
749
 
750
+ # Indicates if this field is returnable in GraphQL query responses. When `false`, the field will
751
+ # still be available for filtering, sorting, grouping, and aggregation, but will not appear in the
752
+ # GraphQL output type and its data will be excluded from `_source` in the datastore for storage savings.
753
+ #
754
+ # @return [Boolean] true if this field's data can be returned (default: true)
755
+ def returnable?
756
+ return true if returnable.nil?
757
+ returnable
758
+ end
759
+
746
760
  # Defines an argument on the field.
747
761
  #
748
762
  # @note ElasticGraph takes care of defining arguments for all the query features it supports, so there is generally no need to use
@@ -892,7 +906,10 @@ module ElasticGraph
892
906
  parent_type: parent_type,
893
907
  name_in_index: name_in_index,
894
908
  type_for_derived_types: nil,
895
- resolver: nil
909
+ resolver: nil,
910
+ # Filter fields should always appear in their parent input type's SDL regardless
911
+ # of the source field's returnability.
912
+ returnable: true
896
913
  )
897
914
 
898
915
  schema_def_state.factory.new_field(**params).tap do |f|
@@ -30,7 +30,7 @@ module ElasticGraph
30
30
  include Mixins::SupportsFilteringAndAggregation
31
31
 
32
32
  # `include HasIndices` provides the following methods:
33
- # @dynamic runtime_metadata, derived_indexed_types, indices, indexed?, abstract?
33
+ # @dynamic runtime_metadata, derived_indexed_types, indices, root_document_type?, abstract?, directly_queryable?
34
34
  include Mixins::HasIndices
35
35
 
36
36
  # `include ImplementsInterfaces` provides the following methods:
@@ -38,6 +38,16 @@ module ElasticGraph
38
38
  include Mixins::ImplementsInterfaces
39
39
  include Mixins::HasReadableToSAndInspect.new { |t| t.name }
40
40
 
41
+ # @return [Hash<String, Field>] fields that will be indexed, including __typename for mixed-type indices (types
42
+ # that inherit an index from an abstract supertype)
43
+ # @private
44
+ def indexing_fields_by_name_in_index
45
+ return super if has_own_index_def?
46
+ return super unless root_document_type?
47
+
48
+ super.merge("__typename" => schema_def_state.factory.new_field(name: "__typename", type: "String", parent_type: self))
49
+ end
50
+
41
51
  # @private
42
52
  def initialize(schema_def_state, name)
43
53
  field_factory = schema_def_state.factory.method(:new_field)
@@ -236,11 +236,6 @@ module ElasticGraph
236
236
  end
237
237
  end
238
238
 
239
- # @private
240
- def indexed?
241
- false
242
- end
243
-
244
239
  private
245
240
 
246
241
  EQUAL_TO_ANY_OF_DOC = <<~EOS
@@ -28,7 +28,7 @@ module ElasticGraph
28
28
 
29
29
  # Determines the set of sub aggregation paths for the given type.
30
30
  def self.paths_for(type, schema_def_state:)
31
- root_paths = type.indexed? ? [SubAggregationPath.new([type.name], [])] : [] # : ::Array[SubAggregationPath]
31
+ root_paths = type.root_document_type? ? [SubAggregationPath.new([type.name], [])] : [] # : ::Array[SubAggregationPath]
32
32
 
33
33
  non_relation_field_refs = schema_def_state
34
34
  .user_defined_field_references_by_type_name.fetch(type.name) { [] }
@@ -137,6 +137,9 @@ module ElasticGraph
137
137
  # ElasticGraph will infer field sortability based on the field's GraphQL type and mapping type.
138
138
  # @option options [Boolean] highlightable force-enables or disables the ability to request search highlights for this field. When
139
139
  # not provided, ElasticGraph will infer field highlightable based on the field's mapping type.
140
+ # @option options [Boolean] returnable when set to `false`, the field will not appear in the GraphQL output type and its data
141
+ # will be excluded from `_source` in the datastore for storage savings. The field will still be available for filtering,
142
+ # sorting, grouping, and aggregation. Defaults to `true`.
140
143
  # @yield [Field] the field for further customization
141
144
  # @return [void]
142
145
  #
@@ -470,11 +473,6 @@ module ElasticGraph
470
473
  schema_def_state.type_ref("NonNumeric").as_aggregated_values
471
474
  end
472
475
 
473
- # @private
474
- def indexed?
475
- false
476
- end
477
-
478
476
  # @private
479
477
  def to_indexing_field_type
480
478
  Indexing::FieldType::Object.new(
@@ -536,6 +534,7 @@ module ElasticGraph
536
534
 
537
535
  def fields_sdl(&arg_selector)
538
536
  graphql_fields_by_name.values
537
+ .select(&:returnable?)
539
538
  .map { |f| f.to_sdl(&arg_selector) }
540
539
  .flat_map { |sdl| sdl.split("\n") }
541
540
  .join("\n ")
@@ -94,6 +94,9 @@ module ElasticGraph
94
94
  end
95
95
 
96
96
  subtype_refs << type_ref
97
+
98
+ # Register reverse lookup so we can efficiently find which unions contain this type
99
+ schema_def_state.union_types_by_member_ref[type_ref] << self
97
100
  end
98
101
 
99
102
  # Defines multiple subtypes of this union type.
@@ -128,6 +131,15 @@ module ElasticGraph
128
131
  "#{formatted_documentation}union #{name} #{directives_sdl(suffix_with: " ")}= #{subtype_refs.map(&:name).to_a.join(" | ")}"
129
132
  end
130
133
 
134
+ # Union types cannot themselves be members of other unions or implement interfaces,
135
+ # so they have no supertypes.
136
+ #
137
+ # @return [Set] empty set
138
+ # @private
139
+ def recursively_resolve_supertypes
140
+ Set[]
141
+ end
142
+
131
143
  # @private
132
144
  def verify_graphql_correctness!
133
145
  # Nothing to verify. `verify_graphql_correctness!` will be called on each subtype automatically.
@@ -32,6 +32,7 @@ module ElasticGraph
32
32
  :scalar_types_by_name,
33
33
  :enum_types_by_name,
34
34
  :implementations_by_interface_ref,
35
+ :union_types_by_member_ref,
35
36
  :sdl_parts,
36
37
  :paginated_collection_element_types,
37
38
  :user_defined_fields,
@@ -43,7 +44,7 @@ module ElasticGraph
43
44
  :json_schema_version_setter_location,
44
45
  :graphql_extension_modules,
45
46
  :graphql_resolvers_by_name,
46
- :resolvers_by_name,
47
+ :built_in_graphql_resolvers,
47
48
  :initially_registered_built_in_types,
48
49
  :built_in_types_customization_blocks,
49
50
  :user_definition_complete,
@@ -54,7 +55,8 @@ module ElasticGraph
54
55
  :type_namer,
55
56
  :enum_value_namer,
56
57
  :allow_omitted_json_schema_fields,
57
- :allow_extra_json_schema_fields
58
+ :allow_extra_json_schema_fields,
59
+ :indexed_types_by_index_name
58
60
  )
59
61
  include Mixins::HasReadableToSAndInspect.new
60
62
 
@@ -79,6 +81,7 @@ module ElasticGraph
79
81
  scalar_types_by_name: {},
80
82
  enum_types_by_name: {},
81
83
  implementations_by_interface_ref: ::Hash.new { |h, k| h[k] = ::Set.new },
84
+ union_types_by_member_ref: ::Hash.new { |h, k| h[k] = ::Set.new },
82
85
  sdl_parts: [],
83
86
  paginated_collection_element_types: ::Set.new,
84
87
  user_defined_fields: ::Set.new,
@@ -90,6 +93,7 @@ module ElasticGraph
90
93
  json_schema_version: nil,
91
94
  graphql_extension_modules: [],
92
95
  graphql_resolvers_by_name: {},
96
+ built_in_graphql_resolvers: ::Set.new,
93
97
  initially_registered_built_in_types: ::Set.new,
94
98
  built_in_types_customization_blocks: [],
95
99
  user_definition_complete: false,
@@ -103,7 +107,8 @@ module ElasticGraph
103
107
  enum_value_namer: SchemaElements::EnumValueNamer.new(enum_value_overrides_by_type),
104
108
  output: output,
105
109
  allow_omitted_json_schema_fields: false,
106
- allow_extra_json_schema_fields: true
110
+ allow_extra_json_schema_fields: true,
111
+ indexed_types_by_index_name: {}
107
112
  )
108
113
  end
109
114
 
@@ -132,6 +137,13 @@ module ElasticGraph
132
137
  register_type(type)
133
138
  end
134
139
 
140
+ def register_index(name, type)
141
+ if (existing_type = indexed_types_by_index_name[name])
142
+ raise Errors::SchemaError, "Duplicate index name `#{name}` defined on `#{type.name}` and `#{existing_type.name}`. Each index can only be defined once."
143
+ end
144
+ indexed_types_by_index_name[name] = type
145
+ end
146
+
135
147
  def register_renamed_type(type_name, from:, defined_at:, defined_via:)
136
148
  renamed_types_by_old_name[from] = factory.new_deprecated_element(
137
149
  type_name,
@@ -185,8 +197,8 @@ module ElasticGraph
185
197
  @factory ||= Factory.new(self)
186
198
  end
187
199
 
188
- def enums_for_indexed_types
189
- @enums_for_indexed_types ||= factory.new_enums_for_indexed_types
200
+ def enums_for_directly_queryable_types
201
+ @enums_for_directly_queryable_types ||= factory.new_enums_for_directly_queryable_types
190
202
  end
191
203
 
192
204
  def sub_aggregation_paths_for(type)