Coverage Report

Created: 2025-08-12 06:43

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