From ca5342a2ceb2adfc867971c80e3bd0a16ccab3aa Mon Sep 17 00:00:00 2001
From: Vaibhav Dalvi <vaibhav.dalvi@enterprisedb.com>
Date: Mon, 31 Aug 2026 10:59:23 +0000
Subject: [PATCH] Drop equality operator support from gist_trgm_ops

The planner's generic, selectivity-based cost model for GiST index
scans has no way to see that gtrgm_consistent()'s containment check
for '=' can be a false positive at a signature-tree node regardless
of how selective the query value actually is (a short query value
with few trigrams is especially prone to this).  This can make the
planner choose a gist_trgm_ops index over a much cheaper btree index
for a plain equality lookup, by orders of magnitude in real execution
time, with no way for the user to see it coming.

gin_trgm_ops does not have this problem: GIN's cost estimator was
fixed to use real per-key statistics in commit cd9479af2af ("Improve
GIN cost estimation").  GiST's estimator has no analogous fix, so
until it does, stop offering '=' via gist_trgm_ops specifically.
gin_trgm_ops is unaffected and keeps supporting the operator.

LIKE, ILIKE, and the similarity operators on gist_trgm_ops are
unaffected; they were registered independently of this operator and
predate it.

Discussion: https://postgr.es/m/CAOBaU_YWwtT7tdggtROacjdOdeYHCz-tmSwuC-j-TOG-g97J0w@mail.gmail.com
---
 contrib/pg_trgm/Makefile              |  6 +++---
 contrib/pg_trgm/pg_trgm--1.6--1.7.sql | 19 +++++++++++++++++++
 contrib/pg_trgm/pg_trgm.control       |  2 +-
 doc/src/sgml/pgtrgm.sgml              | 14 +++++++++-----
 4 files changed, 32 insertions(+), 9 deletions(-)
 create mode 100644 contrib/pg_trgm/pg_trgm--1.6--1.7.sql

diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile
index c1756993ec7..b9b8903169c 100644
--- a/contrib/pg_trgm/Makefile
+++ b/contrib/pg_trgm/Makefile
@@ -9,9 +9,9 @@ OBJS = \
 	trgm_regexp.o
 
 EXTENSION = pg_trgm
-DATA = pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql pg_trgm--1.3--1.4.sql \
-	pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql pg_trgm--1.1--1.2.sql \
-	pg_trgm--1.0--1.1.sql
+DATA = pg_trgm--1.6--1.7.sql pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql \
+	pg_trgm--1.3--1.4.sql pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql \
+	pg_trgm--1.1--1.2.sql pg_trgm--1.0--1.1.sql
 PGFILEDESC = "pg_trgm - trigram matching"
 
 REGRESS = pg_trgm pg_utf8_trgm pg_word_trgm pg_strict_word_trgm
diff --git a/contrib/pg_trgm/pg_trgm--1.6--1.7.sql b/contrib/pg_trgm/pg_trgm--1.6--1.7.sql
new file mode 100644
index 00000000000..3ef33a512ec
--- /dev/null
+++ b/contrib/pg_trgm/pg_trgm--1.6--1.7.sql
@@ -0,0 +1,19 @@
+/* contrib/pg_trgm/pg_trgm--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.7'" to load this file. \quit
+
+-- The planner's generic, selectivity-based cost model for GiST index scans
+-- has no way to account for gtrgm_consistent()'s containment-style check
+-- for '=' being a false positive at signature-tree nodes regardless of how
+-- selective the query value actually is (short strings, with few
+-- trigrams, are especially prone to this).  That can make the planner
+-- choose a gist_trgm_ops index over a much cheaper btree index for a
+-- plain equality query, sometimes by orders of magnitude in actual
+-- execution time.  gin_trgm_ops does not have this problem: GIN's cost
+-- estimator was fixed to use real per-key statistics in commit
+-- cd9479af2af ("Improve GIN cost estimation").  Until GiST's cost
+-- estimator gets an analogous fix, stop offering '=' via gist_trgm_ops;
+-- gin_trgm_ops is unaffected and keeps supporting it.
+ALTER OPERATOR FAMILY gist_trgm_ops USING gist
+DROP OPERATOR 11 (text, text);
diff --git a/contrib/pg_trgm/pg_trgm.control b/contrib/pg_trgm/pg_trgm.control
index 1d6a9ddf259..6e3ee43c510 100644
--- a/contrib/pg_trgm/pg_trgm.control
+++ b/contrib/pg_trgm/pg_trgm.control
@@ -1,6 +1,6 @@
 # pg_trgm extension
 comment = 'text similarity measurement and index searching based on trigrams'
-default_version = '1.6'
+default_version = '1.7'
 module_pathname = '$libdir/pg_trgm'
 relocatable = true
 trusted = true
diff --git a/doc/src/sgml/pgtrgm.sgml b/doc/src/sgml/pgtrgm.sgml
index 07bfcac9319..57e76d27862 100644
--- a/doc/src/sgml/pgtrgm.sgml
+++ b/doc/src/sgml/pgtrgm.sgml
@@ -416,12 +416,16 @@
    the purpose of very fast similarity searches.  These index types support
    the above-described similarity operators, and additionally support
    trigram-based index searches for <literal>LIKE</literal>, <literal>ILIKE</literal>,
-   <literal>~</literal>, <literal>~*</literal> and <literal>=</literal> queries.
-   The similarity comparisons are case-insensitive in a default build of
-   <filename>pg_trgm</filename>.
+   <literal>~</literal> and <literal>~*</literal> queries.  The GIN operator
+   class additionally supports <literal>=</literal> queries; the GiST
+   operator class does not, because GiST's cost estimator has no way to
+   recognize that this can require visiting most of the index regardless of
+   how selective the query value is, which can make the planner choose it
+   over a much cheaper B-tree index.  The similarity comparisons are
+   case-insensitive in a default build of <filename>pg_trgm</filename>.
    Inequality operators are not supported.
-   Note that those indexes may not be as efficient as regular B-tree indexes
-   for equality operator.
+   Note that even the GIN index may not be as efficient as a regular B-tree
+   index for the equality operator.
   </para>
 
   <para>
-- 
2.43.0

