agora inbox for pgsql-bugs@postgresql.org  
help / color / mirror / Atom feed
BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
4+ messages / 3 participants
[nested] [flat]

* BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
@ 2026-09-25 19:38  PG Bug reporting form <noreply@postgresql.org>
  0 siblings, 1 reply; 4+ messages in thread

From: PG Bug reporting form @ 2026-09-25 19:38 UTC (permalink / raw)
  To: pgsql-bugs@lists.postgresql.org; +Cc: natec425@gmail.com

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';








^ permalink  raw  reply  [nested|flat] 4+ messages in thread

* Re: BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
@ 2026-09-26 09:02  Kirill Reshke <reshkekirill@gmail.com>
  parent: PG Bug reporting form <noreply@postgresql.org>
  0 siblings, 1 reply; 4+ messages in thread

From: Kirill Reshke @ 2026-09-26 09:02 UTC (permalink / raw)
  To: natec425@gmail.com; pgsql-bugs@lists.postgresql.org

On Sat, 26 Sept 2026 at 12:15, PG Bug reporting form
<noreply@postgresql.org> wrote:
>
> 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';
>
>
>


Hi!
I checked this report  at 1a846a55, and it looks like there is indeed
corruption.

```
reshke=# set enable_seqscan to off;
SET
reshke=# select 'total' as what, count(*) from t
union
select 'tests (total minus 3)', count(*) from t where v = 'test';
         what          | count
-----------------------+-------
 tests (total minus 3) |     0
 total                 | 33117
(2 rows)

reshke=# set enable_seqscan to on;
SET
reshke=# select 'total' as what, count(*) from t
union
select 'tests (total minus 3)', count(*) from t where v = 'test';
         what          | count
-----------------------+-------
 tests (total minus 3) | 33114
 total                 | 33117
(2 rows)
```

Your analysis also looks correct for me, would you share a patch with
result->flag = ALLISTRUE; -> result->flag |= ALLISTRUE; ?

I also used patch from [0] and it complans with  ERROR:  index "t_idx"
has inconsistent records on page 293 offset 1


[0] https://commitfest.postgresql.org/patch/5879/
-- 
Best regards,
Kirill Reshke





^ permalink  raw  reply  [nested|flat] 4+ messages in thread

* Re: BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
@ 2026-09-26 09:31  Kirill Reshke <reshkekirill@gmail.com>
  parent: Kirill Reshke <reshkekirill@gmail.com>
  0 siblings, 1 reply; 4+ messages in thread

From: Kirill Reshke @ 2026-09-26 09:31 UTC (permalink / raw)
  To: natec425@gmail.com; pgsql-bugs@lists.postgresql.org

> Your analysis also looks correct for me, would you share a patch with
> result->flag = ALLISTRUE; -> result->flag |= ALLISTRUE; ?
>

This looks like oversight of [0]

[0] https://git.postgresql.org/cgit/postgresql.git/diff/contrib/pg_trgm/trgm_gist.c?id=911e7020



-- 
Best regards,
Kirill Reshke






^ permalink  raw  reply  [nested|flat] 4+ messages in thread

* Re: BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY
@ 2026-09-26 14:26  Nate Clark <natec425@gmail.com>
  parent: Kirill Reshke <reshkekirill@gmail.com>
  0 siblings, 0 replies; 4+ messages in thread

From: Nate Clark @ 2026-09-26 14:26 UTC (permalink / raw)
  To: Kirill Reshke <reshkekirill@gmail.com>; +Cc: pgsql-bugs@lists.postgresql.org

> Your analysis also looks correct for me, would you share a patch with
> result->flag = ALLISTRUE; -> result->flag |= ALLISTRUE; ?

Of course. I've attached a patch with that one line change.

Please let me know if there is anything else I should do, and thanks
for your help!

Attachments:

  [application/octet-stream] v1-0001-Fix-pg_trgm-GiST-union-dropping-SIGNKEY-from-all-.patch (948B, ../../CAOP7+xgnM+tPHWDFEonj7nrkL+-CyY-04Yz9QxDN-x05z+aeRA@mail.gmail.com/2-v1-0001-Fix-pg_trgm-GiST-union-dropping-SIGNKEY-from-all-.patch)
  download | inline diff:
From e08981fa6b827ca9f74a722c230981723dbbcf19 Mon Sep 17 00:00:00 2001
From: Nate Clark <natec425@gmail.com>
Date: Sat, 26 Sep 2026 09:03:05 -0500
Subject: [PATCH v1] Fix pg_trgm GiST union dropping SIGNKEY from all-true keys

gtrgm_union() assigned ALLISTRUE instead of OR-ing it in, leaving a
key that unionkey() reads as an empty array. See BUG #19720.
---
 contrib/pg_trgm/trgm_gist.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c
index 42d0b7a5d6..dfa2f968d7 100644
--- a/contrib/pg_trgm/trgm_gist.c
+++ b/contrib/pg_trgm/trgm_gist.c
@@ -576,7 +576,7 @@ gtrgm_union(PG_FUNCTION_ARGS)
 	{
 		if (unionkey(base, GETENTRY(entryvec, i), siglen))
 		{
-			result->flag = ALLISTRUE;
+			result->flag |= ALLISTRUE;
 			SET_VARSIZE(result, CALCGTSIZE(ALLISTRUE, siglen));
 			break;
 		}

base-commit: 45da2c1d75663d7a692f967b77df42a84fdd6b4d
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 4+ messages in thread


end of thread, other threads:[~2026-09-26 14:26 UTC | newest]

Thread overview: 4+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-09-25 19:38 BUG #19720: pg_trgm GiST index corruption from gtrgm_union() dropping SIGNKEY PG Bug reporting form <noreply@postgresql.org>
2026-09-26 09:02 ` Kirill Reshke <reshkekirill@gmail.com>
2026-09-26 09:31   ` Kirill Reshke <reshkekirill@gmail.com>
2026-09-26 14:26     ` Nate Clark <natec425@gmail.com>

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