[PGX Inc.](https://pgexperts.com/) has released version 1.0 of 
[pgx-bm25](https://github.com/pgexperts/pgx-bm25), an extension that adds Okapi 
BM25 ranked full-text search to PostgreSQL as a native index access method. It 
supports PostgreSQL 17 and 18, and installs as `bm25_native`.

    CREATE EXTENSION bm25_native;
    CREATE INDEX docs_bm25 ON docs USING bm25_native (body);
    
    SELECT id, bm25_score(ctid) AS score
    FROM   docs
    WHERE  body @@@ 'quick fox'
    ORDER  BY body &@@ 'quick fox'
    LIMIT  10;

`@@@` selects the matching rows and `&@@` orders them by relevance. The query 
runs as an ordered index scan; there is no Sort node.

## It is a PostgreSQL index

The whole index lives in the index relation's own pages, so it gets WAL 
logging, crash recovery, and physical replication from core. `VACUUM` maintains 
it. There is no external search engine and no separate runtime to operate. The 
extension is written in C against the stock server headers and builds with 
PGXS; a C compiler and `pg_config` are the whole toolchain.

Text is analyzed with PostgreSQL's own Snowball dictionaries, with the language 
set per index, so a search for `negligent` finds `negligence`. Ranked top-N 
queries use block-max WAND, so a typical `LIMIT 10` search does not have to 
score every matching document.

## Query features

- Multi-column indexes with BM25F scoring: per-field boosts and per-field 
length normalization. `k1`, `b`, and the boosts can be changed with `ALTER 
INDEX ... SET` and take effect on the next scan, without a `REINDEX`.
- Exact phrases, and proximity search by token distance, ordered or unordered.
- Boolean queries (`must`, `should`, `must_not`), nested as needed.
- Prefix wildcards (`judg*`).
- Query-time boosts on any clause.
- Highlighted snippets with `bm25_snippet()`, HTML-escaped by default.

Simple searches can be written as a text string. Everything else is composed 
from builder functions that produce a jsonb query tree, so user input always 
arrives as a value and never as query syntax:

    CREATE INDEX docs_bm25 ON docs
      USING bm25_native (title, summary, body) INCLUDE (id) WITH (key_field = 
'id');
    
    SELECT id, bm25_score_key(id) AS score
    FROM   docs
    WHERE  title @@@ bm25_boolean(
                       must     => ARRAY[ bm25_term('body', 'tort') ],
                       must_not => ARRAY[ bm25_term('body', 'battery') ])
    ORDER  BY title &@@ bm25_boolean(
                       must     => ARRAY[ bm25_term('body', 'tort') ],
                       must_not => ARRAY[ bm25_term('body', 'battery') ])
    LIMIT  10;

## License

pgx-bm25 is released under the [PostgreSQL 
License](https://www.postgresql.org/about/licence/), the same terms as 
PostgreSQL itself. It is a standalone project: it does not depend on, or 
connect to, any other product or service, and there is no separate commercial 
edition. The source, the regression and TAP test suites, the benchmark scripts, 
and the design record (an architecture guide and a log of design decisions) are 
all in the repository.

## Upgrades and testing

The on-disk format carries a compatibility contract: additive format changes do 
not require a `REINDEX`, and where a breaking change allows it, 
`bm25_upgrade()` migrates an index in place. CI builds and tests against 
PostgreSQL 17 and 18, including assert-enabled, UBSan, and AddressSanitizer 
runs, plus TAP tests for crash recovery and replica equality. PostgreSQL 19 is 
tested against the current beta but is not yet a supported major version.

## Getting it

    make    
    sudo make install

Source, documentation, and issue tracker: https://github.com/pgexperts/pgx-bm25

Reply via email to