diff --git a/elasticgraph-apollo/apollo_tests_implementation/config/products_schema.rb b/elasticgraph-apollo/apollo_tests_implementation/config/products_schema.rb index 703a0aa4..80ded5ba 100644 --- a/elasticgraph-apollo/apollo_tests_implementation/config/products_schema.rb +++ b/elasticgraph-apollo/apollo_tests_implementation/config/products_schema.rb @@ -8,7 +8,28 @@ # @private module ApolloTestImpl - module GraphQLSDLEnumeratorExtension + federation_version = ENV["TARGET_APOLLO_FEDERATION_VERSION"] + + # Note: this includes many "manual" schema elements (directives, raw SDL, etc) that the + # `elasticgraph-apollo` library will generate on our behalf in the future. For now, this + # includes all these schema elements just to make it as close as possible to an apollo + # compatible schema without further changes to elasticgraph-apollo. + # + # https://github.com/apollographql/apollo-federation-subgraph-compatibility/blob/2.0.0/COMPATIBILITY.md#products-schema-to-be-implemented-by-library-maintainers + ElasticGraph.define_schema do |schema| + schema.json_schema_version 1 + schema.target_apollo_federation_version(federation_version) if federation_version + + unless federation_version == "2.0" + schema.raw_sdl <<~EOS + extend schema + @link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"]) + @composeDirective(name: "@custom") + + directive @custom on OBJECT + EOS + end + # The `apollo-federation-subgraph-compatibility` project requires[^1] that each tested implementation provide # specific `Query` fields: # @@ -24,8 +45,8 @@ module GraphQLSDLEnumeratorExtension # `Query` type to add the required fields. # # [^1]: https://github.com/apollographql/apollo-federation-subgraph-compatibility/blob/2.0.0/COMPATIBILITY.md#products-schema-to-be-implemented-by-library-maintainers - def root_query_type - super.tap do |type| + schema.on_built_in_types do |type| + if type.name == "Query" type.field "product", "Product" do |f| f.argument "id", "ID!" end @@ -37,40 +58,6 @@ def root_query_type end end end - end - - # @private - module SchemaDefFactoryExtension - def new_graphql_sdl_enumerator(all_types_except_root_query_type) - super(all_types_except_root_query_type).tap do |enum| - enum.extend GraphQLSDLEnumeratorExtension - end - end - end - - federation_version = ENV["TARGET_APOLLO_FEDERATION_VERSION"] - - # Note: this includes many "manual" schema elements (directives, raw SDL, etc) that the - # `elasticgraph-apollo` library will generate on our behalf in the future. For now, this - # includes all these schema elements just to make it as close as possible to an apollo - # compatible schema without further changes to elasticgraph-apollo. - # - # https://github.com/apollographql/apollo-federation-subgraph-compatibility/blob/2.0.0/COMPATIBILITY.md#products-schema-to-be-implemented-by-library-maintainers - ElasticGraph.define_schema do |schema| - schema.factory.extend SchemaDefFactoryExtension - - schema.json_schema_version 1 - schema.target_apollo_federation_version(federation_version) if federation_version - - unless federation_version == "2.0" - schema.raw_sdl <<~EOS - extend schema - @link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"]) - @composeDirective(name: "@custom") - - directive @custom on OBJECT - EOS - end schema.object_type "Product" do |t| t.directive "custom" unless federation_version == "2.0" diff --git a/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/api_extension.rb b/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/api_extension.rb index fe3afb4a..3e1ce3b0 100644 --- a/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/api_extension.rb +++ b/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/api_extension.rb @@ -117,7 +117,12 @@ def self.extended(api) api.on_built_in_types do |type| # Built-in types like `PageInfo` need to be tagged with `@shareable` on Federation V2 since other subgraphs may # have them and they aren't entity types. `Query`, as the root, is a special case that must be skipped. - (_ = type).apollo_shareable if type.respond_to?(:apollo_shareable) && type.name != "Query" + # We also need to customize it. + if type.name == "Query" + customize_root_query_type(_ = type) + elsif type.respond_to?(:apollo_shareable) + (_ = type).apollo_shareable + end end api.register_graphql_extension GraphQL::EngineExtension, defined_at: "elastic_graph/apollo/graphql/engine_extension" @@ -444,6 +449,53 @@ def validate_entity_types_can_all_be_resolved(entity_types) raise Errors::SchemaError, unresolvable_field_errors.join("\n#{"-" * 100}\n") end end + + private_class_method def self.customize_root_query_type(type) + if type.schema_def_state.object_types_by_name.values.any?(&:indexed?) + type.field "_entities", "[_Entity]!" do |f| + f.documentation <<~EOS + A field required by the [Apollo Federation subgraph + spec](https://www.apollographql.com/docs/federation/subgraph-spec/#query_entities): + + > The graph router uses this root-level `Query` field to directly fetch fields of entities defined by a subgraph. + > + > This field must take a `representations` argument of type `[_Any!]!` (a non-nullable list of non-nullable + > [`_Any` scalars](https://www.apollographql.com/docs/federation/subgraph-spec/#scalar-_any)). Its return type must be `[_Entity]!` (a non-nullable list of _nullable_ + > objects that belong to the [`_Entity` union](https://www.apollographql.com/docs/federation/subgraph-spec/#union-_entity)). + > + > Each entry in the `representations` list must be validated with the following rules: + > + > - A representation must include a `__typename` string field. + > - A representation must contain all fields included in the fieldset of a `@key` directive applied to the corresponding entity definition. + > + > For details, see [Resolving entity fields with `Query._entities`](https://www.apollographql.com/docs/federation/subgraph-spec/#resolving-entity-fields-with-query_entities). + + Not intended for use by clients other than Apollo. + EOS + + f.argument "representations", "[_Any!]!" do |a| + a.documentation <<~EOS + A list of entity data blobs from other apollo subgraphs. For more information (and + to see an example of what form this argument takes), see the [Apollo Federation subgraph + spec](https://www.apollographql.com/docs/federation/subgraph-spec/#resolve-requests-for-entities). + EOS + end + end + end + + type.field "_service", "_Service!" do |f| + f.documentation <<~EOS + A field required by the [Apollo Federation subgraph + spec](https://www.apollographql.com/docs/federation/subgraph-spec/#query_service): + + > This field of the root `Query` type must return a non-nullable [`_Service` type](https://www.apollographql.com/docs/federation/subgraph-spec/#type-_service). + + > For details, see [Enhanced introspection with `Query._service`](https://www.apollographql.com/docs/federation/subgraph-spec/#enhanced-introspection-with-query_service). + + Not intended for use by clients other than Apollo. + EOS + end + end end end end diff --git a/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/factory_extension.rb b/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/factory_extension.rb index 5891d8a8..6ebdcb5a 100644 --- a/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/factory_extension.rb +++ b/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/factory_extension.rb @@ -10,7 +10,6 @@ require "elastic_graph/apollo/schema_definition/enum_type_extension" require "elastic_graph/apollo/schema_definition/enum_value_extension" require "elastic_graph/apollo/schema_definition/field_extension" -require "elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension" require "elastic_graph/apollo/schema_definition/input_type_extension" require "elastic_graph/apollo/schema_definition/interface_type_extension" require "elastic_graph/apollo/schema_definition/object_type_extension" @@ -32,12 +31,6 @@ module FactoryExtension end end - def new_graphql_sdl_enumerator(all_types_except_root_query_type) - super.tap do |enum| - enum.extend GraphQLSDLEnumeratorExtension - end - end - def new_argument(field, name, value_type) super(field, name, value_type) do |type| type.extend ArgumentExtension diff --git a/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension.rb b/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension.rb deleted file mode 100644 index 107f0187..00000000 --- a/elasticgraph-apollo/lib/elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension.rb +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2024 Block, Inc. -# -# Use of this source code is governed by an MIT-style -# license that can be found in the LICENSE file or at -# https://opensource.org/licenses/MIT. -# -# frozen_string_literal: true - -module ElasticGraph - module Apollo - module SchemaDefinition - # Module designed to be extended onto an `ElasticGraph::SchemaDefinition::GraphQLSDLEnumerator` - # instance to customize the schema artifacts to support Apollo. - # - # @private - module GraphQLSDLEnumeratorExtension - def root_query_type - super.tap do |type_or_nil| - # @type var type: ElasticGraph::SchemaDefinition::SchemaElements::ObjectType - type = _ = type_or_nil - - if schema_def_state.object_types_by_name.values.any?(&:indexed?) - type.field "_entities", "[_Entity]!" do |f| - f.documentation <<~EOS - A field required by the [Apollo Federation subgraph - spec](https://www.apollographql.com/docs/federation/subgraph-spec/#query_entities): - - > The graph router uses this root-level `Query` field to directly fetch fields of entities defined by a subgraph. - > - > This field must take a `representations` argument of type `[_Any!]!` (a non-nullable list of non-nullable - > [`_Any` scalars](https://www.apollographql.com/docs/federation/subgraph-spec/#scalar-_any)). Its return type must be `[_Entity]!` (a non-nullable list of _nullable_ - > objects that belong to the [`_Entity` union](https://www.apollographql.com/docs/federation/subgraph-spec/#union-_entity)). - > - > Each entry in the `representations` list must be validated with the following rules: - > - > - A representation must include a `__typename` string field. - > - A representation must contain all fields included in the fieldset of a `@key` directive applied to the corresponding entity definition. - > - > For details, see [Resolving entity fields with `Query._entities`](https://www.apollographql.com/docs/federation/subgraph-spec/#resolving-entity-fields-with-query_entities). - - Not intended for use by clients other than Apollo. - EOS - - f.argument "representations", "[_Any!]!" do |a| - a.documentation <<~EOS - A list of entity data blobs from other apollo subgraphs. For more information (and - to see an example of what form this argument takes), see the [Apollo Federation subgraph - spec](https://www.apollographql.com/docs/federation/subgraph-spec/#resolve-requests-for-entities). - EOS - end - end - end - - type.field "_service", "_Service!" do |f| - f.documentation <<~EOS - A field required by the [Apollo Federation subgraph - spec](https://www.apollographql.com/docs/federation/subgraph-spec/#query_service): - - > This field of the root `Query` type must return a non-nullable [`_Service` type](https://www.apollographql.com/docs/federation/subgraph-spec/#type-_service). - - > For details, see [Enhanced introspection with `Query._service`](https://www.apollographql.com/docs/federation/subgraph-spec/#enhanced-introspection-with-query_service). - - Not intended for use by clients other than Apollo. - EOS - end - end - end - end - end - end -end diff --git a/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/api_extension.rbs b/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/api_extension.rbs index e8ba2eb5..c25b27aa 100644 --- a/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/api_extension.rbs +++ b/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/api_extension.rbs @@ -19,6 +19,7 @@ module ElasticGraph def validate_entity_types_can_all_be_resolved: (::Array[ElasticGraph::SchemaDefinition::indexableType]) -> void def self.extended: (ElasticGraph::SchemaDefinition::API & APIExtension) -> void + def self.customize_root_query_type: (ElasticGraph::SchemaDefinition::SchemaElements::ObjectType) -> void end end end diff --git a/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension.rbs b/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension.rbs deleted file mode 100644 index 07ad5c2d..00000000 --- a/elasticgraph-apollo/sig/elastic_graph/apollo/schema_definition/graphql_sdl_enumerator_extension.rbs +++ /dev/null @@ -1,8 +0,0 @@ -module ElasticGraph - module Apollo - module SchemaDefinition - module GraphQLSDLEnumeratorExtension: ElasticGraph::SchemaDefinition::SchemaElements::GraphQLSDLEnumerator - end - end - end -end diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb index 8a378390..f6e567b6 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb @@ -361,24 +361,14 @@ class GraphQL context "when the schema has been customized (as in an extension like elasticgraph-apollo)" do before(:context) do - enumerator_extension = Module.new do - def root_query_type - super.tap do |type| + self.schema_artifacts = generate_schema_artifacts do |schema| + schema.on_built_in_types do |type| + if type.name == "Query" type.field "multiply", "Int" do |f| f.argument("operands", "Operands!") end end end - end - - self.schema_artifacts = generate_schema_artifacts do |schema| - schema.factory.extend(Module.new { - define_method :new_graphql_sdl_enumerator do |all_types_except_root_query_type| - super(all_types_except_root_query_type).tap do |enum| - enum.extend enumerator_extension - end - end - }) schema.scalar_type "Operands" do |t| t.mapping type: nil diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/factory.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/factory.rb index d4117e28..fe7ae5f1 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/factory.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/factory.rb @@ -115,8 +115,8 @@ def new_enums_for_indexed_types end @@field_new = prevent_non_factory_instantiation_of(SchemaElements::Field) - def new_graphql_sdl_enumerator(all_types_except_root_query_type) - @@graphql_sdl_enumerator_new.call(@state, all_types_except_root_query_type) + def new_graphql_sdl_enumerator(all_types) + @@graphql_sdl_enumerator_new.call(@state, all_types) end @@graphql_sdl_enumerator_new = prevent_non_factory_instantiation_of(SchemaElements::GraphQLSDLEnumerator) diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/results.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/results.rb index aec644c8..2803bb7c 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/results.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/results.rb @@ -110,6 +110,68 @@ def after_initialize # Record that we are now generating results so that caching can kick in. state.user_definition_complete = true state.user_definition_complete_callbacks.each(&:call) + define_root_graphql_type + end + + def define_root_graphql_type + # Some of our tests need to define their own root `Query` type, so here we avoid + # generating `Query` if an sdl part exists that already defines it. + return if state.sdl_parts.flat_map { |sdl| sdl.lines }.any? { |line| line.start_with?("type Query") } + + state.api.object_type "Query" do |query_type| + query_type.documentation "The query entry point for the entire schema." + query_type.override_runtime_metadata default_graphql_resolver: nil + + state.types_by_name.values.select(&:indexed?).sort_by(&:name).each do |type| + # @type var indexed_type: Mixins::HasIndices & _Type + indexed_type = _ = type + + query_type.relates_to_many( + indexed_type.plural_root_query_field_name, + indexed_type.name, + via: "ignore", + dir: :in, + singular: indexed_type.singular_root_query_field_name + ) do |f| + f.documentation "Fetches `#{indexed_type.name}`s based on the provided arguments." + f.resolver = nil + f.hide_relationship_runtime_metadata = true + indexed_type.root_query_fields_customizations&.call(f) + end + + # Add additional efficiency hints to the aggregation field documentation if we have any such hints. + # This needs to be outside the `relates_to_many` block because `relates_to_many` adds its own "suffix" to + # the field documentation, and here we add another one. + if (agg_efficiency_hint = aggregation_efficiency_hints_for(indexed_type.derived_indexed_types)) + agg_name = state.schema_elements.normalize_case("#{indexed_type.singular_root_query_field_name}_aggregations") + agg_field = query_type.graphql_fields_by_name.fetch(agg_name) + agg_field.documentation "#{agg_field.doc_comment}\n\n#{agg_efficiency_hint}" + end + end + + state.built_in_types_customization_blocks.each do |customization_block| + customization_block.call(query_type) + end + end + end + + def aggregation_efficiency_hints_for(derived_indexed_types) + return nil if derived_indexed_types.empty? + + hints = derived_indexed_types.map do |type| + derived_indexing_type = state.types_by_name.fetch(type.destination_type_ref.name) + alternate_field_name = (_ = derived_indexing_type).plural_root_query_field_name + grouping_field = type.id_source + + " - The root `#{alternate_field_name}` field groups by `#{grouping_field}`" + end + + <<~EOS + Note: aggregation queries are relatively expensive, and some fields have been pre-aggregated to allow + more efficient queries for some common aggregation cases: + + #{hints.join("\n")} + EOS end def json_schema_with_metadata_merger @@ -144,7 +206,7 @@ def build_dynamic_scripts def build_runtime_metadata extra_update_targets_by_object_type_name = identify_extra_update_targets_by_object_type_name - object_types_by_name = all_types_except_root_query_type + object_types_by_name = all_types .select { |t| t.respond_to?(:graphql_fields_by_name) } .to_h { |type| [type.name, (_ = type).runtime_metadata(extra_update_targets_by_object_type_name.fetch(type.name) { [] })] } @@ -157,7 +219,7 @@ def build_runtime_metadata .filter_map { |type| enum_generator.sort_order_enum_for(_ = type) } .to_h { |enum_type| [(_ = enum_type).name, (_ = enum_type).runtime_metadata] } - enum_types_by_name = all_types_except_root_query_type + enum_types_by_name = all_types .grep(SchemaElements::EnumType) # : ::Array[SchemaElements::EnumType] .to_h { |t| [t.name, t.runtime_metadata] } .merge(indexed_enum_types_by_name) @@ -183,7 +245,7 @@ def identify_extra_update_targets_by_object_type_name sourced_field_errors = [] # : ::Array[::String] relationship_errors = [] # : ::Array[::String] - state.object_types_by_name.values.each_with_object(::Hash.new { |h, k| h[k] = [] }) do |object_type, accum| + state.object_types_by_name.except("Query").values.each_with_object(::Hash.new { |h, k| h[k] = [] }) do |object_type, accum| fields_with_sources_by_relationship_name = if object_type.indices.empty? # only indexed types can have `sourced_from` fields, and resolving `fields_with_sources` on an unindexed union type @@ -251,7 +313,7 @@ def generate_sdl state.object_types_by_name.values.each(&:verify_graphql_correctness!) type_defs = state.factory - .new_graphql_sdl_enumerator(all_types_except_root_query_type) + .new_graphql_sdl_enumerator(all_types) .map { |sdl| strip_trailing_whitespace(sdl) } [type_defs + state.sdl_parts].join("\n\n") @@ -283,7 +345,9 @@ def build_public_json_schema def json_schema_indexing_field_types_by_name @json_schema_indexing_field_types_by_name ||= state - .types_by_name.values + .types_by_name + .except("Query") + .values .reject do |t| derived_indexing_type_names.include?(t.name) || # Skip graphql framework types @@ -341,9 +405,15 @@ def recursively_add_referenced_types_to(source_type_ref, references_cache) end end - def all_types_except_root_query_type - @all_types_except_root_query_type ||= state.types_by_name.values.flat_map do |registered_type| - related_types = [registered_type] + registered_type.derived_graphql_types + def all_types + @all_types ||= state.types_by_name.values.flat_map do |registered_type| + related_types = + if registered_type.name == "Query" + [registered_type] + else + [registered_type] + registered_type.derived_graphql_types + end + apply_customizations_to(related_types, registered_type) related_types end diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb index bafb84cd..e57531e6 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rb @@ -17,15 +17,15 @@ class GraphQLSDLEnumerator # @dynamic schema_def_state attr_reader :schema_def_state - def initialize(schema_def_state, all_types_except_root_query_type) + def initialize(schema_def_state, all_types) @schema_def_state = schema_def_state - @all_types_except_root_query_type = all_types_except_root_query_type + @all_types = all_types end # Yields the SDL for each GraphQL type, including both explicitly defined # GraphQL types and derived GraphqL types. def each(&block) - all_types = enumerate_all_types.sort_by(&:name) + all_types = @all_types.sort_by(&:name) all_type_names = all_types.map(&:name).to_set all_types.each do |type| @@ -33,80 +33,6 @@ def each(&block) yield type.to_sdl { |arg| all_type_names.include?(arg.value_type.fully_unwrapped.name) } end end - - private - - def enumerate_all_types - [root_query_type].compact + @all_types_except_root_query_type - end - - def aggregation_efficiency_hints_for(derived_indexed_types) - return nil if derived_indexed_types.empty? - - hints = derived_indexed_types.map do |type| - derived_indexing_type = @schema_def_state.types_by_name.fetch(type.destination_type_ref.name) - alternate_field_name = (_ = derived_indexing_type).plural_root_query_field_name - grouping_field = type.id_source - - " - The root `#{alternate_field_name}` field groups by `#{grouping_field}`" - end - - <<~EOS - Note: aggregation queries are relatively expensive, and some fields have been pre-aggregated to allow - more efficient queries for some common aggregation cases: - - #{hints.join("\n")} - EOS - end - - def root_query_type - # Some of our tests need to define their own root `Query` type, so here we avoid - # generating `Query` if an sdl part exists that already defines it. - return nil if @schema_def_state.sdl_parts.flat_map { |sdl| sdl.lines }.any? { |line| line.start_with?("type Query") } - - new_built_in_object_type "Query" do |t| - t.documentation "The query entry point for the entire schema." - - @schema_def_state.types_by_name.values.select(&:indexed?).sort_by(&:name).each do |type| - # @type var indexed_type: Mixins::HasIndices & _Type - indexed_type = _ = type - - t.relates_to_many( - indexed_type.plural_root_query_field_name, - indexed_type.name, - via: "ignore", - dir: :in, - singular: indexed_type.singular_root_query_field_name - ) do |f| - f.documentation "Fetches `#{indexed_type.name}`s based on the provided arguments." - indexed_type.root_query_fields_customizations&.call(f) - end - - # Add additional efficiency hints to the aggregation field documentation if we have any such hints. - # This needs to be outside the `relates_to_many` block because `relates_to_many` adds its own "suffix" to - # the field documentation, and here we add another one. - if (agg_efficiency_hint = aggregation_efficiency_hints_for(indexed_type.derived_indexed_types)) - agg_name = @schema_def_state.schema_elements.normalize_case("#{indexed_type.singular_root_query_field_name}_aggregations") - agg_field = t.graphql_fields_by_name.fetch(agg_name) - agg_field.documentation "#{agg_field.doc_comment}\n\n#{agg_efficiency_hint}" - end - end - end - end - - def new_built_in_object_type(name, &block) - new_object_type name do |type| - @schema_def_state.built_in_types_customization_blocks.each do |customization_block| - customization_block.call(type) - end - - block.call(type) - end - end - - def new_object_type(name, &block) - @schema_def_state.factory.new_object_type(name, &block) - end end end end diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/relationship.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/relationship.rb index eede322e..1785269a 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/relationship.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/relationship.rb @@ -37,14 +37,18 @@ module SchemaElements # end # end class Relationship < DelegateClass(Field) - # @dynamic related_type + # @dynamic related_type, hide_relationship_runtime_metadata, hide_relationship_runtime_metadata= # @return [ObjectType, InterfaceType, UnionType] the type this relationship relates to attr_reader :related_type + # @private + attr_accessor :hide_relationship_runtime_metadata + # @private def initialize(field, cardinality:, related_type:, foreign_key:, direction:) super(field) + self.hide_relationship_runtime_metadata = false @cardinality = cardinality @related_type = related_type @foreign_key = foreign_key @@ -192,6 +196,8 @@ def many? # @private def runtime_metadata + return nil if hide_relationship_runtime_metadata + resolved_related_type = (_ = related_type.unwrap_list.as_object_type) # : indexableType foreign_key_nested_paths = schema_def_state.field_path_resolver.determine_nested_paths(resolved_related_type, @foreign_key) foreign_key_nested_paths ||= [] # : ::Array[::String] diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb index 760489bf..cdc306ca 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb @@ -537,10 +537,9 @@ def relates_to(field_name, type, via:, dir:, foreign_key_type:, cardinality:, re direction: dir ) + field.resolver = :nested_relationships yield relationship if block_given? - field.relationship = relationship - field.resolver = :nested_relationships if dir == :out register_inferred_foreign_key_fields(from_type: [via, foreign_key_type], to_other: ["id", "ID!"], related_type: relationship.related_type) diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/results.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/results.rbs index 6f488421..8768df52 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/results.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/results.rbs @@ -34,6 +34,8 @@ module ElasticGraph private + def define_root_graphql_type: () -> void + def aggregation_efficiency_hints_for: (::Array[Indexing::DerivedIndexedType]) -> ::String? def json_schema_with_metadata_merger: () -> Indexing::JSONSchemaWithMetadata::Merger def generate_datastore_config: () -> ::Hash[::String, untyped] def build_dynamic_scripts: () -> ::Array[Scripting::Script] @@ -46,8 +48,8 @@ module ElasticGraph def check_for_circular_dependencies!: () -> void def recursively_add_referenced_types_to: (SchemaElements::TypeReference, ::Hash[::String, ::Set[::String]]) -> void - @all_types_except_root_query_type: Array[SchemaElements::graphQLType]? - def all_types_except_root_query_type: () -> ::Array[SchemaElements::graphQLType] + @all_types: Array[SchemaElements::graphQLType]? + def all_types: () -> ::Array[SchemaElements::graphQLType] def apply_customizations_to: (::Array[SchemaElements::graphQLType], SchemaElements::graphQLType) -> void end end diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rbs index 851538bb..2d926908 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/graphql_sdl_enumerator.rbs @@ -11,13 +11,7 @@ module ElasticGraph private @schema_def_state: State - @all_types_except_root_query_type: ::Array[graphQLType] - - def enumerate_all_types: () -> ::Array[graphQLType] - def aggregation_efficiency_hints_for: (::Array[Indexing::DerivedIndexedType]) -> ::String? - def root_query_type: () -> ObjectType? - def new_built_in_object_type: (::String) { (ObjectType) -> void } -> ObjectType - def new_object_type: (::String) { (ObjectType) -> void } -> ObjectType + @all_types: ::Array[graphQLType] end end end diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/relationship.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/relationship.rbs index 24fad2ab..ee1d0312 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/relationship.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/relationship.rbs @@ -8,6 +8,7 @@ module ElasticGraph class Relationship < RelationshipSupertype type cardinality = :one | :many attr_reader related_type: TypeReference + attr_accessor hide_relationship_runtime_metadata: bool @cardinality: cardinality @related_type: TypeReference @@ -30,7 +31,7 @@ module ElasticGraph def rollover_timestamp_value_source_for_index: [T] (Indexing::Index) { (::String) -> bot } -> ::String? def validate_equivalent_fields: (SchemaElements::FieldPath::Resolver) -> ::Array[::String] def many?: () -> bool - def runtime_metadata: () -> SchemaArtifacts::RuntimeMetadata::Relation + def runtime_metadata: () -> SchemaArtifacts::RuntimeMetadata::Relation? private diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/type_with_subfields.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/type_with_subfields.rbs index 4b182741..2e5faf6c 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/type_with_subfields.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/type_with_subfields.rbs @@ -71,8 +71,8 @@ module ElasticGraph ?singular: ::String? ) ?{ (Field) -> void } -> Field - def relates_to_one: (::String, ::String, via: ::String, dir: foreignKeyDirection) ?{ (Field) -> void } -> void - def relates_to_many: (::String, ::String, via: ::String, dir: foreignKeyDirection, singular: ::String) ?{ (Field) -> void } -> void + def relates_to_one: (::String, ::String, via: ::String, dir: foreignKeyDirection) ?{ (Relationship) -> void } -> void + def relates_to_many: (::String, ::String, via: ::String, dir: foreignKeyDirection, singular: ::String) ?{ (Relationship) -> void } -> void def generate_sdl: (name_section: ::String) ?{ (Field::argument) -> boolish } -> String def current_sources: () -> ::Array[::String] @@ -101,7 +101,7 @@ module ElasticGraph dir: foreignKeyDirection, foreign_key_type: ::String, cardinality: Relationship::cardinality, - related_type: ::String) ?{ (Field) -> void } -> void + related_type: ::String) ?{ (Relationship) -> void } -> void def register_inferred_foreign_key_fields: ( from_type: [::String, ::String], diff --git a/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/root_query_type_spec.rb b/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/root_query_type_spec.rb index ff96979b..ac13a602 100644 --- a/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/root_query_type_spec.rb +++ b/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/root_query_type_spec.rb @@ -372,6 +372,25 @@ module SchemaDefinition EOS end + it "does not generate derived types from the `Query` type, even if customized" do + result = define_schema do |api| + api.object_type "Person" do |t| + t.root_query_fields plural: "people" + t.field "id", "ID!", sortable: false, filterable: false + t.index "people" + end + + api.on_built_in_types do |t| + if t.name == "Query" + t.field "time", "String" + end + end + end + + type_names = ::GraphQL::Schema.from_definition(result).types.keys + expect(type_names.grep(/\AQuery/)).to eq ["Query"] + end + it "can be overridden via `raw_sdl` to support ElasticGraph tests that require a custom `Query` type" do query_type_def = <<~EOS.strip type Query { diff --git a/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/json_schema_spec.rb b/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/json_schema_spec.rb index 616a6811..9b15654e 100644 --- a/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/json_schema_spec.rb +++ b/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/json_schema_spec.rb @@ -45,7 +45,7 @@ module SchemaDefinition # Input enum types are named with an `Input` suffix. The JSON schema only contains the types we index, which are output types, # and therefore it does not have the input enum types. - untested_types = built_in_types - @tested_types.to_a - input_enum_types + untested_types = built_in_types - @tested_types.to_a - input_enum_types - ["Query"] expect(untested_types).to be_empty, "It appears that #{untested_types.size} built-in type(s) lack test coverage in `json_schema_spec.rb`. " \ @@ -287,6 +287,10 @@ module SchemaDefinition .and_fails_to_match(0.5, nil, true, LONG_STRING_MIN - 1, LONG_STRING_MAX + 1) end + it "excludes `Query`" do + expect(json_schema.fetch("$defs").keys).to exclude "Query" + end + def have_json_schema_like(type_name, *args, **kwargs) @tested_types << type_name super(type_name, *args, **kwargs)