From 5aea45342a7f5ccd9ec8313ef80869ac00a45f6d Mon Sep 17 00:00:00 2001 From: Ernest Bursa Date: Tue, 10 Feb 2026 11:41:39 +0100 Subject: [PATCH 1/2] feat: add pg_textsearch extension for PG17 (BM25 full-text search) Add pg_textsearch v0.5.0 by Timescale, providing modern full-text search with BM25 ranking via the `bm25` index access method. PG17 only. Co-Authored-By: Claude Opus 4.6 --- .../tests/extensions/31-pg_textsearch.sql | 9 ++ migrations/tests/extensions/test.sql | 1 + nix/checks.nix | 1 + nix/ext/pg_textsearch.nix | 91 +++++++++++++++++++ nix/ext/versions.json | 8 ++ nix/packages/postgres.nix | 2 +- nix/tests/expected/z_17_pg_textsearch.out | 27 ++++++ nix/tests/prime.sql | 1 + nix/tests/sql/z_17_pg_textsearch.sql | 22 +++++ 9 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 migrations/tests/extensions/31-pg_textsearch.sql create mode 100644 nix/ext/pg_textsearch.nix create mode 100644 nix/tests/expected/z_17_pg_textsearch.out create mode 100644 nix/tests/sql/z_17_pg_textsearch.sql diff --git a/migrations/tests/extensions/31-pg_textsearch.sql b/migrations/tests/extensions/31-pg_textsearch.sql new file mode 100644 index 000000000..2973e0193 --- /dev/null +++ b/migrations/tests/extensions/31-pg_textsearch.sql @@ -0,0 +1,9 @@ +begin; +do $_$ +begin + if current_setting('server_version_num')::integer >= 170000 then + create extension if not exists pg_textsearch with schema "extensions"; + end if; +end +$_$; +rollback; diff --git a/migrations/tests/extensions/test.sql b/migrations/tests/extensions/test.sql index 1d7075c13..d684e8fcc 100644 --- a/migrations/tests/extensions/test.sql +++ b/migrations/tests/extensions/test.sql @@ -29,3 +29,4 @@ \ir 28-pgvector.sql \ir 29-pg_tle.sql \ir 30-pg_partman.sql +\ir 31-pg_textsearch.sql diff --git a/nix/checks.nix b/nix/checks.nix index cf9e03900..744ed099e 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -241,6 +241,7 @@ "z_17_pg_stat_monitor" "z_17_pgvector" "z_17_rum" + "z_17_pg_textsearch" "z_17_roles" # version-specific roles test, includes pgtle_admin ]; diff --git a/nix/ext/pg_textsearch.nix b/nix/ext/pg_textsearch.nix new file mode 100644 index 000000000..0af08fab7 --- /dev/null +++ b/nix/ext/pg_textsearch.nix @@ -0,0 +1,91 @@ +{ + lib, + stdenv, + fetchFromGitHub, + postgresql, + buildEnv, +}: +let + pname = "pg_textsearch"; + + # Load version configuration from external file + allVersions = (builtins.fromJSON (builtins.readFile ./versions.json)).${pname}; + + # Filter versions compatible with current PostgreSQL version + supportedVersions = lib.filterAttrs ( + _: value: builtins.elem (lib.versions.major postgresql.version) value.postgresql + ) allVersions; + + # Derived version information + versions = lib.naturalSort (lib.attrNames supportedVersions); + latestVersion = lib.last versions; + numberOfVersions = builtins.length versions; + packages = builtins.attrValues ( + lib.mapAttrs (name: value: build name value.hash) supportedVersions + ); + + # Build function for individual versions + build = + version: hash: + stdenv.mkDerivation { + inherit pname version; + + src = fetchFromGitHub { + owner = "timescale"; + repo = "pg_textsearch"; + rev = "refs/tags/v${version}"; + inherit hash; + }; + + buildInputs = [ postgresql ]; + + makeFlags = [ "USE_PGXS=1" ]; + + installPhase = '' + mkdir -p $out/{lib,share/postgresql/extension} + + # Install shared library with version suffix + mv ${pname}${postgresql.dlSuffix} $out/lib/${pname}-${version}${postgresql.dlSuffix} + + # Create version-specific control file + sed -e "/^default_version =/d" \ + -e "s|^module_pathname = .*|module_pathname = '\$libdir/${pname}-${version}'|" \ + ${pname}.control > $out/share/postgresql/extension/${pname}--${version}.control + + # Copy SQL file to install the specific version + cp sql/${pname}--${version}.sql $out/share/postgresql/extension/${pname}--${version}.sql + + # For the latest version, copy sql upgrade scripts, default control file and symlink + if [[ "${version}" == "${latestVersion}" ]]; then + cp sql/*.sql $out/share/postgresql/extension + { + echo "default_version = '${version}'" + cat $out/share/postgresql/extension/${pname}--${version}.control + } > $out/share/postgresql/extension/${pname}.control + ln -sfn ${pname}-${latestVersion}${postgresql.dlSuffix} $out/lib/${pname}${postgresql.dlSuffix} + fi + ''; + + meta = with lib; { + description = "Full-text search with BM25 ranking for PostgreSQL"; + homepage = "https://github.com/timescale/pg_textsearch"; + license = licenses.postgresql; + inherit (postgresql.meta) platforms; + }; + }; +in +buildEnv { + name = pname; + paths = packages; + + pathsToLink = [ + "/lib" + "/share/postgresql/extension" + ]; + + passthru = { + inherit versions numberOfVersions pname; + version = + "multi-" + lib.concatStringsSep "-" (map (v: lib.replaceStrings [ "." ] [ "-" ] v) versions); + }; +} diff --git a/nix/ext/versions.json b/nix/ext/versions.json index 0fca984cd..82b40238c 100644 --- a/nix/ext/versions.json +++ b/nix/ext/versions.json @@ -786,6 +786,14 @@ "hash": "sha256-STJVvvrLVLe1JevNu6u6EftzAWv+X+J8lu66su7Or2s=" } }, + "pg_textsearch": { + "0.5.0": { + "postgresql": [ + "17" + ], + "hash": "sha256-Vi18urkZlD62eFLtITAwI2iBkKpKeaciBkdiKBXtE2o=" + } + }, "pgtap": { "1.2.0": { "postgresql": [ diff --git a/nix/packages/postgres.nix b/nix/packages/postgres.nix index ccff04aa0..9f934e42a 100644 --- a/nix/packages/postgres.nix +++ b/nix/packages/postgres.nix @@ -54,7 +54,7 @@ ) ourExtensions; orioledbExtensions = orioleFilteredExtensions ++ [ ../ext/orioledb.nix ]; - dbExtensions17 = orioleFilteredExtensions; + dbExtensions17 = orioleFilteredExtensions ++ [ ../ext/pg_textsearch.nix ]; # CLI extensions - minimal set for Supabase CLI with migration support cliExtensions = [ diff --git a/nix/tests/expected/z_17_pg_textsearch.out b/nix/tests/expected/z_17_pg_textsearch.out new file mode 100644 index 000000000..9384c7438 --- /dev/null +++ b/nix/tests/expected/z_17_pg_textsearch.out @@ -0,0 +1,27 @@ +-- Test pg_textsearch extension (BM25 ranked text search) +create schema ts; +create table ts.docs ( + id serial primary key, + content text +); +insert into ts.docs (content) values + ('PostgreSQL is a powerful relational database system'), + ('BM25 is a ranking function used in search engines'); +create index docs_bm25_idx on ts.docs using bm25(content) with (text_config='english'); +-- Verify BM25 index was created +select indexname, indexdef from pg_indexes +where schemaname = 'ts' and indexname = 'docs_bm25_idx'; + indexname | indexdef +---------------+------------------------------------------------------------------------------------------------ + docs_bm25_idx | CREATE INDEX docs_bm25_idx ON ts.docs USING bm25 (content) WITH (text_config='english') +(1 row) + +-- Verify BM25 search returns results (check count, not exact scores) +select count(*) from ts.docs where content <@> 'database' < 1.0; + count +------- + 1 +(1 row) + +drop schema ts cascade; +NOTICE: drop cascades to table ts.docs diff --git a/nix/tests/prime.sql b/nix/tests/prime.sql index 5ae47444e..6a303ad84 100644 --- a/nix/tests/prime.sql +++ b/nix/tests/prime.sql @@ -51,6 +51,7 @@ create extension if not exists pg_partman with schema partman; create extension if not exists pg_repack; create extension if not exists pg_stat_monitor; create extension if not exists pg_stat_statements; +create extension if not exists pg_textsearch; create extension if not exists pg_surgery; create extension if not exists pg_tle; create extension if not exists pg_trgm; diff --git a/nix/tests/sql/z_17_pg_textsearch.sql b/nix/tests/sql/z_17_pg_textsearch.sql new file mode 100644 index 000000000..751b1e937 --- /dev/null +++ b/nix/tests/sql/z_17_pg_textsearch.sql @@ -0,0 +1,22 @@ +-- Test pg_textsearch extension (BM25 ranked text search) +create schema ts; + +create table ts.docs ( + id serial primary key, + content text +); + +insert into ts.docs (content) values + ('PostgreSQL is a powerful relational database system'), + ('BM25 is a ranking function used in search engines'); + +create index docs_bm25_idx on ts.docs using bm25(content) with (text_config='english'); + +-- Verify BM25 index was created +select indexname, indexdef from pg_indexes +where schemaname = 'ts' and indexname = 'docs_bm25_idx'; + +-- Verify BM25 search returns results (check count, not exact scores) +select count(*) from ts.docs where content <@> 'database' < 1.0; + +drop schema ts cascade; From 8d88e77fd4e20d419d607f2a12bdbace792201f0 Mon Sep 17 00:00:00 2001 From: Ernest Bursa Date: Tue, 10 Feb 2026 11:43:50 +0100 Subject: [PATCH 2/2] docs: add pg_textsearch to PG17 extensions table in README Co-Authored-By: Claude Opus 4.6 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 990dfcaa9..2ddbd5b3b 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,7 @@ This is the same PostgreSQL build that powers [Supabase](https://supabase.io), b | [pg_plan_filter](https://github.com/pgexperts/pg_plan_filter/archive/5081a7b5cb890876e67d8e7486b6a64c38c9a492.tar.gz) | [5081a7b5cb890876e67d8e7486b6a64c38c9a492](https://github.com/pgexperts/pg_plan_filter/archive/5081a7b5cb890876e67d8e7486b6a64c38c9a492.tar.gz) | Filter PostgreSQL statements by execution plans | | [pg_repack](https://github.com/reorg/pg_repack/archive/ver_1.5.2.tar.gz) | [1.5.2](https://github.com/reorg/pg_repack/archive/ver_1.5.2.tar.gz) | Reorganize tables in PostgreSQL databases with minimal locks | | [pg_stat_monitor](https://github.com/percona/pg_stat_monitor/archive/refs/tags/2.1.0.tar.gz) | [2.1.0](https://github.com/percona/pg_stat_monitor/archive/refs/tags/2.1.0.tar.gz) | Query Performance Monitoring Tool for PostgreSQL | +| [pg_textsearch](https://github.com/timescale/pg_textsearch/archive/refs/tags/v0.5.0.tar.gz) | [0.5.0](https://github.com/timescale/pg_textsearch/archive/refs/tags/v0.5.0.tar.gz) | Full-text search with BM25 ranking for PostgreSQL | | [pg_tle](https://github.com/aws/pg_tle/archive/refs/tags/v1.4.0.tar.gz) | [1.4.0](https://github.com/aws/pg_tle/archive/refs/tags/v1.4.0.tar.gz) | Framework for 'Trusted Language Extensions' in PostgreSQL | | [pgaudit](https://github.com/pgaudit/pgaudit/archive/17.0.tar.gz) | [17.0](https://github.com/pgaudit/pgaudit/archive/17.0.tar.gz) | Open Source PostgreSQL Audit Logging | | [pgjwt](https://github.com/michelp/pgjwt/archive/9742dab1b2f297ad3811120db7b21451bca2d3c9.tar.gz) | [9742dab1b2f297ad3811120db7b21451bca2d3c9](https://github.com/michelp/pgjwt/archive/9742dab1b2f297ad3811120db7b21451bca2d3c9.tar.gz) | PostgreSQL implementation of JSON Web Tokens |