/* pg_fts--0.3.6.sql */
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION pg_fts" to load this file. \quit
--
-- ftsdoc: an analyzed full-text document (terms + term frequencies).
-- to_ftsdoc() records per-term token positions (ftsdoc format v2), which the
-- phrase-query syntax ("a b c") relies on.
--
CREATE TYPE ftsdoc;
CREATE FUNCTION ftsdoc_in(cstring)
RETURNS ftsdoc
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsdoc_out(ftsdoc)
RETURNS cstring
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsdoc_recv(internal)
RETURNS ftsdoc
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsdoc_send(ftsdoc)
RETURNS bytea
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE TYPE ftsdoc (
INPUT = ftsdoc_in,
OUTPUT = ftsdoc_out,
RECEIVE = ftsdoc_recv,
SEND = ftsdoc_send,
INTERNALLENGTH = VARIABLE,
STORAGE = extended
);
--
-- ftsquery: a parsed boolean query. Supports boolean operators, phrase
-- syntax ("a b c"), prefix (term*), fuzzy (term~k) and regex (/re/) terms.
--
CREATE TYPE ftsquery;
CREATE FUNCTION ftsquery_in(cstring)
RETURNS ftsquery
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsquery_out(ftsquery)
RETURNS cstring
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsquery_recv(internal)
RETURNS ftsquery
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsquery_send(ftsquery)
RETURNS bytea
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE TYPE ftsquery (
INPUT = ftsquery_in,
OUTPUT = ftsquery_out,
RECEIVE = ftsquery_recv,
SEND = ftsquery_send,
INTERNALLENGTH = VARIABLE,
STORAGE = extended
);
--
-- Constructors from text.
--
CREATE FUNCTION to_ftsdoc(text)
RETURNS ftsdoc
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION to_ftsquery(text)
RETURNS ftsquery
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- Analyzer that reuses an installed text search configuration (pg_ts_config):
-- the configured parser + dictionary chain (stemming, stopwords, synonyms,
-- thesaurus) is applied, rather than the built-in simple tokenizer.
CREATE FUNCTION to_ftsdoc(regconfig, text)
RETURNS ftsdoc
AS 'MODULE_PATHNAME', 'to_ftsdoc_byid'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- Build an ftsdoc directly from an existing tsvector (no re-analysis of source
-- text): the adoption on-ramp for a table that already materializes a tsvector
-- column. A tsvector's lexemes are sorted+distinct with ascending positions,
-- exactly ftsdoc's shape; positions are kept iff every lexeme has them.
CREATE FUNCTION to_ftsdoc(tsvector)
RETURNS ftsdoc
AS 'MODULE_PATHNAME', 'to_ftsdoc_from_tsvector'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- to_ftsquery(regconfig, text): parse query text AND normalize each plain term
-- through the given text search configuration (stemming, case, stopwords), so
-- query terms match the same lexemes an index built with the same config
-- stores. Prefix (term*), fuzzy (term~k) and regex (/re/) terms stay literal.
-- Use this (not the raw text->ftsquery cast) whenever the indexed ftsdoc was
-- built with to_ftsdoc(regconfig, ...).
CREATE FUNCTION to_ftsquery(regconfig, text)
RETURNS ftsquery
AS 'MODULE_PATHNAME', 'to_ftsquery_byid'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
--
-- Support functions.
--
CREATE FUNCTION ftsdoc_length(ftsdoc)
RETURNS integer
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
--
-- The @@@ match operator.
--
CREATE FUNCTION fts_match(ftsdoc, ftsquery)
RETURNS boolean
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION fts_match_commutator(ftsquery, ftsdoc)
RETURNS boolean
AS 'MODULE_PATHNAME'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE OPERATOR @@@ (
LEFTARG = ftsdoc,
RIGHTARG = ftsquery,
PROCEDURE = fts_match,
COMMUTATOR = @@@,
RESTRICT = tsmatchsel,
JOIN = tsmatchjoinsel
);
CREATE OPERATOR @@@ (
LEFTARG = ftsquery,
RIGHTARG = ftsdoc,
PROCEDURE = fts_match_commutator,
COMMUTATOR = @@@,
RESTRICT = tsmatchsel,
JOIN = tsmatchjoinsel
);
--
-- BM25 relevance scoring.
--
-- BM25 relevance score of a document against a query.
-- n_docs : total documents in the corpus (N)
-- avgdl : average document length (in tokens)
-- dfs : optional float8[] of per-query-term document frequencies, in the
-- order the query's distinct terms appear; NULL treats terms as rare
CREATE FUNCTION fts_bm25(ftsdoc, ftsquery, n_docs float8, avgdl float8,
dfs float8[] DEFAULT NULL)
RETURNS float8
AS 'MODULE_PATHNAME', 'fts_bm25'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
-- BM25 with selectable variant and explicit k1/b, for reproducing reference
-- implementations (Lucene, Robertson/classic, ATIRE, BM25+).
CREATE FUNCTION fts_bm25_opts(ftsdoc, ftsquery,
n_docs float8, avgdl float8,
k1 float8, b float8,
variant text,
dfs float8[] DEFAULT NULL)
RETURNS float8
AS 'MODULE_PATHNAME', 'fts_bm25_opts'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
-- BM25F: multi-field BM25. Pass one ftsdoc per field (e.g. title, body), a
-- weight per field, and an avgdl per field. Per-field term frequencies are
-- length-normalized per field and combined by weight before tf-saturation
-- (the Robertson/Zaragoza BM25F formulation).
CREATE FUNCTION fts_bm25f(docs ftsdoc[], query ftsquery,
weights float8[], n_docs float8, avgdls float8[],
dfs float8[] DEFAULT NULL)
RETURNS float8
AS 'MODULE_PATHNAME', 'fts_bm25f'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
--
-- Highlighting and snippets.
--
-- Highlight query terms in the source text.
CREATE FUNCTION fts_highlight(doc text, query ftsquery,
pre text DEFAULT '', post text DEFAULT '')
RETURNS text
AS 'MODULE_PATHNAME', 'fts_highlight'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- Best-matching window (snippet) of the source text.
CREATE FUNCTION fts_snippet(doc text, query ftsquery,
pre text DEFAULT '', post text DEFAULT '',
ellipsis text DEFAULT '...', max_tokens int DEFAULT 15)
RETURNS text
AS 'MODULE_PATHNAME', 'fts_snippet'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
--
-- tsquery migration.
--
-- Migration helper: mechanically convert a tsquery to an ftsquery.
-- & -> AND, | -> OR, ! -> NOT. The phrase operator <-> degrades to AND with
-- a NOTICE (phrase search is a later stage).
CREATE FUNCTION tsquery_to_ftsquery(tsquery)
RETURNS ftsquery
AS 'MODULE_PATHNAME', 'tsquery_to_ftsquery'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
-- Convenience cast so existing tsquery values flow into @@@ queries.
CREATE CAST (tsquery AS ftsquery)
WITH FUNCTION tsquery_to_ftsquery(tsquery) AS ASSIGNMENT;
--
-- The bm25 index access method: an inverted index over an ftsdoc column that
-- answers the @@@ operator by bitmap scan and maintains corpus statistics.
-- The storage engine is segment-based: inserts flush to new segments, and a
-- size-tiered merge compacts them so query cost stays bounded. An expression
-- index on to_ftsdoc(text_column) is the external-content model -- the text
-- lives in the base table and the index stores only postings.
--
CREATE FUNCTION fts_handler(internal)
RETURNS index_am_handler
AS 'MODULE_PATHNAME'
LANGUAGE C;
CREATE ACCESS METHOD fts TYPE INDEX HANDLER fts_handler;
COMMENT ON ACCESS METHOD fts IS 'fts inverted index for full-text search with BM25 ranking';
-- Operator class: strategy 1 is @@@ (ftsdoc @@@ ftsquery).
CREATE OPERATOR CLASS ftsdoc_fts_ops
DEFAULT FOR TYPE ftsdoc USING fts AS
OPERATOR 1 @@@ (ftsdoc, ftsquery);
--
-- Index-maintained corpus statistics, so BM25 can be scored from the values
-- the bm25 index keeps rather than caller-supplied guesses.
--
CREATE FUNCTION fts_index_stats(regclass,
OUT ndocs float8, OUT avgdl float8,
OUT nterms bigint)
RETURNS record
AS 'MODULE_PATHNAME', 'fts_index_stats'
LANGUAGE C STRICT PARALLEL SAFE;
-- Per-query-term document frequencies from the index (for fts_bm25's dfs arg).
CREATE FUNCTION fts_index_df(regclass, ftsquery)
RETURNS float8[]
AS 'MODULE_PATHNAME', 'fts_index_df'
LANGUAGE C STRICT PARALLEL SAFE;
-- Number of live segments in a bm25 index. Useful for observing/tuning merge
-- behavior.
CREATE FUNCTION fts_index_nsegments(regclass)
RETURNS integer
AS 'MODULE_PATHNAME', 'fts_index_nsegments'
LANGUAGE C STRICT PARALLEL SAFE;
--
-- Index-only BM25 top-k search. Scores are computed entirely from the index
-- (postings, dictionary df/max-tf, metapage N/avgdl) with no heap access; the
-- result is the top-k (ctid, score) pairs. Join back to the table on ctid to
-- fetch rows. A WAND upper-bound prunes documents that cannot enter the top-k.
--
CREATE FUNCTION fts_search(index regclass, query ftsquery, k int DEFAULT 10,
OUT ctid tid, OUT score float8)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'fts_search'
LANGUAGE C STRICT PARALLEL SAFE;
--
-- Lexical anomaly detection: the top-k most anomalous documents in the index,
-- i.e. those containing globally RARE terms. A document's anomaly score is the
-- max idf over its terms (driven by its single rarest term), idf being the same
-- rarity value BM25 uses: log(1 + (N-df+0.5)/(df+0.5)) on the GLOBAL df. It is
-- cheap because only the LOW-df tail of the dictionary is walked -- common terms
-- are skipped before any posting is decoded, so this is not a full-corpus scan.
-- `max_df` caps which terms count as rare (only df <= max_df contribute); NULL
-- defaults to max(N/1000, 1). Returns the heap ctid, the score, the rarest term
-- driving the doc, and that term's global df, ordered by score DESC limit k.
-- The ctids are index-resident heap pointers (like fts_search); join back to
-- the table and filter for visibility if needed. Per-segment tombstones are
-- honored so deleted docs are not reported.
CREATE FUNCTION fts_anomalous_docs(index regclass, k int DEFAULT 100,
max_df int DEFAULT NULL,
OUT ctid tid, OUT score float8,
OUT rarest_term text, OUT min_df int)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'fts_anomalous_docs'
LANGUAGE C PARALLEL SAFE;
-- MVCC-correct count of documents matching a query, computed in bulk from the
-- bm25 index (visibility via the visibility map, heap probed only for
-- not-all-visible pages) without the per-tuple executor round-trips of a scan.
CREATE FUNCTION fts_count(regclass, ftsquery)
RETURNS bigint
AS 'MODULE_PATHNAME', 'fts_count'
LANGUAGE C STRICT PARALLEL SAFE;
--
-- BM25 distance operator for ORDER BY. distance = 1/(1+score), so ascending
-- distance is descending relevance. When used against a bm25 index it drives
-- an index ordering scan (block-max WAND top-k) with no sort node.
--
CREATE FUNCTION fts_distance(ftsdoc, ftsquery)
RETURNS float8
AS 'MODULE_PATHNAME', 'fts_distance'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION fts_distance_commutator(ftsquery, ftsdoc)
RETURNS float8
AS 'MODULE_PATHNAME', 'fts_distance_commutator'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
CREATE OPERATOR <=> (
LEFTARG = ftsdoc,
RIGHTARG = ftsquery,
PROCEDURE = fts_distance,
COMMUTATOR = <=>
);
CREATE OPERATOR <=> (
LEFTARG = ftsquery,
RIGHTARG = ftsdoc,
PROCEDURE = fts_distance_commutator,
COMMUTATOR = <=>
);
-- Add the ORDER BY operator (strategy 2) to the fts operator class so
-- "ORDER BY col <=> query LIMIT k" uses an index ordering scan.
ALTER OPERATOR FAMILY ftsdoc_fts_ops USING fts ADD
OPERATOR 2 <=> (ftsdoc, ftsquery) FOR ORDER BY pg_catalog.float_ops;
--
-- Pending-list / segment maintenance. INSERT appends to an in-index pending
-- list; VACUUM (amvacuumcleanup) folds pending documents into the main
-- posting structure, and these functions do it on demand.
--
-- fts_merge(regclass): merge the pending list into the main segments.
CREATE FUNCTION fts_merge(regclass)
RETURNS boolean
AS 'MODULE_PATHNAME', 'fts_merge'
LANGUAGE C STRICT;
-- fts_vacuum(regclass): on-demand full compaction + truncation, reclaiming
-- the dead pages left by prior merges (shrinks the physical index file).
CREATE FUNCTION fts_vacuum(regclass)
RETURNS boolean
AS 'MODULE_PATHNAME', 'fts_vacuum'
LANGUAGE C STRICT;
-- Privilege model. Most functions operate on values the caller already holds
-- (to_ftsdoc / to_ftsquery / fts_bm25* / fts_highlight / operators / I/O) and
-- stay executable by PUBLIC. Two functions take an index by OID and emit its
-- internal content -- heap TIDs, scores, and (fts_anomalous_docs) indexed term
-- text -- which can widen content visibility past table-level permissions.
-- Revoke them from PUBLIC; the index owner (and superusers) keep access, and an
-- owner can grant them explicitly. (The maintenance functions fts_merge /
-- fts_vacuum enforce ownership in C and additionally refuse to run during
-- recovery, so they need no REVOKE here.)
REVOKE ALL ON FUNCTION fts_search(regclass, ftsquery, int) FROM PUBLIC;
REVOKE ALL ON FUNCTION fts_anomalous_docs(regclass, int, int) FROM PUBLIC;
-- Field-targeted (weight-zone) search (v1.4.0). Tag a sub-document with a
-- weight label A/B/C/D and concatenate labelled sub-documents so a query term
-- can restrict itself to a field zone (tsvector-style term:A). Labels live in
-- the ftsdoc value only (no index format change, no REINDEX); a field-restricted
-- query is answered via the heap recheck.
CREATE FUNCTION to_ftsdoc(regconfig, text, "char")
RETURNS ftsdoc
AS 'MODULE_PATHNAME', 'to_ftsdoc_byid_weight'
LANGUAGE C IMMUTABLE PARALLEL SAFE;
CREATE FUNCTION setftsweight(ftsdoc, "char")
RETURNS ftsdoc
AS 'MODULE_PATHNAME', 'setftsweight'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION ftsdoc_concat(ftsdoc, ftsdoc)
RETURNS ftsdoc
AS 'MODULE_PATHNAME', 'ftsdoc_concat'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE OPERATOR || (
LEFTARG = ftsdoc, RIGHTARG = ftsdoc, FUNCTION = ftsdoc_concat
);