Coverage Report

Created: 2026-08-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/gin/ginscan.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * ginscan.c
4
 *    routines to manage scans of inverted index relations
5
 *
6
 *
7
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1994, Regents of the University of California
9
 *
10
 * IDENTIFICATION
11
 *      src/backend/access/gin/ginscan.c
12
 *-------------------------------------------------------------------------
13
 */
14
15
#include "postgres.h"
16
17
#include "access/gin_private.h"
18
#include "access/relscan.h"
19
#include "executor/instrument_node.h"
20
#include "pgstat.h"
21
#include "utils/memutils.h"
22
#include "utils/rel.h"
23
24
25
IndexScanDesc
26
ginbeginscan(Relation rel, int nkeys, int norderbys)
27
0
{
28
0
  IndexScanDesc scan;
29
0
  GinScanOpaque so;
30
31
  /* no order by operators allowed */
32
0
  Assert(norderbys == 0);
33
34
0
  scan = RelationGetIndexScan(rel, nkeys, norderbys);
35
36
  /* allocate private workspace */
37
0
  so = (GinScanOpaque) palloc_object(GinScanOpaqueData);
38
0
  so->keys = NULL;
39
0
  so->nkeys = 0;
40
0
  so->tempCtx = AllocSetContextCreate(CurrentMemoryContext,
41
0
                    "Gin scan temporary context",
42
0
                    ALLOCSET_DEFAULT_SIZES);
43
0
  so->keyCtx = AllocSetContextCreate(CurrentMemoryContext,
44
0
                     "Gin scan key context",
45
0
                     ALLOCSET_DEFAULT_SIZES);
46
0
  initGinState(&so->ginstate, scan->indexRelation);
47
48
0
  scan->opaque = so;
49
50
0
  return scan;
51
0
}
52
53
/*
54
 * Create a new GinScanEntry, unless an equivalent one already exists,
55
 * in which case just return it
56
 */
57
static GinScanEntry
58
ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum,
59
         StrategyNumber strategy, int32 searchMode,
60
         Datum queryKey, GinNullCategory queryCategory,
61
         bool isPartialMatch, Pointer extra_data)
62
0
{
63
0
  GinState   *ginstate = &so->ginstate;
64
0
  GinScanEntry scanEntry;
65
0
  uint32    i;
66
67
  /*
68
   * Look for an existing equivalent entry.
69
   *
70
   * Entries with non-null extra_data are never considered identical, since
71
   * we can't know exactly what the opclass might be doing with that.
72
   *
73
   * Also, give up de-duplication once we have 100 entries.  That avoids
74
   * spending O(N^2) time on probably-fruitless de-duplication of large
75
   * search-key sets.  The threshold of 100 is arbitrary but matches
76
   * predtest.c's threshold for what's a large array.
77
   */
78
0
  if (extra_data == NULL && so->totalentries < 100)
79
0
  {
80
0
    for (i = 0; i < so->totalentries; i++)
81
0
    {
82
0
      GinScanEntry prevEntry = so->entries[i];
83
84
0
      if (prevEntry->extra_data == NULL &&
85
0
        prevEntry->isPartialMatch == isPartialMatch &&
86
0
        prevEntry->strategy == strategy &&
87
0
        prevEntry->searchMode == searchMode &&
88
0
        prevEntry->attnum == attnum &&
89
0
        ginCompareEntries(ginstate, attnum,
90
0
                  prevEntry->queryKey,
91
0
                  prevEntry->queryCategory,
92
0
                  queryKey,
93
0
                  queryCategory) == 0)
94
0
      {
95
        /* Successful match */
96
0
        return prevEntry;
97
0
      }
98
0
    }
99
0
  }
100
101
  /* Nope, create a new entry */
102
0
  scanEntry = palloc_object(GinScanEntryData);
103
0
  scanEntry->queryKey = queryKey;
104
0
  scanEntry->queryCategory = queryCategory;
105
0
  scanEntry->isPartialMatch = isPartialMatch;
106
0
  scanEntry->extra_data = extra_data;
107
0
  scanEntry->strategy = strategy;
108
0
  scanEntry->searchMode = searchMode;
109
0
  scanEntry->attnum = attnum;
110
111
0
  scanEntry->buffer = InvalidBuffer;
112
0
  ItemPointerSetMin(&scanEntry->curItem);
113
0
  scanEntry->matchBitmap = NULL;
114
0
  scanEntry->matchIterator = NULL;
115
0
  scanEntry->matchResult.blockno = InvalidBlockNumber;
116
0
  scanEntry->matchNtuples = -1;
117
0
  scanEntry->list = NULL;
118
0
  scanEntry->nlist = 0;
119
0
  scanEntry->offset = InvalidOffsetNumber;
120
0
  scanEntry->isFinished = false;
121
0
  scanEntry->reduceResult = false;
122
123
  /* Add it to so's array */
124
0
  if (so->totalentries >= so->allocentries)
125
0
  {
126
0
    so->allocentries *= 2;
127
0
    so->entries = repalloc_array(so->entries, GinScanEntry, so->allocentries);
128
0
  }
129
0
  so->entries[so->totalentries++] = scanEntry;
130
131
0
  return scanEntry;
132
0
}
133
134
/*
135
 * Append hidden scan entry of given category to the scan key.
136
 *
137
 * NB: this had better be called at most once per scan key, since
138
 * ginFillScanKey leaves room for only one hidden entry.  Currently,
139
 * it seems sufficiently clear that this is true that we don't bother
140
 * with any cross-check logic.
141
 */
