postgresql/src/backend/access/gin/ginscan.c

440 lines
11 KiB
C

/*-------------------------------------------------------------------------
*
* ginscan.c
* routines to manage scans of inverted index relations
*
*
* Portions Copyright (c) 1996-2019, 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;
}
/*
* 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 nUserQueryValues = nQueryValues;
uint32 i;
/* Non-default search modes add one "hidden" entry to each key */
if (searchMode != GIN_SEARCH_MODE_DEFAULT)
nQueryValues++;
key->nentries = nQueryValues;
key->nuserentries = nUserQueryValues;
key->scanEntry = (GinScanEntry *) palloc(sizeof(GinScanEntry) * nQueryValues);
key->entryRes = (GinTernaryValue *) palloc0(sizeof(GinTernaryValue) * nQueryValues);
key->query = query;
key->queryValues = queryValues;
key->queryCategories = queryCategories;
key->extra_data = extra_data;
key->strategy = strategy;
key->searchMode = searchMode;
key->attnum = attnum;
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);
for (i = 0; i < nQueryValues; i++)
{
Datum queryKey;
GinNullCategory queryCategory;
bool isPartialMatch;
Pointer this_extra;
if (i < nUserQueryValues)
{
/* set up normal entry using extractQueryFn's outputs */
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;
}
else
{
/* set up hidden entry */
queryKey = (Datum) 0;
switch (searchMode)
{
case GIN_SEARCH_MODE_INCLUDE_EMPTY:
queryCategory = GIN_CAT_EMPTY_ITEM;
break;
case GIN_SEARCH_MODE_ALL:
queryCategory = GIN_CAT_EMPTY_QUERY;
break;
case GIN_SEARCH_MODE_EVERYTHING:
queryCategory = GIN_CAT_EMPTY_QUERY;
break;
default:
elog(ERROR, "unexpected searchMode: %d", searchMode);
queryCategory = 0; /* keep compiler quiet */
break;
}
isPartialMatch = false;
this_extra = NULL;
/*
* We set the strategy to a fixed value so that ginFillScanEntry
* can combine these entries for different scan keys. This is
* safe because the strategy value in the entry struct is only
* used for partial-match cases. It's OK to overwrite our local
* variable here because this is the last loop iteration.
*/
strategy = InvalidStrategy;
}
key->scanEntry[i] = ginFillScanEntry(so, attnum,
strategy, searchMode,
queryKey, queryCategory,
isPartialMatch, this_extra);
}
}
/*
* 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;
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);
}
/*
* 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);
}