Changelog

All notable changes to pg_fts are documented here.

0.2.0

Breaking: the index access method was renamed bm25fts (CREATE INDEX ... USING fts (to_ftsdoc('english', body))). This lets pg_fts coexist in the same database as Timescale pg_textsearch (whose AM is named bm25), so a pg_textsearch workload can be migrated one index at a time rather than in a single hard cutover. Existing USING bm25 indexes must be recreated as USING fts. The BM25 scoring functions (fts_bm25, fts_bm25f, fts_bm25_opts) are unchanged — BM25 is the ranking algorithm, fts is the access method.

  • On-disk format version check: opening an index whose stored format version does not match the loaded shared library now raises a clear error (... has pg_fts on-disk format version N, but this build expects M) with a REINDEX hint, instead of silently misreading the index.
  • New doc/MIGRATING_FROM_PG_TEXTSEARCH.md: query/DDL rewrite table, the multi-column → concatenated-to_ftsdoc pattern, and index build sizing (CREATE INDEX bounds build memory to maintenance_work_mem).

0.1.0 — initial public release

First public release. The extension was developed as an internal, qualified feature series (each stage clean under --enable-cassert, regression-green) that reached internal version 1.20 before being squashed to a single 0.1.0 install script for release. Versioning starts at 0.1.0 to signal that the on-disk format and ranked-query performance will iterate before 1.0.

Included in 0.1.0:

  • ftsdoc / ftsquery types, the @@@ match operator, and the <=> relevance-ordering operator (ORDER BY d <=> q LIMIT k plans as an index scan, no Sort).
  • The bm25 inverted-index access method: WAL-logged via GenericXLog (crash-safe, physical-replication safe), MVCC-correct (per-segment tombstones), segmented (Lucene/Tantivy-style) on-disk format with a size-tiered background merge, block-max WAND / MaxScore top-k with lazy per-column decode.
  • Okapi BM25 scoring with the lucene / robertson / atire / bm25+ / bm25l variants; BM25F multi-field weighting; index-maintained corpus statistics (N, avgdl, per-term df) so ranking needs no heap recheck.
  • A rich query language over one operator: boolean, phrase "a b c", NEAR, prefix term*, fuzzy term~k (Levenshtein DFA), and regex /re/, with a trigram pre-filter for fuzzy/regex.
  • fts_highlight() / fts_snippet(); tsquery_to_ftsquery() migration helper and cast.
  • Incremental maintenance (INSERT appends to a pending list, no REINDEX); fts_merge() and fts_vacuum() (compact + truncate) for on-demand maintenance.
  • fts_count() and a transparent count(*) ... WHERE @@@ CustomScan pushdown for MVCC-correct bulk counts from the index — a capability the specialist BM25 extensions do not expose.
  • Parallel index build/merge; standalone PGXS build plus Nix flake and a Windows/MSVC meson recipe; supported on PostgreSQL 17, 18, and 19/devel.

Known performance position (see bench/RESULTS_VS_VCHORD_PGTEXTSEARCH.md and HANDOFF.md): pg_fts is far faster than the built-in tsvector/GIN + ts_rank stack on ranked retrieval (up to ~40×), but trails the specialist BM25 extensions (VectorChord-bm25, Timescale pg_textsearch) on raw ranked latency and index size, because it stores positional postings for phrase/NEAR. Closing that gap is a posting-codec rewrite tracked in ROADMAP.md; 0.1.0 ships on its distinguishing strengths — query-language breadth, index-native COUNT, and MVCC/crash correctness — and will iterate on ranked performance.