142
static void
143
ginScanKeyAddHiddenEntry(GinScanOpaque so, GinScanKey key,
144
             GinNullCategory queryCategory)
145
0
{
146
0
  int     i = key->nentries++;
147
148
  /* strategy is of no interest because this is not a partial-match item */
149
0
  key->scanEntry[i] = ginFillScanEntry(so, key->attnum,
150
0
                     InvalidStrategy, key->searchMode,
151
0
                     (Datum) 0, queryCategory,
152
0
                     false, NULL);
153
0
}
154
155
/*
156
 * Initialize the next GinScanKey using the output from the extractQueryFn
157
 */
158
static void
159
ginFillScanKey(GinScanOpaque so, OffsetNumber attnum,
160
         StrategyNumber strategy, int32 searchMode,
161
         Datum query, uint32 nQueryValues,
162
         Datum *queryValues, GinNullCategory *queryCategories,
163
         bool *partial_matches, Pointer *extra_data)
164
0
{
165
0
  GinScanKey  key = &(so->keys[so->nkeys++]);
166
0
  GinState   *ginstate = &so->ginstate;
167
0
  uint32    i;
168
169
0
  key->nentries = nQueryValues;
170
0
  key->nuserentries = nQueryValues;
171
172
  /* Allocate one extra array slot for possible "hidden" entry */
173
0
  key->scanEntry = palloc_array(GinScanEntry, nQueryValues + 1);
174
0
  key->entryRes = palloc0_array(GinTernaryValue, nQueryValues + 1);
175
176
0
  key->query = query;
177
0
  key->queryValues = queryValues;
178
0
  key->queryCategories = queryCategories;
179
0
  key->extra_data = extra_data;
180
0
  key->strategy = strategy;
181
0
  key->searchMode = searchMode;
182
0
  key->attnum = attnum;
183
184
  /*
185
   * Initially, scan keys of GIN_SEARCH_MODE_ALL mode are marked
186
   * excludeOnly.  This might get changed later.
187
   */
188
0
  key->excludeOnly = (searchMode == GIN_SEARCH_MODE_ALL);
189
190
0
  ItemPointerSetMin(&key->curItem);
191
0
  key->curItemMatches = false;
192
0
  key->recheckCurItem = false;
193
0
  key->isFinished = false;
194
0
  key->nrequired = 0;
195
0
  key->nadditional = 0;
196
0
  key->requiredEntries = NULL;
197
0
  key->additionalEntries = NULL;
198
199
0
  ginInitConsistentFunction(ginstate, key);
200
201
  /* Set up normal scan entries using extractQueryFn's outputs */
202
0
  for (i = 0; i < nQueryValues; i++)
203
0
  {
204
0
    Datum   queryKey;
205
0
    GinNullCategory queryCategory;
206
0
    bool    isPartialMatch;
207
0
    Pointer   this_extra;
208
209
0
    queryKey = queryValues[i];
210
0
    queryCategory = queryCategories[i];
211
0
    isPartialMatch =
212
0
      (ginstate->canPartialMatch[attnum - 1] && partial_matches)
213
0
      ? partial_matches[i] : false;
214
0
    this_extra = (extra_data) ? extra_data[i] : NULL;
215
216
0
    key->scanEntry[i] = ginFillScanEntry(so, attnum,
217
0
                       strategy, searchMode,
218
0
                       queryKey, queryCategory,
219
0
                       isPartialMatch, this_extra);
220
0
  }
221
222
  /*
223
   * For GIN_SEARCH_MODE_INCLUDE_EMPTY and GIN_SEARCH_MODE_EVERYTHING search
224
   * modes, we add the "hidden" entry immediately.  GIN_SEARCH_MODE_ALL is
225
   * handled later, since we might be able to omit the hidden entry for it.
226
   */
227
0
  if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY)
