/*------------------------------------------------------------------------- * * ginscan.c * routines to manage scans of inverted index relations * * * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/backend/access/gin/ginscan.c *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/gin_private.h" #include "access/relscan.h" #include "pgstat.h" #include "utils/memutils.h" #include "utils/rel.h" IndexScanDesc ginbeginscan(Relation rel, int nkeys, int norderbys) { IndexScanDesc scan; GinScanOpaque so; /* no order by operators allowed */ Assert(norderbys == 0); scan = RelationGetIndexScan(rel, nkeys, norderbys); /* allocate private workspace */ so = (GinScanOpaque) palloc(sizeof(GinScanOpaqueData)); so->keys = NULL; so->nkeys = 0; so->tempCtx = AllocSetContextCreate(CurrentMemoryContext, "Gin scan temporary context", ALLOCSET_DEFAULT_SIZES); so->keyCtx = AllocSetContextCreate(CurrentMemoryContext, "Gin scan key context", ALLOCSET_DEFAULT_SIZES); initGinState(&so->ginstate, scan->indexRelation); scan->opaque = so; return scan; } /* * Create a new GinScanEntry, unless an equivalent one already exists, * in which case just return it */ static GinScanEntry ginFillScanEntry(GinScanOpaque so, OffsetNumber attnum, StrategyNumber strategy, int32 searchMode, Datum queryKey, GinNullCategory queryCategory, bool isPartialMatch, Pointer extra_data) { GinState *ginstate = &so->ginstate; GinScanEntry scanEntry; uint32 i; /* * Look for an existing equivalent entry. * * Entries with non-null extra_data are never considered identical, since * we can't know exactly what the opclass might be doing with that. */ if (extra_data == NULL) { for (i = 0; i < so->totalentries; i++) { GinScanEntry prevEntry = so->entries[i]; if (prevEntry->extra_data == NULL && prevEntry->isPartialMatch == isPartialMatch && prevEntry->strategy == strategy && prevEntry->searchMode == searchMode && prevEntry->attnum == attnum && ginCompareEntries(ginstate, attnum, prevEntry->queryKey, prevEntry->queryCategory, queryKey, queryCategory) == 0) { /* Successful match */ return prevEntry; } } } /* Nope, create a new entry */ scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData)); scanEntry->queryKey = queryKey; scanEntry->queryCategory = queryCategory; scanEntry->isPartialMatch = isPartialMatch; scanEntry->extra_data = extra_data; scanEntry->strategy = strategy; scanEntry->searchMode = searchMode; scanEntry->attnum = attnum; scanEntry->buffer = InvalidBuffer; ItemPointerSetMin(&scanEntry->curItem); scanEntry->matchBitmap = NULL; scanEntry->matchIterator = NULL; scanEntry->matchResult = NULL; scanEntry->list = NULL; scanEntry->nlist = 0; scanEntry->offset = InvalidOffsetNumber; scanEntry->isFinished = false; scanEntry->reduceResult = false; /* Add it to so's array */ if (so->totalentries >= so->allocentries) { so->allocentries *= 2; so->entries = (GinScanEntry *) repalloc(so->entries, so->allocentries * sizeof(GinScanEntry)); } so->entries[so->totalentries++] = scanEntry; return scanEntry; } /* * Append hidden scan entry of given category to the scan key. * * NB: this had better be called at most once per scan key, since * ginFillScanKey leaves room for only one hidden entry. Currently, * it seems sufficiently clear that this is true that we don't bother * with any cross-check logic. */ static void ginScanKeyAddHiddenEntry(GinScanOpaque so, GinScanKey key, GinNullCategory queryCategory) { int i = key->nentries++; /* strategy is of no interest because this is not a partial-match item */ key->scanEntry[i] = ginFillScanEntry(so, key->attnum, InvalidStrategy, key->searchMode, (Datum) 0, queryCategory, false, NULL); } /* * Initialize the next GinScanKey using the output from the extractQueryFn */ 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) { GinScanKey key = &(so->keys[so->nkeys++]); GinState *ginstate = &so->ginstate; uint32 i; key->nentries = nQueryValues; key->nuserentries = nQueryValues; /* Allocate one extra array slot for possible "hidden" entry */ key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) * (nQueryValues + 1)); key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) * (nQueryValues + 1)); key->query = query; key->queryValues = queryValues; key->queryCategories = queryCategories; key->extra_data = extra_data; key->strategy = strategy; key->searchMode = searchMode; key->attnum = attnum; /* * Initially, scan keys of GIN_SEARCH_MODE_ALL mode are marked * excludeOnly. This might get changed later. */ key->excludeOnly = (searchMode == GIN_SEARCH_MODE_ALL); ItemPointerSetMin(&key->curItem); key->curItemMatches = false; key->recheckCurItem = false; key->isFinished = false; key->nrequired = 0; key->nadditional = 0; key->requiredEntries = NULL; key->additionalEntries = NULL; ginInitConsistentFunction(ginstate, key); /* Set up normal scan entries using extractQueryFn's outputs */ for (i = 0; i < nQueryValues; i++) { Datum queryKey; GinNullCategory queryCategory; bool isPartialMatch; Pointer this_extra; queryKey = queryValues[i]; queryCategory = queryCategories[i]; isPartialMatch = (ginstate->canPartialMatch[attnum - 1] && partial_matches) ? partial_matches[i] : false; this_extra = (extra_data) ? extra_data[i] : NULL; key->scanEntry[i] = ginFillScanEntry(so, attnum, strategy, searchMode, queryKey, queryCategory, isPartialMatch, this_extra); } /* * For GIN_SEARCH_MODE_INCLUDE_EMPTY and GIN_SEARCH_MODE_EVERYTHING search * modes, we add the "hidden" entry immediately. GIN_SEARCH_MODE_ALL is * handled later, since we might be able to omit the hidden entry for it. */ if (searchMode == GIN_SEARCH_MODE_INCLUDE_EMPTY) ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_ITEM); else if (searchMode == GIN_SEARCH_MODE_EVERYTHING) ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY); } /* * Release current scan keys, if any. */ void ginFreeScanKeys(GinScanOpaque so) { uint32 i; if (so->keys == NULL) return; for (i = 0; i < so->totalentries; i++) { GinScanEntry entry = so->entries[i]; if (entry->buffer != InvalidBuffer) ReleaseBuffer(entry->buffer); if (entry->list) pfree(entry->list); if (entry->matchIterator) tbm_end_iterate(entry->matchIterator); if (entry->matchBitmap) tbm_free(entry->matchBitmap); } MemoryContextResetAndDeleteChildren(so->keyCtx); so->keys = NULL; so->nkeys = 0; so->entries = NULL; so->totalentries = 0; } void ginNewScanKey(IndexScanDesc scan) { ScanKey scankey = scan->keyData; GinScanOpaque so = (GinScanOpaque) scan->opaque; int i; bool hasNullQuery = false; bool attrHasNormalScan[INDEX_MAX_KEYS] = {false}; MemoryContext oldCtx; /* * Allocate all the scan key information in the key context. (If * extractQuery leaks anything there, it won't be reset until the end of * scan or rescan, but that's OK.) */ oldCtx = MemoryContextSwitchTo(so->keyCtx); /* if no scan keys provided, allocate extra EVERYTHING GinScanKey */ so->keys = (GinScanKey) palloc(Max(scan->numberOfKeys, 1) * sizeof(GinScanKeyData)); so->nkeys = 0; /* initialize expansible array of GinScanEntry pointers */ so->totalentries = 0; so->allocentries = 32; so->entries = (GinScanEntry *) palloc(so->allocentries * sizeof(GinScanEntry)); so->isVoidRes = false; for (i = 0; i < scan->numberOfKeys; i++) { ScanKey skey = &scankey[i]; Datum *queryValues; int32 nQueryValues = 0; bool *partial_matches = NULL; Pointer *extra_data = NULL; bool *nullFlags = NULL; GinNullCategory *categories; int32 searchMode = GIN_SEARCH_MODE_DEFAULT; /* * We assume that GIN-indexable operators are strict, so a null query * argument means an unsatisfiable query. */ if (skey->sk_flags & SK_ISNULL) { so->isVoidRes = true; break; } /* OK to call the extractQueryFn */ queryValues = (Datum *) DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1], so->ginstate.supportCollation[skey->sk_attno - 1], skey->sk_argument, PointerGetDatum(&nQueryValues), UInt16GetDatum(skey->sk_strategy), PointerGetDatum(&partial_matches), PointerGetDatum(&extra_data), PointerGetDatum(&nullFlags), PointerGetDatum(&searchMode))); /* * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note * in particular we don't allow extractQueryFn to select * GIN_SEARCH_MODE_EVERYTHING. */ if (searchMode < GIN_SEARCH_MODE_DEFAULT || searchMode > GIN_SEARCH_MODE_ALL) searchMode = GIN_SEARCH_MODE_ALL; /* Non-default modes require the index to have placeholders */ if (searchMode != GIN_SEARCH_MODE_DEFAULT) hasNullQuery = true; /* * In default mode, no keys means an unsatisfiable query. */ if (queryValues == NULL || nQueryValues <= 0) { if (searchMode == GIN_SEARCH_MODE_DEFAULT) { so->isVoidRes = true; break; } nQueryValues = 0; /* ensure sane value */ } /* * Create GinNullCategory representation. If the extractQueryFn * didn't create a nullFlags array, we assume everything is non-null. * While at it, detect whether any null keys are present. */ categories = (GinNullCategory *) palloc0(nQueryValues * sizeof(GinNullCategory)); if (nullFlags) { int32 j; for (j = 0; j < nQueryValues; j++) { if (nullFlags[j]) { categories[j] = GIN_CAT_NULL_KEY; hasNullQuery = true; } } } ginFillScanKey(so, skey->sk_attno, skey->sk_strategy, searchMode, skey->sk_argument, nQueryValues, queryValues, categories, partial_matches, extra_data); /* Remember if we had any non-excludeOnly keys */ if (searchMode != GIN_SEARCH_MODE_ALL) attrHasNormalScan[skey->sk_attno - 1] = true; } /* * Processing GIN_SEARCH_MODE_ALL scan keys requires us to make a second * pass over the scan keys. Above we marked each such scan key as * excludeOnly. If the involved column has any normal (not excludeOnly) * scan key as well, then we can leave it like that. Otherwise, one * excludeOnly scan key must receive a GIN_CAT_EMPTY_QUERY hidden entry * and be set to normal (excludeOnly = false). */ for (i = 0; i < so->nkeys; i++) { GinScanKey key = &so->keys[i]; if (key->searchMode != GIN_SEARCH_MODE_ALL) continue; if (!attrHasNormalScan[key->attnum - 1]) { key->excludeOnly = false; ginScanKeyAddHiddenEntry(so, key, GIN_CAT_EMPTY_QUERY); attrHasNormalScan[key->attnum - 1] = true; } } /* * If there are no regular scan keys, generate an EVERYTHING scankey to * drive a full-index scan. */ if (so->nkeys == 0 && !so->isVoidRes) { hasNullQuery = true; ginFillScanKey(so, FirstOffsetNumber, InvalidStrategy, GIN_SEARCH_MODE_EVERYTHING, (Datum) 0, 0, NULL, NULL, NULL, NULL); } /* * If the index is version 0, it may be missing null and placeholder * entries, which would render searches for nulls and full-index scans * unreliable. Throw an error if so. */ if (hasNullQuery && !so->isVoidRes) { GinStatsData ginStats; ginGetStats(scan->indexRelation, &ginStats); if (ginStats.ginVersion < 1) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("old GIN indexes do not support whole-index scans nor searches for nulls"), errhint("To fix this, do REINDEX INDEX \"%s\".", RelationGetRelationName(scan->indexRelation)))); } MemoryContextSwitchTo(oldCtx); pgstat_count_index_scan(scan->indexRelation); } void ginrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys) { GinScanOpaque so = (GinScanOpaque) scan->opaque; ginFreeScanKeys(so); if (scankey && scan->numberOfKeys > 0) { memmove(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData)); } } void ginendscan(IndexScanDesc scan) { GinScanOpaque so = (GinScanOpaque) scan->opaque; ginFreeScanKeys(so); MemoryContextDelete(so->tempCtx); MemoryContextDelete(so->keyCtx); pfree(so); }