Skip to content

Commit 9789e46

Browse files
myronmarstonclaude
andcommitted
Add _typename filter field to abstract type filter inputs
Enables filtering union/interface types by concrete subtype. Uses single underscore (`_typename`) since GraphQL spec prohibits `__` prefix on input fields. Also fixes CamelCaseConverter to preserve leading underscores via lookbehind assertion. This is step 1 for #1024. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1433121 commit 9789e46

8 files changed

Lines changed: 86 additions & 10 deletions

File tree

config/schema/artifacts/schema.graphql

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3545,6 +3545,12 @@ Input type used to specify filters on `Inventor` fields.
35453545
Will match all documents if passed as an empty object (or as `null`).
35463546
"""
35473547
input InventorFilterInput {
3548+
"""
3549+
Used to filter `Inventor` documents by which concrete subtype they belong to.
3550+
Analogous to the `__typename` return field.
3551+
"""
3552+
_typename: StringFilterInput
3553+
35483554
"""
35493555
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
35503556
@@ -5697,6 +5703,12 @@ Input type used to specify filters on `NamedEntity` fields.
56975703
Will match all documents if passed as an empty object (or as `null`).
56985704
"""
56995705
input NamedEntityFilterInput {
5706+
"""
5707+
Used to filter `NamedEntity` documents by which concrete subtype they belong to.
5708+
Analogous to the `__typename` return field.
5709+
"""
5710+
_typename: StringFilterInput
5711+
57005712
"""
57015713
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
57025714
@@ -6786,6 +6798,12 @@ Input type used to specify filters on `NamedInventor` fields.
67866798
Will match all documents if passed as an empty object (or as `null`).
67876799
"""
67886800
input NamedInventorFilterInput {
6801+
"""
6802+
Used to filter `NamedInventor` documents by which concrete subtype they belong to.
6803+
Analogous to the `__typename` return field.
6804+
"""
6805+
_typename: StringFilterInput
6806+
67896807
"""
67906808
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
67916809
@@ -7080,6 +7098,12 @@ Input type used to specify filters on `Part` fields.
70807098
Will match all documents if passed as an empty object (or as `null`).
70817099
"""
70827100
input PartFilterInput {
7101+
"""
7102+
Used to filter `Part` documents by which concrete subtype they belong to.
7103+
Analogous to the `__typename` return field.
7104+
"""
7105+
_typename: StringFilterInput
7106+
70837107
"""
70847108
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
70857109
@@ -14737,6 +14761,12 @@ Input type used to specify filters on `WidgetOrAddress` fields.
1473714761
Will match all documents if passed as an empty object (or as `null`).
1473814762
"""
1473914763
input WidgetOrAddressFilterInput {
14764+
"""
14765+
Used to filter `WidgetOrAddress` documents by which concrete subtype they belong to.
14766+
Analogous to the `__typename` return field.
14767+
"""
14768+
_typename: StringFilterInput
14769+
1474014770
"""
1474114771
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
1474214772

config/schema/artifacts_with_apollo/schema.graphql

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,6 +3827,12 @@ Input type used to specify filters on `Inventor` fields.
38273827
Will match all documents if passed as an empty object (or as `null`).
38283828
"""
38293829
input InventorFilterInput {
3830+
"""
3831+
Used to filter `Inventor` documents by which concrete subtype they belong to.
3832+
Analogous to the `__typename` return field.
3833+
"""
3834+
_typename: StringFilterInput
3835+
38303836
"""
38313837
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
38323838
@@ -5979,6 +5985,12 @@ Input type used to specify filters on `NamedEntity` fields.
59795985
Will match all documents if passed as an empty object (or as `null`).
59805986
"""
59815987
input NamedEntityFilterInput {
5988+
"""
5989+
Used to filter `NamedEntity` documents by which concrete subtype they belong to.
5990+
Analogous to the `__typename` return field.
5991+
"""
5992+
_typename: StringFilterInput
5993+
59825994
"""
59835995
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
59845996
@@ -7068,6 +7080,12 @@ Input type used to specify filters on `NamedInventor` fields.
70687080
Will match all documents if passed as an empty object (or as `null`).
70697081
"""
70707082
input NamedInventorFilterInput {
7083+
"""
7084+
Used to filter `NamedInventor` documents by which concrete subtype they belong to.
7085+
Analogous to the `__typename` return field.
7086+
"""
7087+
_typename: StringFilterInput
7088+
70717089
"""
70727090
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
70737091
@@ -7362,6 +7380,12 @@ Input type used to specify filters on `Part` fields.
73627380
Will match all documents if passed as an empty object (or as `null`).
73637381
"""
73647382
input PartFilterInput {
7383+
"""
7384+
Used to filter `Part` documents by which concrete subtype they belong to.
7385+
Analogous to the `__typename` return field.
7386+
"""
7387+
_typename: StringFilterInput
7388+
73657389
"""
73667390
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
73677391
@@ -15060,6 +15084,12 @@ Input type used to specify filters on `WidgetOrAddress` fields.
1506015084
Will match all documents if passed as an empty object (or as `null`).
1506115085
"""
1506215086
input WidgetOrAddressFilterInput {
15087+
"""
15088+
Used to filter `WidgetOrAddress` documents by which concrete subtype they belong to.
15089+
Analogous to the `__typename` return field.
15090+
"""
15091+
_typename: StringFilterInput
15092+
1506315093
"""
1506415094
Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL.
1506515095

