Add __typename as constant_keyword to single-type index mappings#1171
Conversation
Previously, single-type indices omitted __typename from their mappings entirely. This adds __typename as a constant_keyword (storing the type name once at the index level with zero per-document overhead), enabling __typename filtering to work uniformly whether a type has its own index or inherits one from an abstract parent. AbstractTypeFilter previously included nil in its equal_to_any_of filter values when querying abstract types that span single-type indices — nil was needed to let those documents through since they lacked __typename entirely. With constant_keyword, those documents match by their type name directly, so nil is no longer needed. Generated with Claude Code
myronmarston
left a comment
There was a problem hiding this comment.
Since I can't identify an immediate use for
constant_keyword__typenameon nested concrete types — even as a stepping stone toward eliminatingdocument_types_stored_in— I've decided not to include them speculatively. Do you agree we should limit this to root types only
Yes, I agree we should limit it. I thought of another reason: Elasticsearch/OpenSearch do not allow the mappings of an existing field to change on an existing index. That means that we cannot evolve a constant_keyword field to being a keyword field at a later point. I'm OK with taking on that restriction for root types, because:
- There's a concrete benefit to doing so--it unlocks the full functionality of #1169 and simplifies
AbstractTypeFilter. - The index topology--e.g. what types live in which indices, etc--is a very fundamental part of your schema that should rarely change, if ever. If a concrete indexed type is being converted into an abstract indexed type on an existing ElasticGraph project, you're going to think carefully about the index layout anyway.
With embedded types, there's not a concrete benefit and it's more likely that a schema would involve to cross the concrete/abstract boundary. So not worth it to include __typename as a constant_keyword in that case, IMO.
What is changing?
Single-type indices previously omitted
__typenamefrom their mappings entirely. This adds__typenameas aconstant_keywordto the root mapping of every single-type index, using the type name as the value.constant_keywordstores the value once at the index level with zero per-document overhead and supports filtering — soAbstractTypeFiltercan now include dedicated-index subtypes in__typenamefilter values without thenilsentinel workaround that was previously needed.Why are we making this change?
Besides the
nilsentinel cleanup inAbstractTypeFiltermentioned above, this is a prerequisite for_typenamefiltering (#1169) to match concrete subtypes stored in single-type indices. When querying an abstract type whose subtypes span both shared and dedicated indices, a_typenamefilter must be able to match documents in both. Without__typenamein their mappings, documents in single-type indices have no field to match against — they would always be excluded from results when a_typenamefilter is applied. Storing__typenameas aconstant_keywordgives those documents a value to match without any per-document storage cost.For example, querying
retailerswith_typename: { equal_to_any_of: ["OnlineStore", "PhysicalStore"] }would target two indices:distribution_channels(whereOnlineStoredocuments live) andphysical_stores(wherePhysicalStoredocuments live). The filter is applied against both indexes, but before this changephysical_storeshad no__typenamefield — so none of its documents would match:distribution_channelsphysical_storesOnlineStorematches ✓__typenamefield — no match ✗OnlineStorematches ✓PhysicalStorematches ✓The "After" works because
physical_storesnow has__typenamemapped as aconstant_keywordwith valuePhysicalStore.Why only root types?
This adds
__typenameonly to the root mapping of each single-type index, not to nested concrete object types (e.g. Manufacturer.ceo: Person).I considered going further and adding
constant_keyword__typenameto every concrete nested object type for consistency. However, the known spots that conditionally use__typename—resolve_typeandall_highlights— both fall back todocument_types_stored_inonly for root documents from single-type indices. Suppose we do a follow-on optimization to eliminate those fallbacks. We could do that by adding"fields": ["__typename"]to search requests so the datastore returns theconstant_keyword__typenamevalue alongside each hit, thereby eliminating the need fordocument_types_stored_in. But the fields API returnsconstant_keywordvalues only for root-level fields, not for nested object properties — so nested concrete types can't contribute to that goal even if they hadconstant_keyword.Since I can't identify an immediate use for
constant_keyword__typenameon nested concrete types — even as a stepping stone toward eliminatingdocument_types_stored_in— I've decided not to include them speculatively. Do you agree we should limit this to root types only, or do you want me to add it to all concrete object types in case a use case emerges in the future?