quickwit-oss/quickwit · error

normalizer generated multiple tokens

Error message

normalizer generated multiple tokens

What it means

Wildcard queries are implemented by normalizing the literal portions of the pattern and matching them as exact tokens within a regex. sub_query_parts_to_regex expects the normalizer to produce exactly one token per literal part; if the token stream yields a second token the pattern cannot be matched deterministically, so it bails with this error.

Solutions

  1. Remove separator characters (spaces, hyphens) from the wildcard pattern's literal parts.
  2. Run wildcard queries against a keyword-type field with a lowercase normalizer instead of a tokenized text field.
  3. Escape or restructure the pattern, or use a regex query where multi-token matching is intended.

Example fix

// before
WildcardQuery { field: "title", pattern: "quick brown*" }
// after
WildcardQuery { field: "title", pattern: "quickbrown*" } // or match against a keyword field
Defensive patterns

Strategy: validation

Validate before calling

if wildcard_pattern.split(|c: char| c.is_whitespace() || c == '-').filter(|s| !s.is_empty()).count() > 1 {
    return Err("wildcard pattern literal contains separators; use a keyword field or simplify the pattern");
}

Try / catch

match build_wildcard_query(pattern) {
    Err(e) if e.to_string().contains("normalizer generated multiple tokens") => {
        // sanitize pattern or switch to a regex/keyword field
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling to_regex (via sub_query_parts_to_regex) for a WildcardQuery whose literal part, after applying the field's normalizer, produces multiple tokens - typically because the literal contains characters the normalizer splits on, like spaces, hyphens, or other separators, e.g. pattern `foo bar*`.

Common situations: Wildcard patterns containing whitespace or punctuation while the field's normalizer tokenizes them into several tokens; using wildcard queries on text fields tokenized with aggressive analyzers instead of keyword/normalized fields.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/3e07169f504ba946. Report an issue: GitHub.

Appendix: source

Thrown at quickwit/quickwit-query/src/query_ast/wildcard_query.rs:100

    tokenizer_name: &str,
    tokenizer_manager: &TokenizerManager,
) -> anyhow::Result<String> {
    let mut normalizer = tokenizer_manager
        .get_normalizer(tokenizer_name)
        .with_context(|| format!("no tokenizer named `{tokenizer_name}` is registered"))?;

    sub_query_parts
        .into_iter()
        .map(|part| match part {
            SubQuery::Text(text) => {
                let mut token_stream = normalizer.token_stream(&text);
                let expected_token = token_stream
                    .next()
                    .context("normalizer generated no content")?
                    .text
                    .clone();
                if let Some(_unexpected_token) = token_stream.next() {
                    bail!("normalizer generated multiple tokens")
                }
                Ok(Cow::Owned(regex::escape(&expected_token)))
            }
            SubQuery::Wildcard => Ok(Cow::Borrowed(".*")),
            SubQuery::QuestionMark => Ok(Cow::Borrowed(".")),
        })
        .collect::<Result<String, _>>()
}

impl WildcardQuery {
    pub fn to_regex(
        &self,
        schema: &TantivySchema,
        tokenizer_manager: &TokenizerManager,
    ) -> Result<(Field, Option<Vec<u8>>, String), InvalidQuery> {
        let Some((field, field_entry, json_path)) = find_field_or_hit_dynamic(&self.field, schema)
        else {
            return Err(InvalidQuery::FieldDoesNotExist {

View on GitHub (pinned to a39730c5cd)