Skip to content

Commit 327b732

Browse files
committed
Put "excludeOnly" GIN scan keys at the end of the scankey array.
Commit 4b754d6 introduced the concept of an excludeOnly scan key, which cannot select matching index entries but can reject non-matching tuples, for example a tsquery such as '!term'. There are poorly-documented assumptions that such scan keys do not appear as the first scan key. ginNewScanKey did nothing to ensure that, however, with the result that certain GIN index searches could go into an infinite loop while apparently-equivalent queries with the clauses in a different order were fine. Fix by teaching ginNewScanKey to place all excludeOnly scan keys after all not-excludeOnly ones. So far as we know at present, it might be sufficient to avoid the case where the very first scan key is excludeOnly; but I'm not very convinced that there aren't other dependencies on the ordering. Bug: #19031 Reported-by: Tim Wood <washwithcare@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/19031-0638148643d25548@postgresql.org Backpatch-through: 13
1 parent b550682 commit 327b732

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

contrib/pg_trgm/expected/pg_trgm.out

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4693,6 +4693,23 @@ select count(*) from test_trgm where t like '%99%' and t like '%qw%';
46934693
19
46944694
(1 row)
46954695

4696+
explain (costs off)
4697+
select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
4698+
QUERY PLAN
4699+
-------------------------------------------------------------------------
4700+
Aggregate
4701+
-> Bitmap Heap Scan on test_trgm
4702+
Recheck Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
4703+
-> Bitmap Index Scan on trgm_idx
4704+
Index Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
4705+
(5 rows)
4706+
4707+
select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
4708+
count
4709+
-------
4710+
0
4711+
(1 row)
4712+
46964713
-- ensure that pending-list items are handled correctly, too
46974714
create temp table t_test_trgm(t text COLLATE "C");
46984715
create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops);
@@ -4731,6 +4748,23 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
47314748
1
47324749
(1 row)
47334750

4751+
explain (costs off)
4752+
select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
4753+
QUERY PLAN
4754+
-------------------------------------------------------------------------
4755+
Aggregate
4756+
-> Bitmap Heap Scan on t_test_trgm
4757+
Recheck Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
4758+
-> Bitmap Index Scan on t_trgm_idx
4759+
Index Cond: ((t %> ''::text) AND (t %> '%qwerty%'::text))
4760+
(5 rows)
4761+
4762+
select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
4763+
count
4764+
-------
4765+
0
4766+
(1 row)
4767+
47344768
-- run the same queries with sequential scan to check the results
47354769
set enable_bitmapscan=off;
47364770
set enable_seqscan=on;
@@ -4746,6 +4780,12 @@ select count(*) from test_trgm where t like '%99%' and t like '%qw%';
47464780
19
47474781
(1 row)
47484782

4783+
select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
4784+
count
4785+
-------
4786+
0
4787+
(1 row)
4788+
47494789
select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%';
47504790
count
47514791
-------
@@ -4758,6 +4798,12 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
47584798
1
47594799
(1 row)
47604800

4801+
select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
4802+
count
4803+
-------
4804+
0
4805+
(1 row)
4806+
47614807
reset enable_bitmapscan;
47624808
create table test2(t text COLLATE "C");
47634809
insert into test2 values ('abcdef');

contrib/pg_trgm/sql/pg_trgm.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ select count(*) from test_trgm where t like '%99%' and t like '%qwerty%';
8080
explain (costs off)
8181
select count(*) from test_trgm where t like '%99%' and t like '%qw%';
8282
select count(*) from test_trgm where t like '%99%' and t like '%qw%';
83+
explain (costs off)
84+
select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
85+
select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
8386
-- ensure that pending-list items are handled correctly, too
8487
create temp table t_test_trgm(t text COLLATE "C");
8588
create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops);
@@ -90,14 +93,19 @@ select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%';
9093
explain (costs off)
9194
select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
9295
select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
96+
explain (costs off)
97+
select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
98+
select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
9399

94100
-- run the same queries with sequential scan to check the results
95101
set enable_bitmapscan=off;
96102
set enable_seqscan=on;
97103
select count(*) from test_trgm where t like '%99%' and t like '%qwerty%';
98104
select count(*) from test_trgm where t like '%99%' and t like '%qw%';
105+
select count(*) from test_trgm where t %> '' and t %> '%qwerty%';
99106
select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%';
100107
select count(*) from t_test_trgm where t like '%99%' and t like '%qw%';
108+
select count(*) from t_test_trgm where t %> '' and t %> '%qwerty%';
101109
reset enable_bitmapscan;
102110

103111
create table test2(t text COLLATE "C");

src/backend/access/gin/ginscan.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ ginNewScanKey(IndexScanDesc scan)
271271
ScanKeyscankey = scan->keyData;
272272
GinScanOpaque so = (GinScanOpaque) scan->opaque;
273273
inti;
274+
intnumExcludeOnly;
274275
boolhasNullQuery = false;
275276
boolattrHasNormalScan[INDEX_MAX_KEYS] = {false};
276277
MemoryContext oldCtx;
@@ -393,6 +394,7 @@ ginNewScanKey(IndexScanDesc scan)
393394
* excludeOnly scan key must receive a GIN_CAT_EMPTY_QUERY hidden entry
394395
* and be set to normal (excludeOnly = false).
395396
*/
397+
numExcludeOnly = 0;
396398
for (i = 0; i < so->nkeys; i++)
397399
{
398400
GinScanKeykey = &so->keys[i];
@@ -406,6 +408,47 @@ ginNewScanKey(IndexScanDesc scan)
406408
ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY);
407409
attrHasNormalScan[key->attnum - 1] = true;
408410
}
411+
else
412+
numExcludeOnly++;
413+
}
414+
415+
/*
416+
* If we left any excludeOnly scan keys as-is, move them to the end of the
417+
* scan key array: they must appear after normal key(s).
418+
*/
419+
if (numExcludeOnly > 0)
420+
{
421+
GinScanKeytmpkeys;
422+
intiNormalKey;
423+
intiExcludeOnly;
424+
425+
/* We'd better have made at least one normal key */
426+
Assert(numExcludeOnly < so->nkeys);
427+
/* Make a temporary array to hold the re-ordered scan keys */
428+
tmpkeys = (GinScanKey) palloc(so->nkeys * sizeof(GinScanKeyData));
429+
/* Re-order the keys ... */
430+
iNormalKey = 0;
431+
iExcludeOnly = so->nkeys - numExcludeOnly;
432+
for (i = 0; i < so->nkeys; i++)
433+
{
434+
GinScanKeykey = &so->keys[i];
435+
436+
if (key->excludeOnly)
437+
{
438+
memcpy(tmpkeys + iExcludeOnly, key, sizeof(GinScanKeyData));
439+
iExcludeOnly++;
440+
}
441+
else
442+
{
443+
memcpy(tmpkeys + iNormalKey, key, sizeof(GinScanKeyData));
444+
iNormalKey++;
445+
}
446+
}
447+
Assert(iNormalKey == so->nkeys - numExcludeOnly);
448+
Assert(iExcludeOnly == so->nkeys);
449+
/* ... and copy them back to so->keys[] */
450+
memcpy(so->keys, tmpkeys, so->nkeys * sizeof(GinScanKeyData));
451+
pfree(tmpkeys);
409452
}
410453

411454
/*

0 commit comments

Comments
 (0)