Skip to content

Add __typename as constant_keyword to single-type index mappings#1171

Merged
myronmarston merged 2 commits into
block:mainfrom
marcdaniels-toast:mdaniels/constant-keyword-typename
May 12, 2026
Merged

Add __typename as constant_keyword to single-type index mappings#1171
myronmarston merged 2 commits into
block:mainfrom
marcdaniels-toast:mdaniels/constant-keyword-typename

Conversation

@marcdaniels-toast
Copy link
Copy Markdown
Contributor

What is changing?

Single-type indices previously omitted __typename from their mappings entirely. This adds __typename as a constant_keyword to the root mapping of every single-type index, using the type name as the value. constant_keyword stores the value once at the index level with zero per-document overhead and supports filtering — so AbstractTypeFilter can now include dedicated-index subtypes in __typename filter values without the nil sentinel workaround that was previously needed.

Why are we making this change?

Besides the nil sentinel cleanup in AbstractTypeFilter mentioned above, this is a prerequisite for _typename filtering (#1169) to match concrete subtypes stored in single-type indices. When querying an abstract type whose subtypes span both shared and dedicated indices, a _typename filter must be able to match documents in both. Without __typename in their mappings, documents in single-type indices have no field to match against — they would always be excluded from results when a _typename filter is applied. Storing __typename as a constant_keyword gives those documents a value to match without any per-document storage cost.

For example, querying retailers with _typename: { equal_to_any_of: ["OnlineStore", "PhysicalStore"] } would target two indices: distribution_channels (where OnlineStore documents live) and physical_stores (where PhysicalStore documents live). The filter is applied against both indexes, but before this change physical_stores had no __typename field — so none of its documents would match:

distribution_channels physical_stores
Before OnlineStore matches ✓ no __typename field — no match ✗
After OnlineStore matches ✓ PhysicalStore matches ✓

The "After" works because physical_stores now has __typename mapped as a constant_keyword with value PhysicalStore.

Why only root types?

This adds __typename only 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 __typename to every concrete nested object type for consistency. However, the known spots that conditionally use __typenameresolve_type and all_highlights — both fall back to document_types_stored_in only 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 the constant_keyword __typename value alongside each hit, thereby eliminating the need for document_types_stored_in. But the fields API returns constant_keyword values only for root-level fields, not for nested object properties — so nested concrete types can't contribute to that goal even if they had constant_keyword.

Since I can't identify an immediate use for constant_keyword __typename on nested concrete types — even as a stepping stone toward eliminating document_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?

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
Copy link
Copy Markdown
Collaborator

@myronmarston myronmarston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I can't identify an immediate use for constant_keyword __typename on nested concrete types — even as a stepping stone toward eliminating document_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.

@myronmarston myronmarston enabled auto-merge (squash) May 12, 2026 03:14
@myronmarston myronmarston disabled auto-merge May 12, 2026 06:05
@myronmarston myronmarston merged commit d5074db into block:main May 12, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants