Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,16 @@ 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,
:aggregated_values_customizations, :sort_order_enum_value_customizations, :args,
: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
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
)
Expand All @@ -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
)

Expand Down
Loading