228
0
    ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_ITEM);
229
0
  else if (searchMode == GIN_SEARCH_MODE_EVERYTHING)
230
0
    ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY);
231
0
}
232
233
/*
234
 * Release current scan keys, if any.
235
 */
236
void
237
ginFreeScanKeys(GinScanOpaque so)
238
0
{
239
0
  uint32    i;
240
241
0
  if (so->keys == NULL)
242
0
    return;
243
244
0
  for (i = 0; i < so->totalentries; i++)
245
0
  {
246
0
    GinScanEntry entry = so->entries[i];
247
248
0
    if (entry->buffer != InvalidBuffer)
249
0
      ReleaseBuffer(entry->buffer);
250
0
    if (entry->list)
251
0
      pfree(entry->list);
252
0
    if (entry->matchIterator)
253
0
      tbm_end_private_iterate(entry->matchIterator);
254
0
    if (entry->matchBitmap)
255
0
      tbm_free(entry->matchBitmap);
256
0
  }
257
258
0
  MemoryContextReset(so->keyCtx);
259
260
0
  so->keys = NULL;
261
0
  so->nkeys = 0;
262
0
  so->entries = NULL;
263
0
  so->totalentries = 0;
264
0
}
265
266
void
267
ginNewScanKey(IndexScanDesc scan)
268
0
{
269
0
  ScanKey   scankey = scan->keyData;
270
0
  GinScanOpaque so = (GinScanOpaque) scan->opaque;
271
0
  int     numExcludeOnly;
272
0
  bool    hasNullQuery = false;
273
0
  bool    attrHasNormalScan[INDEX_MAX_KEYS] = {false};
274
0
  MemoryContext oldCtx;
275
276
  /*
277
   * Allocate all the scan key information in the key context. (If
278
   * extractQuery leaks anything there, it won't be reset until the end of
279
   * scan or rescan, but that's OK.)
280
   */
281
0
  oldCtx = MemoryContextSwitchTo(so->keyCtx);
282
283
  /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */
284
0
  so->keys = (GinScanKey)
285
0
    palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData));
286
0
  so->nkeys = 0;
287
288
  /* initialize expansible array of GinScanEntry pointers */
289
0
  so->totalentries = 0;
290
0
  so->allocentries = 32;
291
0
  so->entries = (GinScanEntry *)
292
0
    palloc(so->allocentries * sizeof(GinScanEntry));
293
294
0
  so->isVoidRes = false;
295
296
0
  for (int i = 0; i < scan->numberOfKeys; i++)
297
0
  {
298
0
    ScanKey   skey = &scankey[i];
299
0
    Datum    *queryValues;
300
0
    int32   nQueryValues = 0;
301
0
    bool     *partial_matches = NULL;
302
0
    Pointer    *extra_data = NULL;
303
0
    bool     *nullFlags = NULL;
304
0
    GinNullCategory *categories;
305
0
    int32   searchMode = GIN_SEARCH_MODE_DEFAULT;
306
307
    /*
308
     * We assume that GIN-indexable operators are strict, so a null query
309
     * argument means an unsatisfiable query.
310
     */
311
0
    if (skey->sk_flags & SK_ISNULL)
312
0
    {
313
0
      so->isVoidRes = true;
314
0
      break;
315
0
    }
316
317
    /* OK to call the extractQueryFn */
318
0
    queryValues = (Datum *)
319
0
      DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1],
320
0
                        so->ginstate.supportCollation[skey->sk_attno - 1],
321
0
                        skey->sk_argument,
322
0
                        PointerGetDatum(&nQueryValues),
323
0
                        UInt16GetDatum(skey->sk_strategy),
324
0
                        PointerGetDatum(&partial_matches),
325
0
                        PointerGetDatum(&extra_data),
326
0
                        PointerGetDatum(&nullFlags),
327
0
                        PointerGetDatum(&searchMode)));
328
329
    /*
330
     * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note
331
     * in particular we don't allow extractQueryFn to select
332
     * GIN_SEARCH_MODE_EVERYTHING.
333
     */
334
0
    if (searchMode < GIN_SEARCH_MODE_DEFAULT ||
335
0
      searchMode > GIN_SEARCH_MODE_ALL)
336
0
      searchMode = GIN_SEARCH_MODE_ALL;
337
338
    /* Non-default modes require the index to have placeholders */
339
0
    if (searchMode != GIN_SEARCH_MODE_DEFAULT)
340
0
      hasNullQuery = true;
341
342
    /*
343
     * In default mode, no keys means an unsatisfiable query.
344
     */
345
0
    if (queryValues == NULL || nQueryValues <= 0)
346
0
    {
347
0
      if (searchMode == GIN_SEARCH_MODE_DEFAULT)
348
0
      {
349
0
        so->isVoidRes = true;
350
0
        break;
351
0
      }
352
0
      nQueryValues = 0; /* ensure sane value */
353
0
    }
354
355
    /*
356
     * Create GinNullCategory representation.  If the extractQueryFn
357
     * didn't create a nullFlags array, we assume everything is non-null.
358
     * While at it, detect whether any null keys are present.
359
     */
360
0
    categories = (GinNullCategory *) palloc0(nQueryValues * sizeof(GinNullCategory));
361
0
    if (nullFlags)
362
0
    {
363
0
      int32   j;
364
365
0
      for (j = 0; j < nQueryValues; j++)
366
0
      {
367
0
        if (nullFlags[j])
368
0
        {
369
0
          categories[j] = GIN_CAT_NULL_KEY;
370
0
          hasNullQuery = true;
371
0
        }
372
0
      }
373
0
    }
374
375
0
    ginFillScanKey(so, skey->sk_attno,
376
0
             skey->sk_strategy, searchMode,
377
0
             skey->sk_argument, nQueryValues,
378
0
             queryValues, categories,
379
0
             partial_matches, extra_data);
380
381
    /* Remember if we had any non-excludeOnly keys */
382
0
    if (searchMode != GIN_SEARCH_MODE_ALL)
383
0
      attrHasNormalScan[skey->sk_attno - 1] = true;
384
0
  }
385
386
  /*
387
   * Processing GIN_SEARCH_MODE_ALL scan keys requires us to make a second
388
   * pass over the scan keys.  Above we marked each such scan key as
389
   * excludeOnly.  If the involved column has any normal (not excludeOnly)
390
   * scan key as well, then we can leave it like that.  Otherwise, one
391
   * excludeOnly scan key must receive a GIN_CAT_EMPTY_QUERY hidden entry
392
   * and be set to normal (excludeOnly = false).
393
   */
394
0
  numExcludeOnly = 0;
395
0
  for (uint32 i = 0; i < so->nkeys; i++)
396
0
  {
397
0
    GinScanKey  key = &so->keys[i];
398
399
0
    if (key->searchMode != GIN_SEARCH_MODE_ALL)
400
0
      continue;
401
402
0
    if (!attrHasNormalScan[key->attnum - 1])
403
0
    {
404
0
      key->excludeOnly = false;
405
0
      ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY);
406
0
      attrHasNormalScan[key->attnum - 1] = true;
407
0
    }
408
0
    else
409
0
      numExcludeOnly++;
410
0
  }
411
412
  /*
413
   * If we left any excludeOnly scan keys as-is, move them to the end of the
414
   * scan key array: they must appear after normal key(s).
415
   */
416
0
  if (numExcludeOnly > 0)
417
0
  {
418
0
    GinScanKey  tmpkeys;
419
0
    int     iNormalKey;
420
0
    int     iExcludeOnly;
421
422
    /* We'd better have made at least one normal key */
423
0
    Assert(numExcludeOnly < so->nkeys);
424
    /* Make a temporary array to hold the re-ordered scan keys */
425
0
    tmpkeys = (GinScanKey) palloc(so->nkeys * sizeof(GinScanKeyData));
426
    /* Re-order the keys ... */
427
0
    iNormalKey = 0;
428
0
    iExcludeOnly = so->nkeys - numExcludeOnly;
429
0
    for (uint32 i = 0; i < so->nkeys; i++)
430
0
    {
431
0
      GinScanKey  key = &so->keys[i];
432
433
0
      if (key->excludeOnly)
434
0
      {
435
0
        memcpy(tmpkeys + iExcludeOnly, key, sizeof(GinScanKeyData));
436
0
        iExcludeOnly++;
437
0
      }
438
0
      else
439
0
      {
440
0
        memcpy(tmpkeys + iNormalKey, key, sizeof(GinScanKeyData));
441
0
        iNormalKey++;
442
0
      }
443
0
    }
444
0
    Assert(iNormalKey == so->nkeys - numExcludeOnly);
445
0
    Assert(iExcludeOnly == so->nkeys);
446
    /* ... and copy them back to so->keys[] */
447
0
    memcpy(so->keys, tmpkeys, so->nkeys * sizeof(GinScanKeyData));
448
0
    pfree(tmpkeys);
449
0
  }
450
451
  /*
452
   * If there are no regular scan keys, generate an EVERYTHING scankey to
453
   * drive a full-index scan.
454
   */
455
0
  if (so->nkeys == 0 && !so->isVoidRes)
456
0
  {
457
0
    hasNullQuery = true;
458
0
    ginFillScanKey(so, FirstOffsetNumber,
459
0
             InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING,
460
0
             (Datum) 0, 0,
461
0
             NULL, NULL, NULL, NULL);
462
0
  }
463
464
  /*
465
   * If the index is version 0, it may be missing null and placeholder
466
   * entries, which would render searches for nulls and full-index scans
467
   * unreliable.  Throw an error if so.
468
   */
469
0
  if (hasNullQuery && !so->isVoidRes)
470
0
  {
471
0
    GinStatsData ginStats;
472
473
0
    ginGetStats(scan->indexRelation, &ginStats);
474
0
    if (ginStats.ginVersion < 1)
475
0
      ereport(ERROR,
476
0
          (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
477
0
           errmsg("old GIN indexes do not support whole-index scans nor searches for nulls"),
478
0
           errhint("To fix this, do REINDEX INDEX \"%s\".",
479
0
               RelationGetRelationName(scan->indexRelation))));
480
0
  }
481
482
0
  MemoryContextSwitchTo(oldCtx);
483
484
0
  pgstat_count_index_scan(scan->indexRelation);
485
0
  if (scan->instrument)
486
0
    scan->instrument->nsearches++;
487
0
}
488
489
void
490
ginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
491
      ScanKey orderbys, int norderbys)
492
0
{
493
0
  GinScanOpaque so = (GinScanOpaque) scan->opaque;
494
495
0
  ginFreeScanKeys(so);
496
497
0
  if (scankey && scan->numberOfKeys > 0)
498
0
    memcpy(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData));
499
0
}
500
501
502
void
503
ginendscan(IndexScanDesc scan)
504
0
{
505
0
  GinScanOpaque so = (GinScanOpaque) scan->opaque;
506
507
0
  ginFreeScanKeys(so);
508
509
0
  MemoryContextDelete(so->tempCtx);
510
0
  MemoryContextDelete(so->keyCtx);
511
512
0
  pfree(so);
513
0
}