agora inbox for pgsql-bugs@postgresql.org  
help / color / mirror / Atom feed
From: PG Bug reporting form <noreply@postgresql.org>
To: pgsql-bugs@lists.postgresql.org
Cc: natec425@gmail.com
Subject: BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
Date: Fri, 25 Sep 2026 19:38:25 +0000
Message-ID: <19720-b3e83f5e99c485c5@postgresql.org> (raw)

The following bug has been logged on the website:

Bug reference:      19720
Logged by:          Nate Clark
Email address:      natec425@gmail.com
PostgreSQL version: 18.6
Operating system:   macOS 26 (aarch64)
Description:        

Hey all,

I experienced index corruption for a trigram GiST index at work. I believe
it is due to an invalid `TRGM->flag` state.

Currently, `gtrgm_union` sets `result->flag = ALLISTRUE` when it determines
that the signature is all true, but this drops the `SIGNKEY` bit. Downstream
of this, `unionkey` branches on the flag and defaults to the array handling
else branch. This array branch interprets the state as a 0 length array and
produces an empty signature. These two pieces together mean that an insert
in this bad flag state will produce a downlink with only the signature bits
for that new inserted value rather than the true union of the children.

I believe the fix is to change it to `|= ALLISTRUE` (similar to the other
GiST op classes). I've tested this change locally against the following
repro script.

I get the following output against `master` and 18.6 (with some small jitter
in total count):

         what          | count
-----------------------+-------
 tests (total minus 3) |     0
 total                 | 33120

With the `|=` patch I get:

         what          | count
-----------------------+-------
 tests (total minus 3) | 33130
 total                 | 33133

Thanks so much for your time,
Nate Clark

-- repro script
create extension if not exists pg_trgm;
create extension if not exists pageinspect;
drop table if exists t;
create table t (v text);
create index t_idx on t
  -- siglen 4 to make it simpler to hit ALLISTRUE
  using gist (v gist_trgm_ops(siglen=4));

-- Insert until the root splits as an internal node
-- so we get signature, not array, logic.
do $$
begin
  -- insert a null to hit the null handling branch involving gtrgm_union
  insert into t values (null);
  -- insert an ALLISTRUE input
  insert into t select string_agg(i::text, ' ')
                from generate_series(1, 100) i;
  -- insert until the root splits
  loop
    insert into t values ('test');
    exit when exists (
      select from gist_page_items_bytea(get_raw_page('t_idx', 0)) r,
        gist_page_opaque_info(get_raw_page('t_idx',
          (r.ctid::text::point)[0]::int)) c
      where r.itemoffset = 1 and not 'leaf' = any(c.flags));
  end loop;
end $$;

-- Insert one row whose trigrams set 0 bits.
-- This causes all children to be unreachable.
insert into t values ('');

select 'total' as what, count(*) from t
union
select 'tests (total minus 3)', count(*) from t where v = 'test';








view thread (4+ messages)  latest in thread

Message-ID: <19720-b3e83f5e99c485c5@postgresql.org>
Permalink:  ../19720-b3e83f5e99c485c5@postgresql.org/
Also on:    postgresql.org/message-id/19720-b3e83f5e99c485c5@postgresql.org

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-bugs@postgresql.org
  Cc: noreply@postgresql.org, pgsql-bugs@lists.postgresql.org, natec425@gmail.com
  Subject: Re: BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
  In-Reply-To: <19720-b3e83f5e99c485c5@postgresql.org>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox