Contents
- How textsearch_intl performs by language
- Summary
- Advice that applies to every language
- Chinese: good
- Japanese: good for kanji and katakana, weak for hiragana
- Thai: use intl_search_ngram
- Lao, Khmer, Burmese: likely good, not benchmarked
- Korean: use intl_search_ngram
- Vietnamese: no gain
- Arabic: better with intl_search; Hebrew: no gain
- English and other spaced languages: same as the default parser
How textsearch_intl performs by language
textsearch_intl splits text with ICU’s word-break rules. For scripts written without spaces (Chinese, Japanese, Thai, Lao, Khmer, Burmese), ICU looks words up in its dictionaries; everywhere else it splits on spaces and punctuation like the default parser does. So it helps most where the default parser fails completely, and it does nothing for problems that come from word forms: particles, clitics, compounds and inflection.
The evidence comes from three places:
- Benchmark: 500,000 Wikipedia titles per language, with recall and overmatch scored on 250 labeled term/title pairs per language (bench/results.md). Recall is the share of real word matches a method returns; overmatch is the share of its results that aren’t real matches.
- Production place names: single-word searches against a table of about 550 million business and place names, comparing textsearch_intl with the default parser (
simple) and a pg_trgm substring index. - Examples: every tokenization below was produced by textsearch_intl on ICU 78.3. Another ICU version can split some dictionary words differently.
Summary
| Language | Verdict | Benchmark recall (default → intl_search → intl_search_ngram) | Overmatch (intl_search / ngram) | Main weakness |
|---|---|---|---|---|
| Chinese | Good | 10% → 93% → 99% | 16% / 18% | Transliterated names split into single characters; some compounds stay whole |
| Japanese | Good for kanji and katakana | 7% → 86% → 100% | 20% / 54% | Hiragana words split into syllables with intl_search; compounds |
| Thai | Good with intl_search_ngram | 5% → 79% → 98% | 44% / 69% | intl_search alone splits unknown names and loanwords unpredictably |
| Lao, Khmer, Burmese | Likely good (not benchmarked) | Same dictionary approach as Thai | ||
| Korean | Good with intl_search_ngram | 47% → 48% → 100% | 1% / 22% | intl_search alone leaves particles and compound names attached |
| Vietnamese | No gain | Words span several space-separated syllables | ||
| Arabic | Better with intl_search |
Prepositions other than و stay attached |
||
| Hebrew | No gain | Articles and prepositions are attached to words | ||
| English and other spaced languages | Same as default | No stemming or compound splitting |
Advice that applies to every language
- Query with
phraseto_tsquery, notplainto_tsquery. When ICU splits a name into pieces, a plain query requires the pieces to appear anywhere in the title, in any order.星巴克(Starbucks) splits into星|巴|克, soplainto_tsquerymatches克巴星餐厅;phraseto_tsqueryrequires them adjacent and in order, and doesn’t. - Use the
intl_searchconfiguration. It applies NFKC, case folding, accent folding and apostrophe removal to each token, so half-width katakana (セブン), full-width Latin (ABC), case (ΑΘΗΝΑΣ,STRASSE), accents (café,ё), apostrophes (McDonald's,Oʻahu), Arabic letter variants (أحمد) and non-ASCII digits (٧) match the way users type them. It also indexes French and Italian words with and without an elided article (L'Occitaneandoccitane), and Arabic words with and without the article. It keeps the Thai and Lao vowels intact, which a plainnormalize(name, NFKC)before tokenizing would split (จำกัด→จํา|กัด). - Use
intl_search_ngramfor Korean, Chinese and Japanese. It adds character unigrams and bigrams for Hangul, Han and kana tokens, so a word matches inside a longer word: particles, compounds and branch names. It costs overmatch, the same as pg_bigm’s, and leaves every other script asintl_searchdoes. - Pair it with an n-gram index for names ICU doesn’t know. Outside Hangul, Han and kana, textsearch_intl matches whole words only. For Thai fragments and partial Latin words, add pg_bigm (or pg_trgm for terms of three or more characters) and OR the two conditions.
Chinese: good
The biggest improvement of any language. On Wikipedia titles, recall goes from 10% with the default parser to 93%, at 16% overmatch against 18% for substring search. On place names, single-word searches for common words found 100–1,200 times more rows than the default parser:
| Search | default parser | textsearch_intl | pg_trgm |
|---|---|---|---|
| 公園 (park) | 315 | 116,892 | timed out |
| 銀行 (bank) | 45 | 54,071 | timed out |
| 機場 (airport) | 44 | 5,027 | timed out |
| 麥當勞 (McDonald’s) | 680 | 722 | 722 |
| 星巴克 (Starbucks) | 922 | 1,169 | 1,173 |
pg_trgm can’t use its index for one- and two-character terms, so those searches read the whole index and timed out at 20 seconds.
Where it falls short:
- Transliterated foreign names aren’t in ICU’s dictionary and come out as single characters:
朱莉娅·加布里埃莱斯基→朱|莉|娅|加布里|埃|莱|斯|基, and星巴克咖啡→星|巴|克|咖啡. A phrase query still finds them, but a plain query overmatches. - Some compounds stay whole.
國際機場(international airport, Traditional) is one token, so a search for機場misses it, while the Simplified国际机场splits into国际|机场. This is most of the 7% the benchmark missed, andintl_search_ngramfinds it (99% recall, 18% overmatch). - Traditional and Simplified characters match each other with
intl_searchandintl_search_ngram, which write traditional characters as simplified:麥當勞finds麦当劳and the other way round.intl_simplekeeps them apart.
Japanese: good for kanji and katakana, weak for hiragana
Recall goes from 7% to 86%, with 20% overmatch against 54% for substring search. Kanji compounds split well (東京国際空港 → 東京|国際|空港, so a search for 空港 finds it). Katakana splits at hyphens and middle dots: セブン-イレブン渋谷店 → セブン|イレブン|渋谷|店, so a search for セブンイレブン finds the hyphenated form. On place names that found 18,268 rows, against 541 for a substring search that expected no hyphen.
Where it falls short:
- Hiragana-only words split into syllables:
ひらがな→ひ|ら|が|な,やきとり→や|き|とり,らーめん→ら|ー|めん. Verb forms fragment the same way:食べました→食|べ|ま|した.intl_normalizewrites hiragana as katakana, andintl_search_ngram’s parser keeps adjacent kana words together as one run, soらーめんandラーメンfind each other there. Withintl_searchthey still don’t, because the hiragana spelling is split and the katakana one isn’t. - Unknown katakana names can come out as fragments (
ンズ,ジョ), which then match inside unrelated names. This is most of the 20% overmatch. - Half-width katakana (
セブンイレブン) only matches full-width withintl_search, which applies NFKC. - No morphology. ICU isn’t a morphological analyzer like MeCab or Kuromoji, so it won’t link inflected forms or break up every compound.
intl_search_ngram raises recall to 100% and finds words inside compounds (駅 in 駅前), but overmatch rises to 54%, the same as pg_bigm’s, because Japanese titles are full of short kana sequences that also occur inside other words.
Thai: use intl_search_ngram
Recall goes from 5% to 79%, at 44% overmatch against 77% for substring search. Dictionary words split cleanly: ร้านกาแฟสด → ร้าน|กาแฟ|สด, โรงพยาบาลกรุงเทพ → โรง|พยาบาล|กรุงเทพ. On place names, textsearch_intl found 39,019 rows for กาแฟ (coffee) against 3,808 for the default parser and 40,492 for substring search.
Where it falls short:
- Unknown names pull letters off the next word.
กุ๊งกิ๊งกาแฟโบราณ→กุ๊|งกิ๊|งกาแฟ|โบราณ: the name isn’t in the dictionary, soกาแฟpicks up its last letter and a search for it misses. - Loanwords split differently depending on what follows them.
เซเว่น(Seven) on its own isเซ|เว่น, but inเซเว่นอีเลฟเว่น(7-Eleven) it’sเซ|เว่|นอีเลฟเว่น, so a search forเซเว่นdoesn’t find the full brand name. On place names, textsearch_intl found 2,094 rows against 3,794 for substring search. - Thai followed directly by Latin letters stays joined:
ร้านกาแฟcoffee→ร้าน|กาแฟcoffee. - Fragments overmatch. Short fragments like
องandซ์come out of unknown names as tokens and match inside unrelated words.
intl_search_ngram avoids ICU’s guesses: its intl_runs parser keeps each unspaced Thai run whole and indexes every pair and every three adjacent grapheme clusters, so เซเว่น finds เซเว่นอีเลฟเว่น and กาแฟ finds กุ๊งกิ๊งกาแฟโบราณ. Recall goes to 98% at 69% overmatch, against 77% for substring search; the overmatch is high on this benchmark because its query terms include ICU fragments like อง. The index is about three times intl_search’s for Thai text.
Lao, Khmer, Burmese: likely good, not benchmarked
ICU has dictionaries for all three, and sentences split into words: ຮ້ານອາຫານລາວ → ຮ້ານ|ອາຫານ|ລາວ, ភោជនីយដ្ឋានខ្មែរ → ភោជនីយដ្ឋាន|ខ្មែរ, မြန်မာစားသောက်ဆိုင် → မြန်မာ|စားသောက်ဆိုင်. Expect the same weaknesses as Thai with unknown names.
Korean: use intl_search_ngram
Korean puts spaces between phrases, but attaches particles, endings and other nouns directly to a word. ICU has no Korean dictionary, so each space-separated run is one token, exactly as with the default parser. Recall was 48% against 47% for the default parser.
- Particles:
서울의 맛집→서울의|맛집, so a search for서울doesn’t match. - Brand plus branch names:
스타벅스안양평촌점(Starbucks, Anyang Pyeongchon branch) is a single token. On place names, a search for편의점(convenience store) found 2,041 rows with textsearch_intl and 9,096 with substring search, because names like행복한편의점are one word.
intl_search_ngram fixes both: it indexes every Hangul syllable and syllable pair, so 서울 matches 서울의, 편의점 matches 행복한편의점, and 스타벅스 matches 스타벅스안양평촌점. Recall goes to 100% with 22% overmatch, the same as pg_bigm, at a similar index size. The overmatch is the substring kind: 스타벅스 also matches 파스타벅스. For true morphology, use an analyzer outside Postgres (such as Elasticsearch’s nori).
Vietnamese: no gain
Vietnamese writes each syllable separately, and most words are two or more syllables. ICU splits at every space, like the default parser: Nhà hàng Phở Hà Nội → Nhà|hàng|Phở|Hà|Nội. Use phraseto_tsquery so Hà Nội matches as a unit. intl_search folds the diacritics. With intl_join’s max_length set it also indexes adjacent short syllables run together, so hanoi finds Hà Nội and saigon finds Sài Gòn; the other direction (sai gon finding Saigon) still doesn’t match.
Arabic: better with intl_search; Hebrew: no gain
Spaces separate words, but the article and some prepositions and conjunctions are written as part of the word. intl_search indexes a word that starts with the article ال or وال both with and without it, so a search for مطعم (restaurant) finds المطعم (the restaurant). It also folds the letter variants people type interchangeably in names (أحمد and احمد, مدرسة and مدرسه, Persian ی and ک) and removes short-vowel marks. Other prefixes stay attached: بالمطعم (in the restaurant) is one token, and a word that merely starts with ال (الله) also gets the shortened form, a small source of overmatch.
Hebrew does the same with המסעדה against מסעדה, and textsearch_intl doesn’t remove Hebrew prefixes.
English and other spaced languages: same as the default parser
For English, Russian, Greek, Hindi, German, Turkish and other languages that separate words with spaces, ICU’s splits match the default parser’s, and tokenizing English is slightly faster (0.63 µs against 0.73 µs per title). Search results were the same within 0.5%: starbucks found 54,395 place names with textsearch_intl and 54,352 with the default parser. Differences in detail:
- Apostrophes stay inside the word:
McDonald'sis one token,mcdonald's, so a search formcdonaldsormcdonalddoesn’t match it. The default parser splits it intomcdonald|sinstead.intl_searchremoves the apostrophe, somcdonaldsmatches, and indexes French and Italian elisions both ways, sooccitanefindsL'Occitane. - Case folds fully:
intl_searchuses Unicode case folding rather than the database’slower(), so Greek final sigma (ΑΘΗΝΑΣ,Αθήνας), Germanß(STRASSE) and Turkishİmatch. - Hyphens and ampersands split:
7-Eleven→7|eleven,co-op→co|op,AT&T→at|t. Digits and letters split too (7Eleven→7|eleven). - Case changes inside a word split:
intl_searchindexesCarrefourSAas the whole word and as its parts, socarrefourfinds it. - Words written together or apart (
Mc Donald'sandMcDonald's) match only withintl_join’smax_lengthset, which adds the two words run together (see the README). - URLs and hostnames stay whole:
www.example.comis one token. - No stemming or compound splitting.
Kaffeehausdoesn’t matchhaus. For stemming, mapwordto a Snowball dictionary:ALTER TEXT SEARCH CONFIGURATION my_config ALTER MAPPING FOR word WITH english_stem.