1/*-------------------------------------------------------------------------
4 * routines to manage scans of inverted index relations
7 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/access/gin/ginscan.c
12 *-------------------------------------------------------------------------
30 /* no order by operators allowed */
35 /* allocate private workspace */
40 "Gin scan temporary context",
43 "Gin scan key context",
53 * Create a new GinScanEntry, unless an equivalent one already exists,
54 * in which case just return it
60 bool isPartialMatch,
Pointer extra_data)
67 * Look for an existing equivalent entry.
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.
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.
94 /* Successful match */
100 /* Nope, create a new entry */
116 scanEntry->
list = NULL;
117 scanEntry->
nlist = 0;
122 /* Add it to so's array */
135 * Append hidden scan entry of given category to the scan key.
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.
146 int i =
key->nentries++;
148 /* strategy is of no interest because this is not a partial-match item */
151 (
Datum) 0, queryCategory,
156 * Initialize the next GinScanKey using the output from the extractQueryFn
163 bool *partial_matches,
Pointer *extra_data)
169 key->nentries = nQueryValues;
170 key->nuserentries = nQueryValues;
172 /* Allocate one extra array slot for possible "hidden" entry */
179 key->queryValues = queryValues;
180 key->queryCategories = queryCategories;
181 key->extra_data = extra_data;
182 key->strategy = strategy;
183 key->searchMode = searchMode;
187 * Initially, scan keys of GIN_SEARCH_MODE_ALL mode are marked
188 * excludeOnly. This might get changed later.
193 key->curItemMatches =
false;
194 key->recheckCurItem =
false;
195 key->isFinished =
false;
197 key->nadditional = 0;
198 key->requiredEntries = NULL;
199 key->additionalEntries = NULL;
203 /* Set up normal scan entries using extractQueryFn's outputs */
204 for (
i = 0;
i < nQueryValues;
i++)
211 queryKey = queryValues[
i];
212 queryCategory = queryCategories[
i];
215 ? partial_matches[
i] :
false;
216 this_extra = (extra_data) ? extra_data[
i] : NULL;
219 strategy, searchMode,
220 queryKey, queryCategory,
221 isPartialMatch, this_extra);
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.
236 * Release current scan keys, if any.
243 if (so->
keys == NULL)
275 bool hasNullQuery =
false;
280 * Allocate all the scan key information in the key context. (If
281 * extractQuery leaks anything there, it won't be reset until the end of
282 * scan or rescan, but that's OK.)
286 /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */
291 /* initialize expansible array of GinScanEntry pointers */
292 so->totalentries = 0;
293 so->allocentries = 32;
297 so->isVoidRes =
false;
299 for (
i = 0;
i < scan->numberOfKeys;
i++)
303 int32 nQueryValues = 0;
304 bool *partial_matches = NULL;
306 bool *nullFlags = NULL;
311 * We assume that GIN-indexable operators are strict, so a null query
312 * argument means an unsatisfiable query.
316 so->isVoidRes =
true;
320 /* OK to call the extractQueryFn */
321 queryValues = (
Datum *)
323 so->ginstate.supportCollation[skey->
sk_attno - 1],
333 * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note
334 * in particular we don't allow extractQueryFn to select
335 * GIN_SEARCH_MODE_EVERYTHING.
341 /* Non-default modes require the index to have placeholders */
346 * In default mode, no keys means an unsatisfiable query.
348 if (queryValues == NULL || nQueryValues <= 0)
352 so->isVoidRes =
true;
355 nQueryValues = 0;
/* ensure sane value */
359 * Create GinNullCategory representation. If the extractQueryFn
360 * didn't create a nullFlags array, we assume everything is non-null.
361 * While at it, detect whether any null keys are present.
368 for (
j = 0;
j < nQueryValues;
j++)
381 queryValues, categories,
382 partial_matches, extra_data);
384 /* Remember if we had any non-excludeOnly keys */
386 attrHasNormalScan[skey->
sk_attno - 1] =
true;
390 * Processing GIN_SEARCH_MODE_ALL scan keys requires us to make a second
391 * pass over the scan keys. Above we marked each such scan key as
392 * excludeOnly. If the involved column has any normal (not excludeOnly)
393 * scan key as well, then we can leave it like that. Otherwise, one
394 * excludeOnly scan key must receive a GIN_CAT_EMPTY_QUERY hidden entry
395 * and be set to normal (excludeOnly = false).
398 for (
i = 0;
i < so->nkeys;
i++)
405 if (!attrHasNormalScan[
key->attnum - 1])
407 key->excludeOnly =
false;
409 attrHasNormalScan[
key->attnum - 1] =
true;
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).
419 if (numExcludeOnly > 0)
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 */
429 /* Re-order the keys ... */
431 iExcludeOnly = so->nkeys - numExcludeOnly;
432 for (
i = 0;
i < so->nkeys;
i++)
436 if (
key->excludeOnly)
447 Assert(iNormalKey == so->nkeys - numExcludeOnly);
448 Assert(iExcludeOnly == so->nkeys);
449 /* ... and copy them back to so->keys[] */
455 * If there are no regular scan keys, generate an EVERYTHING scankey to
456 * drive a full-index scan.
458 if (so->nkeys == 0 && !so->isVoidRes)
464 NULL, NULL, NULL, NULL);
468 * If the index is version 0, it may be missing null and placeholder
469 * entries, which would render searches for nulls and full-index scans
470 * unreliable. Throw an error if so.
472 if (hasNullQuery && !so->isVoidRes)
479 (
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
480 errmsg(
"old GIN indexes do not support whole-index scans nor searches for nulls"),
481 errhint(
"To fix this, do REINDEX INDEX \"%s\".",
488 if (scan->instrument)
489 scan->instrument->nsearches++;
494 ScanKey orderbys,
int norderbys)
#define InvalidBlockNumber
void ReleaseBuffer(Buffer buffer)
int errhint(const char *fmt,...)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
Datum FunctionCall7Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, Datum arg3, Datum arg4, Datum arg5, Datum arg6, Datum arg7)
IndexScanDesc RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
#define GIN_SEARCH_MODE_ALL
#define GIN_SEARCH_MODE_EVERYTHING
#define GIN_SEARCH_MODE_DEFAULT
#define GIN_SEARCH_MODE_INCLUDE_EMPTY
struct GinScanKeyData GinScanKeyData
GinScanOpaqueData * GinScanOpaque
struct GinScanEntryData * GinScanEntry
struct GinScanKeyData * GinScanKey
#define GIN_CAT_EMPTY_ITEM
signed char GinNullCategory
#define ItemPointerSetMin(p)
#define GIN_CAT_EMPTY_QUERY
void ginInitConsistentFunction(GinState *ginstate, GinScanKey key)
IndexScanDesc ginbeginscan(Relation rel, int nkeys, int norderbys)
void ginFreeScanKeys(GinScanOpaque so)
static void ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, StrategyNumber strategy, int32 searchMode, Datum query, uint32 nQueryValues, Datum *queryValues, GinNullCategory *queryCategories, bool *partial_matches, Pointer *extra_data)
void ginendscan(IndexScanDesc scan)
void ginNewScanKey(IndexScanDesc scan)
void ginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys)
static void ginScanKeyAddHiddenEntry(GinScanOpaque so, GinScanKey key, GinNullCategory queryCategory)
static GinScanEntry ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum, StrategyNumber strategy, int32 searchMode, Datum queryKey, GinNullCategory queryCategory, bool isPartialMatch, Pointer extra_data)
void ginGetStats(Relation index, GinStatsData *stats)
void initGinState(GinState *state, Relation index)
int ginCompareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, GinNullCategory categorya, Datum b, GinNullCategory categoryb)
Assert(PointerIsAligned(start, uint64))
void MemoryContextReset(MemoryContext context)
void * repalloc(void *pointer, Size size)
void pfree(void *pointer)
void * palloc0(Size size)
MemoryContext CurrentMemoryContext
void MemoryContextDelete(MemoryContext context)
#define AllocSetContextCreate
#define ALLOCSET_DEFAULT_SIZES
#define InvalidOffsetNumber
#define FirstOffsetNumber
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
#define pgstat_count_index_scan(rel)
static Datum PointerGetDatum(const void *X)
static Datum UInt16GetDatum(uint16 X)
static Pointer DatumGetPointer(Datum X)
#define RelationGetRelationName(relation)
TBMIterateResult matchResult
TBMPrivateIterator * matchIterator
GinNullCategory queryCategory
bool canPartialMatch[INDEX_MAX_KEYS]
struct ScanKeyData * keyData
StrategyNumber sk_strategy
void tbm_free(TIDBitmap *tbm)
void tbm_end_private_iterate(TBMPrivateIterator *iterator)