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 c51405584..af1124480 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 @@ -38,8 +38,8 @@ def new_argument(field, name, value_type) end end - def new_enum_type(name) - super(name) do |type| + def new_enum_type(name, **kwargs) + super(name, **kwargs) do |type| type.extend EnumTypeExtension yield type end diff --git a/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb b/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb index d03ad1d02..c93cbd6e6 100644 --- a/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb +++ b/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/index_expression_builder.rb @@ -36,7 +36,7 @@ def initialize(filter_node_interpreter:, schema_names:) else Support::TimeSet.of_range(operator => ::Time.iso8601(filter_value)) end - when :equal_to_any_of + when :equal_to_any_of, :equal_to_any_of_input # This calls `.compact` to remove `nil` timestamp values. ranges = filter_value.compact.map do |iso8601_string| if date_string?(iso8601_string) diff --git a/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/routing_picker.rb b/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/routing_picker.rb index 5910dd234..2b479229b 100644 --- a/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/routing_picker.rb +++ b/elasticgraph-graphql/lib/elastic_graph/graphql/datastore_query/routing_picker.rb @@ -23,7 +23,7 @@ def initialize(filter_node_interpreter:, schema_names:) all_values_set, empty_set ) do |operator, filter_value| - if operator == :equal_to_any_of + if operator == :equal_to_any_of || operator == :equal_to_any_of_input # This calls `.compact` to remove `nil` filter_value values RoutingValueSet.of(filter_value.compact) end diff --git a/elasticgraph-graphql/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb b/elasticgraph-graphql/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb index 4faf64550..0e8f110d0 100644 --- a/elasticgraph-graphql/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb +++ b/elasticgraph-graphql/lib/elastic_graph/graphql/filtering/filter_node_interpreter.rb @@ -54,38 +54,41 @@ def build_filter_operators(runtime_metadata) .static_script_ids_by_scoped_name .fetch("filter/by_time_of_day") - { - schema_names.equal_to_any_of => ->(field_name, value) { - values = to_datastore_value(value.compact.uniq) # : ::Array[untyped] - - equality_sub_expression = - if field_name == "id" - # Use specialized "ids" query when querying on ID field. - # See: https://www.elastic.co/guide/en/elasticsearch/reference/7.15/query-dsl-ids-query.html - # - # We reject empty strings because we otherwise get an error from the datastore: - # "failed to create query: Ids can't be empty" - {ids: {values: values - [""]}} - else - {terms: {field_name => values}} - end - - exists_sub_expression = {exists: {"field" => field_name}} - - if !value.empty? && value.all?(&:nil?) - BooleanQuery.new(:must_not, [{bool: {filter: [exists_sub_expression]}}]) - elsif value.include?(nil) - BooleanQuery.filter({bool: { - minimum_should_match: 1, - should: [ - {bool: {filter: [equality_sub_expression]}}, - {bool: {must_not: [{bool: {filter: [exists_sub_expression]}}]}} - ] - }}) + equal_to_any_of_lambda = ->(field_name, value) { + values = to_datastore_value(value.compact.uniq) # : ::Array[untyped] + + equality_sub_expression = + if field_name == "id" + # Use specialized "ids" query when querying on ID field. + # See: https://www.elastic.co/guide/en/elasticsearch/reference/7.15/query-dsl-ids-query.html + # + # We reject empty strings because we otherwise get an error from the datastore: + # "failed to create query: Ids can't be empty" + {ids: {values: values - [""]}} else - BooleanQuery.filter(equality_sub_expression) + {terms: {field_name => values}} end - }, + + exists_sub_expression = {exists: {"field" => field_name}} + + if !value.empty? && value.all?(&:nil?) + BooleanQuery.new(:must_not, [{bool: {filter: [exists_sub_expression]}}]) + elsif value.include?(nil) + BooleanQuery.filter({bool: { + minimum_should_match: 1, + should: [ + {bool: {filter: [equality_sub_expression]}}, + {bool: {must_not: [{bool: {filter: [exists_sub_expression]}}]}} + ] + }}) + else + BooleanQuery.filter(equality_sub_expression) + end + } + + { + schema_names.equal_to_any_of => equal_to_any_of_lambda, + schema_names.equal_to_any_of_input => equal_to_any_of_lambda, schema_names.gt => ->(field_name, value) { RangeQuery.new(field_name, :gt, value) }, schema_names.gte => ->(field_name, value) { RangeQuery.new(field_name, :gte, value) }, schema_names.lt => ->(field_name, value) { RangeQuery.new(field_name, :lt, value) }, diff --git a/elasticgraph-graphql/lib/elastic_graph/graphql/query_adapter/filters.rb b/elasticgraph-graphql/lib/elastic_graph/graphql/query_adapter/filters.rb index 0c5a751a2..861910814 100644 --- a/elasticgraph-graphql/lib/elastic_graph/graphql/query_adapter/filters.rb +++ b/elasticgraph-graphql/lib/elastic_graph/graphql/query_adapter/filters.rb @@ -107,7 +107,7 @@ def filter_value_set_extractor IncludesNilSet, ExcludesNilSet ) do |operator, filter_value| - if operator == :equal_to_any_of && filter_value.include?(nil) + if (operator == :equal_to_any_of || operator == :equal_to_any_of_input) && filter_value.include?(nil) IncludesNilSet else ExcludesNilSet diff --git a/elasticgraph-schema_artifacts/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb b/elasticgraph-schema_artifacts/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb index 07932f95d..67c67f41e 100644 --- a/elasticgraph-schema_artifacts/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb +++ b/elasticgraph-schema_artifacts/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb @@ -142,7 +142,7 @@ def normalize_case(name) SchemaElementNames = SchemaElementNamesDefinition.new( # Filter arg and operation names: :filter, - :equal_to_any_of, :gt, :gte, :lt, :lte, :matches_phrase, :matches_query, :matches_query_with_prefix, :any_of, :all_of, :not, + :equal_to_any_of, :equal_to_any_of_input, :gt, :gte, :lt, :lte, :matches_phrase, :matches_query, :matches_query_with_prefix, :any_of, :all_of, :not, :time_of_day, :any_satisfy, :contains, :starts_with, :all_substrings_of, :any_substring_of, :ignore_case, :any_prefix_of, # Directives :eg_latency_slo, :ms, diff --git a/elasticgraph-schema_artifacts/sig/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rbs b/elasticgraph-schema_artifacts/sig/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rbs index d12b861b7..c4141c043 100644 --- a/elasticgraph-schema_artifacts/sig/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rbs +++ b/elasticgraph-schema_artifacts/sig/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rbs @@ -18,6 +18,7 @@ module ElasticGraph def canonical_name_for: (::String | ::Symbol) -> ::Symbol attr_reader filter: ::String attr_reader equal_to_any_of: ::String + attr_reader equal_to_any_of_input: ::String attr_reader gt: ::String attr_reader gte: ::String attr_reader lt: ::String diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/api.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/api.rb index 4b61944c2..49757354e 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/api.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/api.rb @@ -63,6 +63,7 @@ def initialize( derived_type_name_formats: {}, type_name_overrides: {}, enum_value_overrides_by_type: {}, + enums_in_transition: [], output: $stdout ) @state = State.with( @@ -72,6 +73,7 @@ def initialize( derived_type_name_formats: derived_type_name_formats, type_name_overrides: type_name_overrides, enum_value_overrides_by_type: enum_value_overrides_by_type, + enums_in_transition: enums_in_transition, output: output ) 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 6ce092aab..5767f8001 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/factory.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/factory.rb @@ -94,8 +94,8 @@ def new_directive(name, arguments) end @@directive_new = prevent_non_factory_instantiation_of(SchemaElements::Directive) - def new_enum_type(name, &block) - @@enum_type_new.call(@state, name, &(_ = block)) + def new_enum_type(name, skip_name_override: false, &block) + @@enum_type_new.call(@state, name, skip_name_override: skip_name_override, &(_ = block)) end @@enum_type_new = prevent_non_factory_instantiation_of(SchemaElements::EnumType) diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/rake_tasks.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/rake_tasks.rb index 58fbe7517..ccc966b46 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/rake_tasks.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/rake_tasks.rb @@ -116,6 +116,7 @@ def initialize( derived_type_name_formats: {}, type_name_overrides: {}, enum_value_overrides_by_type: {}, + enums_in_transition: [], extension_modules: [], enforce_json_schema_version: true, output: $stdout @@ -128,6 +129,7 @@ def initialize( @derived_type_name_formats = derived_type_name_formats @type_name_overrides = type_name_overrides @enum_value_overrides_by_type = enum_value_overrides_by_type + @enums_in_transition = enums_in_transition @index_document_sizes = index_document_sizes @path_to_schema = path_to_schema @schema_artifacts_directory = schema_artifacts_directory @@ -181,6 +183,7 @@ def schema_def_api derived_type_name_formats: @derived_type_name_formats, type_name_overrides: @type_name_overrides, enum_value_overrides_by_type: @enum_value_overrides_by_type, + enums_in_transition: @enums_in_transition, output: @output ).tap do |api| api.as_active_instance { load @path_to_schema.to_s } diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_artifact_manager.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_artifact_manager.rb index 56d288fae..d68c57e80 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_artifact_manager.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_artifact_manager.rb @@ -100,6 +100,7 @@ def artifacts # unused things until after we've used things to generate artifacts. notify_about_unused_type_name_overrides notify_about_unused_enum_value_overrides + notify_about_unrecognized_enums_in_transition end end @@ -173,6 +174,27 @@ def notify_about_unused_enum_value_overrides EOS end + def notify_about_unrecognized_enums_in_transition + state = @schema_definition_results.state + return if state.enums_in_transition.empty? + + unrecognized = state.enums_in_transition.reject { |name| state.enum_types_by_name.key?(name) } + return if unrecognized.empty? + + suggester = ::DidYouMean::SpellChecker.new(dictionary: state.enum_types_by_name.keys) + warnings = unrecognized.map.with_index(1) do |name, index| + alternatives = suggester.correct(name).map { |alt| "`#{alt}`" } + "#{index}. `#{name}` does not match any enum type in your GraphQL schema." \ + "#{" Possible alternatives: #{alternatives.join(", ")}." unless alternatives.empty?}" + end + + @output.puts <<~EOS + WARNING: #{unrecognized.size} of the `enums_in_transition` do not match any enum type(s) in your GraphQL schema: + + #{warnings.join("\n")} + EOS + end + def build_desired_versioned_json_schemas(current_public_json_schema) versioned_parsed_yamls = ::Dir.glob(::File.join(@schema_artifacts_directory, JSON_SCHEMAS_BY_VERSION_DIRECTORY, "v*.yaml")).map do |file| ::YAML.safe_load_file(file) diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb index 0bdb27b60..340b93885 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/enum_type.rb @@ -46,10 +46,15 @@ class EnumType < Struct.new(:schema_def_state, :type_ref, :for_output, :values_b include Mixins::HasReadableToSAndInspect.new { |e| e.name } # @private - def initialize(schema_def_state, name) + def initialize(schema_def_state, name, skip_name_override: false) # @type var values_by_name: ::Hash[::String, EnumValue] values_by_name = {} - super(schema_def_state, schema_def_state.type_ref(name).to_final_form, true, values_by_name) + type_ref = if skip_name_override + schema_def_state.type_ref(name) + else + schema_def_state.type_ref(name).to_final_form + end + super(schema_def_state, type_ref, true, values_by_name) # :nocov: -- currently all invocations have a block yield self if block_given? @@ -150,7 +155,11 @@ def derived_graphql_types end.derived_graphql_types if (input_enum = as_input).equal?(self) - derived_scalar_types + if schema_def_state.enums_in_transition.include?(name) + [build_transition_input_enum] + derived_scalar_types + else + derived_scalar_types + end else [input_enum] + derived_scalar_types end @@ -178,6 +187,20 @@ def as_input values_by_name.each { |_, val| val.duplicate_on(t) } end end + + private + + def build_transition_input_enum + input_enum_name = schema_def_state.type_namer.generate_name_for(:InputEnum, base: name) + + schema_def_state.factory.new_enum_type(input_enum_name, skip_name_override: true) do |t| + t.for_output = false + t.graphql_only true + t.documentation doc_comment + directives.each { |dir| dir.duplicate_on(t) } + values_by_name.each { |_, val| val.duplicate_on(t) } + end + end end end end diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/field.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/field.rb index 9bf47dab0..90b381750 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/field.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/field.rb @@ -87,6 +87,8 @@ module SchemaElements # @private # @!attribute [rw] as_input # @private + # @!attribute [rw] type_already_final + # @private class Field < Struct.new( :name, :original_type, :parent_type, :original_type_for_derived_types, :schema_def_state, :accuracy_confidence, :filter_customizations, :grouped_by_customizations, :highlights_customizations, :sub_aggregations_customizations, @@ -94,7 +96,7 @@ class Field < Struct.new( :sortable, :filterable, :aggregatable, :groupable, :highlightable, :graphql_only, :source, :runtime_field_script, :relationship, :singular_name, :computation_detail, :non_nullable_in_json_schema, :as_input, - :name_in_index, :resolver + :name_in_index, :resolver, :type_already_final ) include Mixins::HasDocumentation include Mixins::HasDirectives @@ -107,7 +109,7 @@ def initialize( accuracy_confidence: :high, name_in_index: name, type_for_derived_types: nil, graphql_only: nil, singular: nil, sortable: nil, filterable: nil, aggregatable: nil, groupable: nil, highlightable: nil, - as_input: false, resolver: nil + as_input: false, resolver: nil, type_already_final: false ) type_ref = schema_def_state.type_ref(type) super( @@ -139,7 +141,8 @@ def initialize( name_in_index: name_in_index, non_nullable_in_json_schema: false, as_input: as_input, - resolver: resolver + resolver: resolver, + type_already_final: type_already_final ) if name != name_in_index @@ -172,6 +175,10 @@ def initialize( # @return [TypeReference] the type of this field def type + # When `type_already_final` is set, the type reference has already been resolved to its final form + # (e.g. for transition input enum filter fields that must reference the InputEnum type directly). + return original_type if type_already_final + # Here we lazily convert the `original_type` to an input type as needed. This must be lazy because # the logic of `as_input` depends on detecting whether the type is an enum type, which it may not # be able to do right away--we assume not if we can't tell, and retry every time this method is called. @@ -182,6 +189,7 @@ def type # # @private def type_for_derived_types + return original_type_for_derived_types if type_already_final original_type_for_derived_types.to_final_form(as_input: as_input) end diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb index 8e2703cd2..21212e486 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb @@ -309,6 +309,16 @@ def to_input_filters f.documentation EQUAL_TO_ANY_OF_DOC end + if schema_def_state.enums_in_transition.include?(name) + input_enum_name = schema_def_state.type_namer.generate_name_for(:InputEnum, base: name) + transition_type = t.type_ref.list_element_filter_input? ? "[#{input_enum_name}!]" : "[#{input_enum_name}]" + + t.field schema_def_state.schema_elements.equal_to_any_of_input, transition_type do |f| + f.documentation "Like `#{schema_def_state.schema_elements.equal_to_any_of}`, but accepts the input enum type. Use this field during enum migration." + f.type_already_final = true + end + end + if mapping_type_efficiently_comparable? t.field schema_def_state.schema_elements.gt, name do |f| f.documentation GT_DOC diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb index 800d15cbf..860167045 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/state.rb @@ -55,7 +55,8 @@ class State < Struct.new( :type_namer, :enum_value_namer, :allow_omitted_json_schema_fields, - :allow_extra_json_schema_fields + :allow_extra_json_schema_fields, + :enums_in_transition ) include Mixins::HasReadableToSAndInspect.new @@ -66,6 +67,7 @@ def self.with( derived_type_name_formats:, type_name_overrides:, enum_value_overrides_by_type:, + enums_in_transition: ::Set.new, output: $stdout ) # @type var types_by_name: SchemaElements::typesByNameHash @@ -106,7 +108,8 @@ def self.with( enum_value_namer: SchemaElements::EnumValueNamer.new(enum_value_overrides_by_type), output: output, allow_omitted_json_schema_fields: false, - allow_extra_json_schema_fields: true + allow_extra_json_schema_fields: true, + enums_in_transition: enums_in_transition.to_set ) end diff --git a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/test_support.rb b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/test_support.rb index 7084db978..32dd47bcc 100644 --- a/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/test_support.rb +++ b/elasticgraph-schema_definition/lib/elastic_graph/schema_definition/test_support.rb @@ -28,6 +28,7 @@ def define_schema( derived_type_name_formats: {}, type_name_overrides: {}, enum_value_overrides_by_type: {}, + enums_in_transition: [], reload_schema_artifacts: false, output: nil, &block @@ -45,6 +46,7 @@ def define_schema( derived_type_name_formats: derived_type_name_formats, type_name_overrides: type_name_overrides, enum_value_overrides_by_type: enum_value_overrides_by_type, + enums_in_transition: enums_in_transition, reload_schema_artifacts: reload_schema_artifacts, output: output, &block @@ -59,6 +61,7 @@ def define_schema_with_schema_elements( derived_type_name_formats: {}, type_name_overrides: {}, enum_value_overrides_by_type: {}, + enums_in_transition: [], reload_schema_artifacts: false, output: nil ) @@ -69,6 +72,7 @@ def define_schema_with_schema_elements( derived_type_name_formats: derived_type_name_formats, type_name_overrides: type_name_overrides, enum_value_overrides_by_type: enum_value_overrides_by_type, + enums_in_transition: enums_in_transition, output: output || $stdout ) diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/api.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/api.rbs index 604ed7842..b010d6349 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/api.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/api.rbs @@ -58,6 +58,7 @@ module ElasticGraph ?derived_type_name_formats: ::Hash[::Symbol, ::String], ?type_name_overrides: ::Hash[::Symbol, ::String], ?enum_value_overrides_by_type: ::Hash[::Symbol, ::Hash[::Symbol, ::String]], + ?enums_in_transition: ::Array[::String], ?output: io ) -> void diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/factory.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/factory.rbs index 98ac87dea..380d25bab 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/factory.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/factory.rbs @@ -22,7 +22,7 @@ module ElasticGraph def new_directive: (::String, SchemaElements::directiveArgHash) -> SchemaElements::Directive @@directive_new: ::Method - def new_enum_type: (::String) ?{ (SchemaElements::EnumType) -> void } -> SchemaElements::EnumType + def new_enum_type: (::String, ?skip_name_override: bool) ?{ (SchemaElements::EnumType) -> void } -> SchemaElements::EnumType @@enum_type_new: ::Method def new_enum_value: (::String, ::String) ?{ (SchemaElements::EnumValue) -> void } -> SchemaElements::EnumValue diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/rake_tasks.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/rake_tasks.rbs index 9578488de..0957cce7f 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/rake_tasks.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/rake_tasks.rbs @@ -10,6 +10,7 @@ module ElasticGraph ?derived_type_name_formats: ::Hash[::Symbol, ::String], ?type_name_overrides: ::Hash[::Symbol, ::String], ?enum_value_overrides_by_type: ::Hash[::Symbol, ::Hash[::Symbol, ::String]], + ?enums_in_transition: ::Array[::String], ?extension_modules: ::Array[::Module], ?enforce_json_schema_version: bool, ?output: io @@ -20,6 +21,7 @@ module ElasticGraph @derived_type_name_formats: ::Hash[::Symbol, ::String] @type_name_overrides: ::Hash[::Symbol, ::String] @enum_value_overrides_by_type: ::Hash[::Symbol, ::Hash[::Symbol, ::String]] + @enums_in_transition: ::Array[::String] @schema_element_names: SchemaArtifacts::RuntimeMetadata::SchemaElementNames @index_document_sizes: bool @path_to_schema: ::String | ::Pathname diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_artifact_manager.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_artifact_manager.rbs index b4b079de0..df49ac957 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_artifact_manager.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_artifact_manager.rbs @@ -28,6 +28,7 @@ module ElasticGraph def artifacts_from_schema_def: () -> ::Array[SchemaArtifact[untyped]] def notify_about_unused_type_name_overrides: () -> void def notify_about_unused_enum_value_overrides: () -> void + def notify_about_unrecognized_enums_in_transition: () -> void def build_desired_versioned_json_schemas: (::Hash[::String, untyped]) -> ::Hash[::Integer, ::Hash[::String, untyped]] def report_json_schema_merge_errors: (::Array[Indexing::JSONSchemaWithMetadata]) -> void def report_json_schema_merge_warnings: () -> void diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs index 518792bcb..04aa95bdc 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/enum_type.rbs @@ -20,7 +20,7 @@ module ElasticGraph include Mixins::HasDocumentation include Mixins::HasDerivedGraphQLTypeCustomizations include _Type - def initialize: (State, ::String) ?{ (EnumType) -> void } -> void + def initialize: (State, ::String, ?skip_name_override: bool) ?{ (EnumType) -> void } -> void def aggregated_values_type: () -> TypeReference def value: (::String) ?{ (EnumValue) -> void } -> void def values: (*::String) -> void diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/field.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/field.rbs index be91e03a3..24b8d00ab 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/field.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/schema_elements/field.rbs @@ -28,6 +28,7 @@ module ElasticGraph attr_reader resolver: SchemaArtifacts::RuntimeMetadata::ConfiguredGraphQLResolver? attr_reader singular_name: ::String? attr_reader as_input: bool + attr_accessor type_already_final: bool def initialize: ( name: ::String, diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs index 7ea53f98d..c00c07e36 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/state.rbs @@ -32,6 +32,7 @@ module ElasticGraph attr_accessor output: io attr_accessor allow_omitted_json_schema_fields: bool attr_accessor allow_extra_json_schema_fields: bool + attr_reader enums_in_transition: ::Set[::String] def initialize: ( api: API, @@ -65,6 +66,7 @@ module ElasticGraph output: io, allow_omitted_json_schema_fields: bool, allow_extra_json_schema_fields: bool, + enums_in_transition: ::Set[::String], ) -> void end @@ -76,6 +78,7 @@ module ElasticGraph derived_type_name_formats: ::Hash[::Symbol, ::String], type_name_overrides: ::Hash[::String, ::String] | ::Hash[::Symbol, ::String], enum_value_overrides_by_type: ::Hash[::String | ::Symbol, ::Hash[::String | ::Symbol, ::String]], + ?enums_in_transition: ::Set[::String] | ::Array[::String], ?output: io ) -> State diff --git a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/test_support.rbs b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/test_support.rbs index 211039ae3..8e3c51f86 100644 --- a/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/test_support.rbs +++ b/elasticgraph-schema_definition/sig/elastic_graph/schema_definition/test_support.rbs @@ -10,6 +10,7 @@ module ElasticGraph ?derived_type_name_formats: ::Hash[::Symbol, ::String], ?type_name_overrides: ::Hash[::Symbol, ::String], ?enum_value_overrides_by_type: ::Hash[::Symbol, ::Hash[::Symbol, ::String]], + ?enums_in_transition: ::Array[::String], ?output: io?, ?reload_schema_artifacts: bool, ) ?{ (API) -> void } -> _SchemaArtifacts @@ -22,6 +23,7 @@ module ElasticGraph ?derived_type_name_formats: ::Hash[::Symbol, ::String], ?type_name_overrides: ::Hash[::Symbol, ::String], ?enum_value_overrides_by_type: ::Hash[::Symbol, ::Hash[::Symbol, ::String]], + ?enums_in_transition: ::Array[::String], ?output: io?, ?reload_schema_artifacts: bool, ) ?{ (API) -> void } -> _SchemaArtifacts diff --git a/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/enum_type_spec.rb b/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/enum_type_spec.rb index 758571e15..b1edf510a 100644 --- a/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/enum_type_spec.rb +++ b/elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/graphql_schema/enum_type_spec.rb @@ -308,6 +308,107 @@ module SchemaDefinition }.to raise_invalid_graphql_name_error_for("RED GREEN") end + context "with enums_in_transition" do + it "generates a transition input enum alongside the collapsed output enum when the enum is in transition" do + result = define_schema( + type_name_overrides: {"ColorInput" => "Color"}, + enums_in_transition: ["Color"] + ) do |schema| + schema.object_type "Widget" do |t| + t.paginated_collection_field "colors", "Color" + end + + schema.enum_type "Color" do |t| + t.value "RED" + t.value "GREEN" + end + end + + # The output enum is unchanged + expect(type_def_from(result, "Color")).to eq(<<~EOS.strip) + enum Color { + RED + GREEN + } + EOS + + # A transition input enum is generated + expect(type_def_from(result, "ColorInput")).to eq(<<~EOS.strip) + enum ColorInput { + RED + GREEN + } + EOS + end + + it "adds an equal_to_any_of_input filter field alongside equal_to_any_of" do + result = define_schema( + type_name_overrides: {"ColorInput" => "Color"}, + enums_in_transition: ["Color"] + ) do |schema| + schema.object_type "Widget" do |t| + t.paginated_collection_field "colors", "Color" + end + + schema.enum_type "Color" do |t| + t.value "RED" + t.value "GREEN" + end + end + + filter_def = filter_type_from(result, "Color") + + # Existing field unchanged — uses output enum + expect(filter_def).to include("#{schema_elements.equal_to_any_of}: [Color]") + + # New field uses input enum + expect(filter_def).to include("#{schema_elements.equal_to_any_of_input}: [ColorInput]") + end + + it "adds an equal_to_any_of_input filter field with non-null elements in list element filter context" do + result = define_schema( + type_name_overrides: {"ColorInput" => "Color"}, + enums_in_transition: ["Color"] + ) do |schema| + schema.object_type "Widget" do |t| + t.paginated_collection_field "colors", "Color" + end + + schema.enum_type "Color" do |t| + t.value "RED" + t.value "GREEN" + end + end + + filter_def = list_element_filter_type_from(result, "Color") + + # Both fields present in list element filter + expect(filter_def).to include("#{schema_elements.equal_to_any_of}: [Color!]") + expect(filter_def).to include("#{schema_elements.equal_to_any_of_input}: [ColorInput!]") + end + + it "does not generate transition types when enum is not in enums_in_transition" do + result = define_schema( + type_name_overrides: {"ColorInput" => "Color"} + ) do |schema| + schema.object_type "Widget" do |t| + t.paginated_collection_field "colors", "Color" + end + + schema.enum_type "Color" do |t| + t.value "RED" + end + end + + # No ColorInput enum + expect(type_def_from(result, "ColorInput")).to eq nil + + # No equal_to_any_of_input field + filter_def = filter_type_from(result, "Color") + expect(filter_def).not_to include(schema_elements.equal_to_any_of_input.to_s) + end + end + def enum_type(name, *args, **options, &block) result = define_schema do |api| api.enum_type(name, *args, **options, &block) diff --git a/elasticgraph-warehouse/lib/elastic_graph/warehouse/schema_definition/factory_extension.rb b/elasticgraph-warehouse/lib/elastic_graph/warehouse/schema_definition/factory_extension.rb index 72d4cf033..e79992269 100644 --- a/elasticgraph-warehouse/lib/elastic_graph/warehouse/schema_definition/factory_extension.rb +++ b/elasticgraph-warehouse/lib/elastic_graph/warehouse/schema_definition/factory_extension.rb @@ -25,8 +25,8 @@ module FactoryExtension # @param name [String] the name of the enum type # @yield [ElasticGraph::SchemaDefinition::SchemaElements::EnumType] the newly created enum type # @return [ElasticGraph::SchemaDefinition::SchemaElements::EnumType] the created enum type - def new_enum_type(name) - super(name) do |type| + def new_enum_type(name, **kwargs) + super(name, **kwargs) do |type| type.extend EnumTypeExtension # :nocov: -- currently all invocations have a block yield type if block_given? diff --git a/spec_support/lib/elastic_graph/spec_support/schema_definition_helpers.rb b/spec_support/lib/elastic_graph/spec_support/schema_definition_helpers.rb index 0c3cf8538..b262bbbd8 100644 --- a/spec_support/lib/elastic_graph/spec_support/schema_definition_helpers.rb +++ b/spec_support/lib/elastic_graph/spec_support/schema_definition_helpers.rb @@ -21,6 +21,7 @@ def define_schema_with_schema_elements( derived_type_name_formats: {}, type_name_overrides: {}, enum_value_overrides_by_type: {}, + enums_in_transition: [], reload_schema_artifacts: false, output: nil ) @@ -32,6 +33,7 @@ def define_schema_with_schema_elements( derived_type_name_formats: derived_type_name_formats, type_name_overrides: type_name_overrides, enum_value_overrides_by_type: enum_value_overrides_by_type, + enums_in_transition: enums_in_transition, reload_schema_artifacts: false, output: output || log_device )