elasticgraph-apollo/spec/unit/elastic_graph/apollo/schema_definition_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,7 @@ def expect_identifiable_type_tagging_of_token(&type_def_for)
13171317
# the tagging of those source fields. That's why `name`, `options1`, etc are tagged with `public` below.
13181318
expect(type_def_for.call("IdentifiableFilterInput")).to eq(<<~EOS.strip)
13191319
input IdentifiableFilterInput {
1320+
#{schema_elements._typename}: StringFilterInput
13201321
#{schema_elements.all_of}: [IdentifiableFilterInput!]
13211322
#{schema_elements.any_of}: [IdentifiableFilterInput!]
13221323
id: IDFilterInput

elasticgraph-schema_artifacts/lib/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ module CamelCaseConverter
123123
extend self
124124

125125
def normalize_case(name)
126-
name.gsub(/_(\w)/) { $1.upcase }
126+
name.gsub(/(?<=\w)_(\w)/) { $1.upcase }
127127
end
128128
end
129129

@@ -136,7 +136,7 @@ def normalize_case(name)
136136
# @private
137137
SchemaElementNames = SchemaElementNamesDefinition.new(
138138
# Filter arg and operation names:
139-
:filter,
139+
:filter, :_typename,
140140
:equal_to_any_of, :gt, :gte, :lt, :lte, :matches_phrase, :matches_query, :matches_query_with_prefix, :any_of, :all_of, :not,
141141
:time_of_day, :any_satisfy, :contains, :starts_with, :all_substrings_of, :any_substring_of, :ignore_case, :any_prefix_of,
142142
# Directives

elasticgraph-schema_artifacts/sig/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names.rbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module ElasticGraph
1717

1818
def canonical_name_for: (::String | ::Symbol) -> ::Symbol
1919
attr_reader filter: ::String
20+
attr_reader _typename: ::String
2021
attr_reader equal_to_any_of: ::String
2122
attr_reader gt: ::String
2223
attr_reader gte: ::String

elasticgraph-schema_artifacts/spec/unit/elastic_graph/schema_artifacts/runtime_metadata/schema_element_names_spec.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ module ElasticGraph
1212
module SchemaArtifacts
1313
module RuntimeMetadata
1414
ExampleElementNames = SchemaElementNamesDefinition.new(
15-
:foo, :multi_word_snake, :multiWordCamel
15+
:foo, :multi_word_snake, :multiWordCamel, :_leading_underscore
1616
)
1717

1818
RSpec.describe SchemaElementNamesDefinition do
1919
it "exposes the set of element names via an `ELEMENT_NAMES` constant" do
20-
expect(ExampleElementNames::ELEMENT_NAMES).to eq [:foo, :multi_word_snake, :multiWordCamel]
20+
expect(ExampleElementNames::ELEMENT_NAMES).to eq [:foo, :multi_word_snake, :multiWordCamel, :_leading_underscore]
2121
end
2222

2323
it "exposes camelCase element names when so configured, via snake case attributes" do
@@ -26,7 +26,8 @@ module RuntimeMetadata
2626
expect(names).to have_attributes(
2727
foo: "foo",
2828
multi_word_snake: "multiWordSnake",
29-
multi_word_camel: "multiWordCamel"
29+
multi_word_camel: "multiWordCamel",
30+
_leading_underscore: "_leadingUnderscore"
3031
)
3132
end
3233

@@ -36,7 +37,8 @@ module RuntimeMetadata
3637
expect(names).to have_attributes(
3738
foo: "foo",
3839
multi_word_snake: "multi_word_snake",
39-
multi_word_camel: "multi_word_camel"
40+
multi_word_camel: "multi_word_camel",
41+
_leading_underscore: "_leading_underscore"
4042
)
4143
end
4244

@@ -54,6 +56,7 @@ module RuntimeMetadata
5456
expect(names.normalize_case("foo_bar")).to eq "fooBar"
5557
expect(names.normalize_case("fooBar")).to eq "fooBar"
5658
expect(names.normalize_case("FooBar")).to eq "FooBar"
59+
expect(names.normalize_case("_typename")).to eq "_typename"
5760
end
5861

5962
it "allows overrides" do

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ def to_input_filters
8787
return [] if does_not_support?(&:filterable?)
8888

8989
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 do |f|
92+
f.documentation <<~EOS
93+
Used to filter `#{name}` documents by which concrete subtype they belong to.
94+
Analogous to the `__typename` return field.
95+
EOS
96+
end
97+
end
98+
9099
graphql_fields_by_name.values.each do |field|
91100
if field.filterable?
92101
t.graphql_fields_by_name[field.name] = field.to_filter_field(parent_type: t)

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,15 +1148,14 @@ module SchemaDefinition
11481148
end
11491149
end
11501150

1151-
# Note: we would like to support filtering on `__typename` but this is invalid according to the
1152-
# GraphQL Spec: http://spec.graphql.org/June2018/#sec-Input-Objects
1153-
# > For each input field of an Input Object type:
1154-
# > 2. The input field must not have a name which begins with the characters "__" (two underscores).
1151+
# Uses `_typename` (single underscore) since the GraphQL spec prohibits `__` prefix on input fields:
1152+
# http://spec.graphql.org/June2018/#sec-Input-Objects
11551153
expect(filter_type_from(result, "Inventor")).to eq(<<~EOS.strip)
11561154
input InventorFilterInput {
11571155
#{schema_elements.any_of}: [InventorFilterInput!]
11581156
#{schema_elements.all_of}: [InventorFilterInput!]
11591157
#{schema_elements.not}: InventorFilterInput
1158+
#{schema_elements._typename}: StringFilterInput
11601159
name: StringFilterInput
11611160
nationality: StringFilterInput
11621161
stock_ticker: StringFilterInput
@@ -1222,6 +1221,7 @@ module SchemaDefinition
12221221
#{schema_elements.any_of}: [ClothingItemFilterInput!]
12231222
#{schema_elements.all_of}: [ClothingItemFilterInput!]
12241223
#{schema_elements.not}: ClothingItemFilterInput
1224+
#{schema_elements._typename}: StringFilterInput
12251225
size: SizeFilterInput
12261226
shirt_color: StringFilterInput
12271227
pants_color: StringFilterInput
@@ -1279,6 +1279,7 @@ module SchemaDefinition
12791279
#{schema_elements.any_of}: [InventorFilterInput!]
12801280
#{schema_elements.all_of}: [InventorFilterInput!]
12811281
#{schema_elements.not}: InventorFilterInput
1282+
#{schema_elements._typename}: StringFilterInput
12821283
name: StringFilterInput
12831284
stock_ticker: StringFilterInput
12841285
}
@@ -1340,6 +1341,7 @@ def link_supertype_to_subtypes(interface_type, *subtype_names)
13401341
#{schema_elements.any_of}: [InventorFilterInput!]
13411342
#{schema_elements.all_of}: [InventorFilterInput!]
13421343
#{schema_elements.not}: InventorFilterInput
1344+
#{schema_elements._typename}: StringFilterInput
13431345
name: StringFilterInput
13441346
nationality: StringFilterInput
13451347
stock_ticker: StringFilterInput

0 commit comments

Comments
 (0)