Skip to main content
Indexes a text or citext column (or a text-producing expression) for TINQL search, including phrases, proximity, and spans. Documents and queries are analyzed with the same tokenization pipeline. Each TIN index covers one text source. Multi-column USING tin (…) indexes are not supported. To search several columns, create one TIN index per column and combine ==> predicates. tin.score(ctid) combines relevance across those columns.

Partial and expression indexes

Partial and expression TIN indexes work like other Postgres index types:
The query must match the indexed expression exactly. (data->>'title') ==> … uses the expression index, and a plain title ==> … does not. A partial index is used only when Postgres can prove the query implies its WHERE predicate (for example WHERE active AND body ==> …).

Index options (WITH)

Scoring

Scoring options are read at query time, so ALTER INDEX ... SET takes effect on the next scored query without a rebuild. k1 and b can also be overridden for a single query with tin.score(ctid, k1 => …, b => …). tin.score_inspect shows which terms a query would score after stop words and dense-term elision are applied.

Tokenization

Defaults fold case and accents, split over-long tokens on grapheme boundaries, and emit emoji as searchable terms. Stemming is off until you set stemmer. Indexing and query analysis use the same pipeline, so ==> searches agree with what the index stored.
Use tin.tokenize to inspect the pipeline. Named arguments match the index options and use the same defaults:
Hyphenated surface forms may still split into multiple tokens (for example wi-fi becomes the phrase "wi fi"). Fuzzy (term~N) requires a single token after tokenization. Changing tokenization options on a populated index does not re-tokenize stored rows. Run REINDEX to apply the new policy to them.

Stemming

stemmer applies Snowball stemming after lowercasing and before accent folding. It is unset by default. Set an ISO language code to match inflected words as the same term:
English run, runs, and running share the stored term run. runner stays a separate term. Stemming requires case_folding = fold; unknown language codes and case_folding = preserve with a stemmer fail at CREATE / ALTER. Ordinary query terms and phrases use the same stemmer as stored text. Wildcard, fuzzy, and range literals skip stemming and search the stored stems. Regular expressions are unchanged. score_stop_words still match stored terms exactly, so write them in stemmed form on a stemmed index. Enable, change, or disable stemming and rebuild together so existing rows and later queries agree:
ALTER INDEX ... RESET (stemmer) turns stemming off. That also needs REINDEX on a populated index.

Segments

A TIN index is stored as segments. The segment count sets how many parallel workers a build and a query can use, and background maintenance folds new writes into segments over time. Changes to these options take effect on the next maintenance pass, except initial_segment_count, which is read only at build.

Need help?

Get help from the PlanetScale Support team, or join our Discord community to see how others are using PlanetScale.