Skip to content

feat(site-search): allow overriding the index analyzer via SITE_SEARCH_ANALYZER - #36840

Open
swicken wants to merge 5 commits into
mainfrom
issue-36839-sitesearch-analyzer-override
Open

feat(site-search): allow overriding the index analyzer via SITE_SEARCH_ANALYZER#36840
swicken wants to merge 5 commits into
mainfrom
issue-36839-sitesearch-analyzer-override

Conversation

@swicken

@swicken swicken commented Jul 31, 2026

Copy link
Copy Markdown
Member

Proposed Changes

  • Add a SITE_SEARCH_ANALYZER Config property (env: DOT_SITE_SEARCH_ANALYZER) naming any analyzer known to the search cluster — built-in language analyzers (cjk, arabic, thai, ...) or plugin-provided ones (kuromoji, nori, smartcn). When set, it is applied at index creation to the text fields of the site-search mapping (content, content_raw, title, description, author) on both the Elasticsearch and OpenSearch providers, and the content.ngram subfield's search_analyzer follows it so query-time tokenization stays consistent. Unset keeps the bundled defaults byte-for-byte.
  • Declare content_raw explicitly in both bundled mappings. The publishers add it to every text/* document but the mapping never declared it, so it was dynamically mapped with the standard analyzer — and since site-search queries use query_string with default_field:"*", it silently reintroduced CJK unigram false positives even with the override applied. Declared type matches the previous dynamic mapping, so the unset path is unchanged.
  • Centralize settings/mapping resource loading in SiteSearchIndexResources (also fixes the ES path reading resources via new File(url.getPath()), which breaks when packaged inside a JAR), with a loud DotSearchException on missing resources or a failed override instead of silently building an index with default analyzers.

Why

Site Search hardcodes an English-tuned analyzer chain (standard tokenizer + lowercase + asciifolding + English stemmer). CJK text degrades to single-character tokens (a query for 東京 matches any document containing 東 or 京 anywhere) and RTL languages get no normalization or stemming (كتاب does not match الكتاب). Resolves #36839.

Checklist

  • Tests
  • Translations (n/a — no user-facing strings)
  • Security Implications Contemplated (analyzer name is operator-controlled config, inserted via the JSON API — no injection path; value is trimmed and failures are loud)

Additional Info

Verified end-to-end against a live stack, not just unit tests: built the branch image, booted dotCMS + OpenSearch with DOT_SITE_SEARCH_ANALYZER=cjk, published Japanese HTML file assets, and ran a real run-now Site Search job:

  • The dotCMS-created index mapping carries cjk on all text fields, including content_raw.
  • Query 東京 (Tokyo) via the exact query_string/default_field:"*" shape Site Search uses returns only the document about Tokyo Tower; a control document containing 東 and 京 in unrelated words no longer matches (it did on the default mapping, at an identical score).
  • Control queries 建物 and 静か each return exactly their one matching document.
  • A misconfigured analyzer name fails index creation with the engine's analyzer [x] has not been configured in mappings error.

Scope notes:

  • One analyzer per index (a mixed-language site picks one tokenization) — per-language subfields/indices are deliberately out of scope until a customer needs them.
  • Plugin analyzers (kuromoji/nori/smartcn/icu) additionally require the plugin installed on every node of the ES/OS cluster; built-ins need nothing.
  • content_raw (raw un-stripped HTML, no readers anywhere) roughly 2–5×'s the indexed text per document; making it index: false is a good follow-up but changes default-path search behavior, so it is not part of this PR.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @swicken's task in 1m 41s —— View job


I'll analyze this and get back to you.

jcastro-dotcms
jcastro-dotcms previously approved these changes Jul 31, 2026
@mergify

mergify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@swicken

swicken commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

Re the two non-blocking notes:

  1. Key order on the set path — agreed and intended; the byte-for-byte guarantee is only claimed (and only matters) for the unset path. Mapping property order is not significant to either engine.

  2. content.ngram index/query divergence — verified: nothing in the codebase queries content.ngram (it is reachable only via the raw-JSON query path; grep shows no reader). The divergence is also deliberate defense, not an accident: with search_analyzer following the override, a CJK query produces bigrams that cannot match the standard-tokenized edge-ngram index, so the subfield is inert under query_string(default_field:"*"). Leaving its search_analyzer at standard_content would decompose CJK queries into unigrams that DO match single-character edge-grams — reintroducing the same false-positive side door this PR closes for content_raw, through a third field. The E2E result (東京 returning only the genuinely matching doc across all fields) covers this path.

@swicken
swicken enabled auto-merge July 31, 2026 18:02
@swicken
swicken added this pull request to the merge queue Jul 31, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 1, 2026
…H_ANALYZER

Site Search hardcodes an English-tuned analyzer chain (standard
tokenizer + lowercase + asciifolding + English stemmer) into its
bundled index mapping, which tokenizes CJK text into single-character
unigrams and applies no RTL normalization, with no way to change it
short of repackaging the webapp.

Add a SITE_SEARCH_ANALYZER Config property (env:
DOT_SITE_SEARCH_ANALYZER) naming any analyzer known to the cluster —
built-in language analyzers (cjk, arabic, thai, ...) or plugin-provided
ones (kuromoji, nori, smartcn). When set, it is applied to the text
fields (content, title, description, author) of the site-search mapping
at index creation for both the ES and OS providers; the ngram
subfield's search_analyzer follows it so queries tokenize consistently.
Unset keeps the bundled defaults byte-for-byte.

Also fixes the ES path reading its resources via new File(url.getPath()),
which breaks when packaged inside a JAR.
…rom review

E2E testing against a live stack (DOT_SITE_SEARCH_ANALYZER=cjk, real
Site Search job over Japanese file assets) surfaced a gap: the
publishers add a content_raw field (raw pre-HTML-strip copy) to every
text/* document, but the bundled mapping never declared it. OpenSearch
dynamically mapped it with the standard analyzer, and since site search
queries use query_string with default_field:*, CJK unigram false
positives came back through that field even with the override applied
(query 東京 matched a doc containing only 東 and 京 in unrelated words,
via content_raw).

Declare content_raw explicitly as text in both bundled mappings — same
effective analysis as the previous dynamic mapping, so the unset path
is unchanged — and include it in the override's text-field list. With
the fix, the same E2E run returns only the genuinely matching document.

Also fold in review feedback: trim the configured analyzer value
(stray whitespace from env files otherwise reaches the cluster as an
unknown-analyzer error), guard the JSONObject fork's null-returning
toString(), log when the override is applied, correct the javadoc's
description of the default analyzers, and extend the unit test with
byte-identity and blank-value fallback cases.
@swicken
swicken force-pushed the issue-36839-sitesearch-analyzer-override branch from b587b57 to c38d30c Compare August 25, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Allow overriding the Site Search index analyzer via config to support CJK/RTL languages

2 participants