Skip to content

Search

kmrs ports Komga’s Lucene search to tantivy: the multilingual analyzer chain behaves like the Java version, while the query syntax is deliberately smaller (see below). This page describes the query syntax, the analyzer chain, and the recall extensions kmrs adds on top.

Query syntax

A query is whitespace-separated terms, all of which must match (AND). Outside field: scoping, everything is literal text: ~, *, ^, quotes and parens carry no operator meaning, so pasting a title from anywhere can never produce a query error or an empty result because of a stray character.

The only operator is field scoping, like Komga:

  • field:term — status:ongoing, publisher:vertigo
  • field:(a b) — terms ANDed under the field: author:(sean murphy)
  • field:(a OR b) — terms ORed under the field: status:(error OR unsupported)
  • field:"a b" — the quoted form of the AND group: tag:"double pages"

A term that analyzes to zero tokens (pure punctuation, decorative symbols like ▪ ◆ ★) is silently dropped from the query instead of failing it.

The fields are the ones Komga documents: title, isbn, name (collections and readlists), author (any role), writer, penciller, inker, colorist, letterer, cover, editor, translator, publisher, status, reading_direction, age_rating, language, tag, series_tag, book_tag, genre, sharing_label, total_book_count, book_count, release_date, deleted, oneshot, complete. Without a field, terms match the series or book title (plus the ISBN for books) and the collection or readlist name.

The analyzer chain

Both the index side and the query side share one pipeline (Komga’s MultiLingualAnalyzer); the index side appends an n-gram filter (Komga’s MultiLingualNGramAnalyzer, minGram 3, maxGram 10, preserveOriginal), which is what makes substring matching work:

  1. t2s — traditional → simplified Chinese conversion (kmrs extension, see below)
  2. standard tokenize — UAX#29 word segmentation: Han/Hiragana characters one per token, katakana runs stay together, Latin/digit runs
  3. CJK width — fullwidth ASCII → ASCII, halfwidth katakana → fullwidth
  4. lowercase
  5. CJK bigram — sliding bigrams over CJK runs; the index side also emits every CJK character as a unigram (kmrs extension, see below)
  6. ASCII fold — accents stripped (café → cafe)

Extensions over the Java version

These change recall only: the index format is versioned (see below), and nothing in the data directory or the API is affected.

CJK unigrams

Java’s CJKBigramFilter indexes a CJK run as sliding bigrams plus a trailing unigram, so a character that only appears mid-run has no index entry of its own.

That breaks ANDed queries: Komga’s defaultOperator = AND requires every analyzed token to resolve, and the query’s trailing unigram resolves nowhere when that character sits mid-run. 可爱 analyzes to +可爱 +爱, but 爱 exists in the Java index only inside the bigrams 可爱/爱对 — so 可爱 cannot match 我的可愛對黑岩目高不管用. Only substrings aligned with a run boundary match at all.

kmrs indexes every CJK character as a unigram (Lucene’s outputUnigrams mode), so every query-side token resolves: 可爱 and 我的 match mid-run, 3月 matches 3月的狮子, and single-character queries like 王 work anywhere in a run.

The added unigrams take positions that keep the bigrams’ relative spacing intact (run-initial unigram → the run’s first position; mid-run unigram → the position of the bigram it starts; trailing unigram → its own position), preserving the established index layout.

CJK prefix fallback

A bare query term that runs past the end of the title misses in the Java version, because every analyzed CJK bigram is ANDed: 葬送的芙莉莲系列 never matches the title 葬送的芙莉莲, as the query’s trailing bigrams (莲系, 系列) exist in no title. When such a query yields nothing, kmrs retries it with trailing analyzed tokens dropped one by one, longest prefix first, and stops at the first hit — the query above matches at its first five tokens.

The relaxation is deliberately narrow:

  • only a suffix of CJK bigram-chain tokens may be dropped — whole-word (Latin/digit) clauses are never relaxed, so JOJO的奇妙冒险系列 matches JOJO的奇妙冒险, but Frieren系列完全版 never degrades to a bare Frieren;
  • the retained prefix keeps at least two tokens;
  • only plain search-box terms qualify — field-scoped and grouped queries are untouched, so a miss stays a miss otherwise.

Fuzzy fallback

When a bare query (no field scoping, no groups) matches nothing even after the prefix fallback above, kmrs retries it with bounded edit-distance tolerance per token: Latin words of 3–4 characters tolerate one edit, longer words two, and pure numbers of 2+ digits two — so a typo (bersrk → berserk) or a slightly-off volume upper bound (Vol.01-Vol.19 against a title carrying Vol.01-Vol.20) still resolves. CJK tokens always stay exact: a one-edit tolerance on two-character bigrams would flood recall, and tokens shorter than three characters stay exact for the same reason.

The retry only fires when the exact query found nothing, so fuzzy matches never dilute an exact hit set.

Index and query text are both normalized to simplified Chinese (OpenCC conversion tables) before tokenization, so the two scripts cross-match in both directions: a traditional query 名偵探柯南 finds the simplified title 名侦探柯南, and a simplified query finds traditional titles.

Two properties of the mapping are worth knowing:

  • It is many-to-one (乾/幹 → 干, 髮/發 → 发), so a few titles can over-merge; phrase-level rules keep the classic cases intact (乾隆 stays 乾隆, while 乾燥 → 干燥).
  • Japanese shinjitai kanji collapse to the simplified forms as well (東京 → 东京), so Chinese queries also match the kanji part of Japanese titles; kana is untouched.

Index versioning and rebuilds

The index directory carries an analyzer-version marker (.kmrs-search-analyzer-version). On startup:

  • version matches — the index is opened as-is;
  • version mismatch or missing marker — the index is wiped and rebuilt automatically in the background;
  • a Java Lucene index is found — it is wiped and rebuilt the same way.

Files that do not belong to a search index are never deleted, so pointing kmrs at a directory with other content is safe (it just builds the index alongside them).

Known deviations from the Java version

  • The query syntax is deliberately smaller than Lucene’s (see Query syntax): boolean operators (AND/OR/NOT, &&/||/!, +/-), fuzzy (~), wildcards, phrases, boosts (^) and ranges ([a TO b]) are literal text, not operators — with the one exception of OR/AND inside a field:(…) group. A query Komga would interpret (batman NOT publisher:dc, release_date:[1990 TO 2000]) is searched as plain terms instead.
  • komga.lucene.index-analyzer.* and komga.lucene.commit-delay are ignored; the analyzer is fixed to the chain described above.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close