Skip to content

Honor ? wildcard for constant_keyword wildcard query - #148585

Merged
benwtrent merged 4 commits into
elastic:mainfrom
daguimu:fix/constant-keyword-question-wildcard-141785
May 15, 2026
Merged

benwtrent merged 4 commits into
elastic:mainfrom
daguimu:fix/constant-keyword-question-wildcard-141785

Conversation

@daguimu

@daguimu daguimu commented May 8, 2026

Copy link
Copy Markdown
Contributor

Problem

A wildcard query against a constant_keyword field treats ? as a literal character instead of "match exactly one character". Per the wildcard query docs, ? and * are both wildcard metacharacters.

PUT test-wildcard
{ "mappings": { "properties": { "ck": { "type": "constant_keyword", "value": "foobar" } } } }

POST test-wildcard/_doc?refresh
{ "ck": "foobar" }

GET test-wildcard/_search
{ "query": { "wildcard": { "ck": { "value": "f?o*" } } } }
# expected: 1 hit, actual: 0 hits

f*o* and other patterns containing only * work as expected — only ? is broken.

Root Cause

ConstantFieldType#wildcardQuery (final) delegates to the abstract matches(String pattern, boolean caseInsensitive, QueryRewriteContext). ConstantKeywordFieldType#matches is implemented with Regex.simpleMatch, which only handles * (per its own Javadoc — "xxx*", "xxx", "xxx", "xxxyyy"). When the pattern contains ?, simpleMatch falls through to literal equality and returns false.

The same matches() is reused by termQuery/termsQuery/prefixQuery where ? should remain literal, so we cannot simply change matches() to honour ?.

Fix

  • ConstantFieldType#wildcardQuery(String, boolean, QueryRewriteContext): drop final so subclasses can plug in their own wildcard matcher when simpleMatch semantics are wrong for them. The default still calls matches(), so behaviour for _index, _index_mode, _tier, etc. is unchanged.
  • ConstantKeywordFieldType: override wildcardQuery to compile the pattern with WildcardQuery.toAutomaton and run the resulting CharacterRunAutomaton against the constant value. This mirrors what IndexFieldType already does for its wildcardLikeQuery path.

Tests Added

Change Test
? matches exactly one character (f?o, ???, f?o*) ConstantKeywordFieldTypeTests#testWildcardQueryWithQuestionMark
? matches exactly one — f? does not match foo same test
null constant value short-circuits to NO_DOCS same test

The pre-existing testWildcardQuery continues to cover *-only patterns and case-insensitive matching.

Impact

  • wildcard queries against constant_keyword fields now honour ? as a single-character wildcard, matching the documented semantics.
  • No change to term, terms, prefix, or regexp queries on constant_keyword.
  • No transport-layer or wire-format change.

Closes #141785

@elasticsearchmachine elasticsearchmachine added external-contributor Pull request authored by a developer outside the Elasticsearch team needs:triage Requires assignment of a team area label v9.5.0 labels May 8, 2026
daguimu added a commit to daguimu/elasticsearch that referenced this pull request May 8, 2026
@john-wagster john-wagster added the :Search Relevance/Search Catch all for Search Relevance label May 12, 2026
@elasticsearchmachine elasticsearchmachine added Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch and removed needs:triage Requires assignment of a team area label labels May 12, 2026
@elasticsearchmachine

Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-search-relevance (Team:Search Relevance)

@john-wagster john-wagster added >bug needs:triage Requires assignment of a team area label and removed Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch labels May 12, 2026
@elasticsearchmachine elasticsearchmachine added Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch and removed needs:triage Requires assignment of a team area label labels May 12, 2026
Comment on lines +239 to +244
Automaton automaton;
try {
automaton = WildcardQuery.toAutomaton(new Term(name(), matchPattern), Operations.DEFAULT_DETERMINIZE_WORK_LIMIT);
} catch (TooComplexToDeterminizeException e) {
throw new IllegalArgumentException("Pattern was too complex to determinize", e);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's do the same thing that was done in the KeywordFieldMapper. So something like:

                    Term term = new Term(name(), value);
                    if (context.getCircuitBreaker() != null) {
                        Automaton dfa = AutomatonQueries.toWildcardAutomaton(term, context.getCircuitBreaker());
                        return new AutomatonQuery(term, dfa, false, MultiTermQuery.DOC_VALUES_REWRITE);
                    }
                    return new WildcardQuery(term, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT, MultiTermQuery.DOC_VALUES_REWRITE);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — switched to AutomatonQueries.toWildcardAutomaton(term, circuitBreaker) so the NFA→DFA determinization heap is tracked alongside the other wildcard queries.

One deviation worth flagging: constant_keyword has no Lucene index (ConstantFieldType uses IndexType.NONE; parseCreateField doesn't write the value), so returning an AutomatonQuery / WildcardQuery against the field would scan an empty index and always return zero docs. I kept the in-memory CharacterRunAutomaton.run(value) → ALL_DOCS_INSTANCE / NO_DOCS_INSTANCE shape, matching the existing ConstantFieldType#automatonQuery pattern and the rest of the matches()-based queries on this type. Happy to switch approach if you'd prefer something else.

daguimu added 3 commits May 15, 2026 13:53
ConstantKeywordFieldType#matches uses Regex.simpleMatch, which only
treats * as a wildcard. The wildcard query DSL also defines ? as
matching exactly one character, so patterns like f?o failed against
constant values that should match. Override wildcardQuery on the
constant_keyword field type to use Lucene's WildcardQuery automaton,
matching the semantics of other text field types.

Also drop the final modifier from ConstantFieldType#wildcardQuery so
constant-value field types can supply their own matcher when
simpleMatch semantics are wrong for them.

Closes elastic#141785
Compile via AutomatonQueries.toWildcardAutomaton when the
SearchExecutionContext exposes a circuit breaker, so the NFA->DFA
determinization heap is tracked alongside other wildcard queries.
Falls back to work-limit-only WildcardQuery.toAutomaton when no
breaker is available (e.g. coordinator rewrite paths).
@daguimu
daguimu force-pushed the fix/constant-keyword-question-wildcard-141785 branch from 02c9d65 to 43ecc0b Compare May 15, 2026 05:54
@benwtrent benwtrent self-assigned this May 15, 2026
@benwtrent

Copy link
Copy Markdown
Contributor

buildkite test this please

@benwtrent
benwtrent merged commit e18ad80 into elastic:main May 15, 2026
39 checks passed
@benwtrent

Copy link
Copy Markdown
Contributor

Thank you for the contribution @daguimu!

@daguimu
daguimu deleted the fix/constant-keyword-question-wildcard-141785 branch May 15, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

>bug external-contributor Pull request authored by a developer outside the Elasticsearch team :Search Relevance/Search Catch all for Search Relevance Team:Search Relevance Meta label for the Search Relevance team in Elasticsearch v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Constant-value fields do not handle "?" wildcards in wildcard queries

4 participants