1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

Teach SP-GiST to do index-only scans.

Operator classes can specify whether or not they support this; this
preserves the flexibility to use lossy representations within an index.

In passing, move constant data about a given index into the rd_amcache
cache area, instead of doing fresh lookups each time we start an index
operation.  This is mainly to try to make sure that spgcanreturn() has
insignificant cost; I still don't have any proof that it matters for
actual index accesses.  Also, get rid of useless copying of FmgrInfo
pointers; we can perfectly well use the relcache's versions in-place.
This commit is contained in:
Tom Lane
2011-12-19 14:58:41 -05:00
parent 3695a55513
commit 9220362493
10 changed files with 286 additions and 172 deletions

View File

@ -15,6 +15,7 @@
#include "postgres.h"
#include "access/genam.h"
#include "access/spgist_private.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
@ -678,6 +679,7 @@ doPickSplit(Relation index, SpGistState *state,
bool insertedNew = false;
spgPickSplitIn in;
spgPickSplitOut out;
FmgrInfo *procinfo;
bool includeNew;
int i,
max,
@ -816,7 +818,8 @@ doPickSplit(Relation index, SpGistState *state,
*/
memset(&out, 0, sizeof(out));
FunctionCall2Coll(&state->picksplitFn,
procinfo = index_getprocinfo(index, 1, SPGIST_PICKSPLIT_PROC);
FunctionCall2Coll(procinfo,
index->rd_indcollation[0],
PointerGetDatum(&in),
PointerGetDatum(&out));
@ -1944,6 +1947,7 @@ spgdoinsert(Relation index, SpGistState *state,
SpGistInnerTuple innerTuple;
spgChooseIn in;
spgChooseOut out;
FmgrInfo *procinfo;
/*
* spgAddNode and spgSplitTuple cases will loop back to here to
@ -1968,7 +1972,8 @@ spgdoinsert(Relation index, SpGistState *state,
memset(&out, 0, sizeof(out));
FunctionCall2Coll(&state->chooseFn,
procinfo = index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC);
FunctionCall2Coll(procinfo,
index->rd_indcollation[0],
PointerGetDatum(&in),
PointerGetDatum(&out));

View File

@ -30,6 +30,7 @@ spg_kd_config(PG_FUNCTION_ARGS)
cfg->prefixType = FLOAT8OID;
cfg->labelType = VOIDOID; /* we don't need node labels */
cfg->canReturnData = true;
cfg->longValuesOK = false;
PG_RETURN_VOID();
}

View File

@ -30,6 +30,7 @@ spg_quad_config(PG_FUNCTION_ARGS)
cfg->prefixType = POINTOID;
cfg->labelType = VOIDOID; /* we don't need node labels */
cfg->canReturnData = true;
cfg->longValuesOK = false;
PG_RETURN_VOID();
}
@ -324,6 +325,9 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
/* all tests are exact */
out->recheck = false;
/* leafDatum is what it is... */
out->leafValue = in->leafDatum;
switch (in->strategy)
{
case RTLeftStrategyNumber:

View File

@ -55,7 +55,10 @@ freeScanStack(SpGistScanOpaque so)
so->scanStack = NIL;
}
/* Initialize scanStack with a single entry for the root page */
/*
* Initialize scanStack with a single entry for the root page, resetting
* any previously active scan
*/
static void
resetSpGistScanOpaque(SpGistScanOpaque so)
{
@ -65,7 +68,16 @@ resetSpGistScanOpaque(SpGistScanOpaque so)
freeScanStack(so);
so->scanStack = list_make1(startEntry);
so->nPtrs = so->iPtr = 0;
if (so->want_itup)
{
/* Must pfree IndexTuples to avoid memory leak */
int i;
for (i = 0; i < so->nPtrs; i++)
pfree(so->indexTups[i]);
}
so->iPtr = so->nPtrs = 0;
}
Datum
@ -87,6 +99,10 @@ spgbeginscan(PG_FUNCTION_ARGS)
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
resetSpGistScanOpaque(so);
/* Set up indexTupDesc and xs_itupdesc in case it's an index-only scan */
so->indexTupDesc = scan->xs_itupdesc = RelationGetDescr(rel);
scan->opaque = so;
PG_RETURN_POINTER(scan);
@ -138,28 +154,35 @@ spgrestrpos(PG_FUNCTION_ARGS)
/*
* Test whether a leaf datum satisfies all the scan keys
*
* *leafValue is set to the reconstructed datum, if provided
* *recheck is set true if any of the operators are lossy
*/
static bool
spgLeafTest(SpGistScanOpaque so, Datum leafDatum,
spgLeafTest(Relation index, SpGistScanOpaque so, Datum leafDatum,
int level, Datum reconstructedValue,
bool *recheck)
Datum *leafValue, bool *recheck)
{
bool result = true;
spgLeafConsistentIn in;
spgLeafConsistentOut out;
FmgrInfo *procinfo;
MemoryContext oldCtx;
int i;
*leafValue = (Datum) 0;
*recheck = false;
/* set up values that are the same for all quals */
in.reconstructedValue = reconstructedValue;
in.level = level;
in.returnData = so->want_itup;
in.leafDatum = leafDatum;
/* Apply each leaf consistent function, working in the temp context */
/* Apply each leaf consistency check, working in the temp context */
oldCtx = MemoryContextSwitchTo(so->tempCxt);
procinfo = index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC);
for (i = 0; i < so->numberOfKeys; i++)
{
ScanKey skey = &so->keyData[i];
@ -174,12 +197,14 @@ spgLeafTest(SpGistScanOpaque so, Datum leafDatum,
in.strategy = skey->sk_strategy;
in.query = skey->sk_argument;
out.leafValue = (Datum) 0;
out.recheck = false;
result = DatumGetBool(FunctionCall2Coll(&so->state.leafConsistentFn,
result = DatumGetBool(FunctionCall2Coll(procinfo,
skey->sk_collation,
PointerGetDatum(&in),
PointerGetDatum(&out)));
*leafValue = out.leafValue;
*recheck |= out.recheck;
if (!result)
break;
@ -198,7 +223,7 @@ spgLeafTest(SpGistScanOpaque so, Datum leafDatum,
*/
static void
spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex,
void (*storeRes) (SpGistScanOpaque, ItemPointer, bool))
void (*storeRes) (SpGistScanOpaque, ItemPointer, Datum, bool))
{
Buffer buffer = InvalidBuffer;
bool reportedSome = false;
@ -243,6 +268,7 @@ redirect:
{
SpGistLeafTuple leafTuple;
OffsetNumber max = PageGetMaxOffsetNumber(page);
Datum leafValue = (Datum) 0;
bool recheck = false;
if (blkno == SPGIST_HEAD_BLKNO)
@ -260,13 +286,14 @@ redirect:
}
Assert(ItemPointerIsValid(&leafTuple->heapPtr));
if (spgLeafTest(so,
if (spgLeafTest(index, so,
SGLTDATUM(leafTuple, &so->state),
stackEntry->level,
stackEntry->reconstructedValue,
&leafValue,
&recheck))
{
storeRes(so, &leafTuple->heapPtr, recheck);
storeRes(so, &leafTuple->heapPtr, leafValue, recheck);
reportedSome = true;
}
}
@ -304,13 +331,14 @@ redirect:
}
Assert(ItemPointerIsValid(&leafTuple->heapPtr));
if (spgLeafTest(so,
if (spgLeafTest(index, so,
SGLTDATUM(leafTuple, &so->state),
stackEntry->level,
stackEntry->reconstructedValue,
&leafValue,
&recheck))
{
storeRes(so, &leafTuple->heapPtr, recheck);
storeRes(so, &leafTuple->heapPtr, leafValue, recheck);
reportedSome = true;
}
@ -374,6 +402,7 @@ redirect:
{
spgInnerConsistentIn in;
spgInnerConsistentOut out;
FmgrInfo *procinfo;
SpGistNodeTuple *nodes;
int *andMap;
int *levelAdds;
@ -388,6 +417,7 @@ redirect:
/* set up values that are the same for all scankeys */
in.reconstructedValue = stackEntry->reconstructedValue;
in.level = stackEntry->level;
in.returnData = so->want_itup;
in.allTheSame = innerTuple->allTheSame;
in.hasPrefix = (innerTuple->prefixSize > 0);
in.prefixDatum = SGITDATUM(innerTuple, &so->state);
@ -405,6 +435,8 @@ redirect:
levelAdds = (int *) palloc0(sizeof(int) * in.nNodes);
reconstructedValues = (Datum *) palloc0(sizeof(Datum) * in.nNodes);
procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC);
for (j = 0; j < so->numberOfKeys; j++)
{
ScanKey skey = &so->keyData[j];
@ -421,7 +453,7 @@ redirect:
memset(&out, 0, sizeof(out));
FunctionCall2Coll(&so->state.innerConsistentFn,
FunctionCall2Coll(procinfo,
skey->sk_collation,
PointerGetDatum(&in),
PointerGetDatum(&out));
@ -490,7 +522,8 @@ redirect:
/* storeRes subroutine for getbitmap case */
static void
storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr, bool recheck)
storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
Datum leafValue, bool recheck)
{
tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
so->ntids++;
@ -506,6 +539,8 @@ spggetbitmap(PG_FUNCTION_ARGS)
/* Copy scankey to *so so we don't need to pass it around separately */
so->numberOfKeys = scan->numberOfKeys;
so->keyData = scan->keyData;
/* Ditto for the want_itup flag */
so->want_itup = false;
so->tbm = tbm;
so->ntids = 0;
@ -517,11 +552,24 @@ spggetbitmap(PG_FUNCTION_ARGS)
/* storeRes subroutine for gettuple case */
static void
storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr, bool recheck)
storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
Datum leafValue, bool recheck)
{
Assert(so->nPtrs < MaxIndexTuplesPerPage);
so->heapPtrs[so->nPtrs] = *heapPtr;
so->recheck[so->nPtrs] = recheck;
if (so->want_itup)
{
/*
* Reconstruct desired IndexTuple. We have to copy the datum out of
* the temp context anyway, so we may as well create the tuple here.
*/
bool isnull = false;
so->indexTups[so->nPtrs] = index_form_tuple(so->indexTupDesc,
&leafValue,
&isnull);
}
so->nPtrs++;
}
@ -538,6 +586,8 @@ spggettuple(PG_FUNCTION_ARGS)
/* Copy scankey to *so so we don't need to pass it around separately */
so->numberOfKeys = scan->numberOfKeys;
so->keyData = scan->keyData;
/* Ditto for the want_itup flag */
so->want_itup = scan->xs_want_itup;
for (;;)
{
@ -546,11 +596,21 @@ spggettuple(PG_FUNCTION_ARGS)
/* continuing to return tuples from a leaf page */
scan->xs_ctup.t_self = so->heapPtrs[so->iPtr];
scan->xs_recheck = so->recheck[so->iPtr];
scan->xs_itup = so->indexTups[so->iPtr];
so->iPtr++;
PG_RETURN_BOOL(true);
}
if (so->want_itup)
{
/* Must pfree IndexTuples to avoid memory leak */
int i;
for (i = 0; i < so->nPtrs; i++)
pfree(so->indexTups[i]);
}
so->iPtr = so->nPtrs = 0;
spgWalk(scan->indexRelation, so, false, storeGettuple);
if (so->nPtrs == 0)
@ -563,6 +623,11 @@ spggettuple(PG_FUNCTION_ARGS)
Datum
spgcanreturn(PG_FUNCTION_ARGS)
{
/* Not implemented yet */
PG_RETURN_BOOL(false);
Relation index = (Relation) PG_GETARG_POINTER(0);
SpGistCache *cache;
/* We can do it if the opclass config function says so */
cache = spgGetCache(index);
PG_RETURN_BOOL(cache->config.canReturnData);
}

View File

@ -51,6 +51,7 @@ spg_text_config(PG_FUNCTION_ARGS)
cfg->prefixType = TEXTOID;
cfg->labelType = CHAROID;
cfg->canReturnData = true;
cfg->longValuesOK = true; /* suffixing will shorten long values */
PG_RETURN_VOID();
}
@ -521,7 +522,10 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS)
queryLen = VARSIZE_ANY_EXHDR(query);
/* For equality, we needn't reconstruct fullValue if not same length */
/*
* For an equality check, we needn't reconstruct fullValue if not same
* length; it can't match
*/
if (strategy == BTEqualStrategyNumber && queryLen != fullLen)
PG_RETURN_BOOL(false);
@ -529,15 +533,20 @@ spg_text_leaf_consistent(PG_FUNCTION_ARGS)
if (VARSIZE_ANY_EXHDR(leafValue) == 0 && level > 0)
{
fullValue = VARDATA(reconstrValue);
out->leafValue = PointerGetDatum(reconstrValue);
}
else
{
fullValue = palloc(fullLen);
text *fullText = palloc(VARHDRSZ + fullLen);
SET_VARSIZE(fullText, VARHDRSZ + fullLen);
fullValue = VARDATA(fullText);
if (level)
memcpy(fullValue, VARDATA(reconstrValue), level);
if (VARSIZE_ANY_EXHDR(leafValue) > 0)
memcpy(fullValue + level, VARDATA_ANY(leafValue),
VARSIZE_ANY_EXHDR(leafValue));
out->leafValue = PointerGetDatum(fullText);
}
/* Run the appropriate type of comparison */

View File

@ -34,51 +34,88 @@ fillTypeDesc(SpGistTypeDesc *desc, Oid type)
get_typlenbyval(type, &desc->attlen, &desc->attbyval);
}
/*
* Fetch local cache of AM-specific info about the index, initializing it
* if necessary
*/
SpGistCache *
spgGetCache(Relation index)
{
SpGistCache *cache;
if (index->rd_amcache == NULL)
{
Oid atttype;
spgConfigIn in;
FmgrInfo *procinfo;
Buffer metabuffer;
SpGistMetaPageData *metadata;
cache = MemoryContextAllocZero(index->rd_indexcxt,
sizeof(SpGistCache));
/* SPGiST doesn't support multi-column indexes */
Assert(index->rd_att->natts == 1);
/*
* Get the actual data type of the indexed column from the index
* tupdesc. We pass this to the opclass config function so that
* polymorphic opclasses are possible.
*/
atttype = index->rd_att->attrs[0]->atttypid;
/* Call the config function to get config info for the opclass */
in.attType = atttype;
procinfo = index_getprocinfo(index, 1, SPGIST_CONFIG_PROC);
FunctionCall2Coll(procinfo,
index->rd_indcollation[0],
PointerGetDatum(&in),
PointerGetDatum(&cache->config));
/* Get the information we need about each relevant datatype */
fillTypeDesc(&cache->attType, atttype);
fillTypeDesc(&cache->attPrefixType, cache->config.prefixType);
fillTypeDesc(&cache->attLabelType, cache->config.labelType);
/* Last, get the lastUsedPages data from the metapage */
metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
elog(ERROR, "index \"%s\" is not an SP-GiST index",
RelationGetRelationName(index));
cache->lastUsedPages = metadata->lastUsedPages;
UnlockReleaseBuffer(metabuffer);
index->rd_amcache = (void *) cache;
}
else
{
/* assume it's up to date */
cache = (SpGistCache *) index->rd_amcache;
}
return cache;
}
/* Initialize SpGistState for working with the given index */
void
initSpGistState(SpGistState *state, Relation index)
{
Oid atttype;
spgConfigIn in;
SpGistCache *cache;
/* SPGiST doesn't support multi-column indexes */
Assert(index->rd_att->natts == 1);
/* Get cached static information about index */
cache = spgGetCache(index);
/*
* Get the actual data type of the indexed column from the index tupdesc.
* We pass this to the opclass config function so that polymorphic
* opclasses are possible.
*/
atttype = index->rd_att->attrs[0]->atttypid;
/* Get the config info for the opclass */
in.attType = atttype;
memset(&state->config, 0, sizeof(state->config));
FunctionCall2Coll(index_getprocinfo(index, 1, SPGIST_CONFIG_PROC),
index->rd_indcollation[0],
PointerGetDatum(&in),
PointerGetDatum(&state->config));
/* Get the information we need about each relevant datatype */
fillTypeDesc(&state->attType, atttype);
fillTypeDesc(&state->attPrefixType, state->config.prefixType);
fillTypeDesc(&state->attLabelType, state->config.labelType);
/* Get lookup info for opclass support procs */
fmgr_info_copy(&(state->chooseFn),
index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC),
CurrentMemoryContext);
fmgr_info_copy(&(state->picksplitFn),
index_getprocinfo(index, 1, SPGIST_PICKSPLIT_PROC),
CurrentMemoryContext);
fmgr_info_copy(&(state->innerConsistentFn),
index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC),
CurrentMemoryContext);
fmgr_info_copy(&(state->leafConsistentFn),
index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC),
CurrentMemoryContext);
state->config = cache->config;
state->attType = cache->attType;
state->attPrefixType = cache->attPrefixType;
state->attLabelType = cache->attLabelType;
/* Make workspace for constructing dead tuples */
state->deadTupleStorage = palloc0(SGDTSIZE);
@ -86,6 +123,7 @@ initSpGistState(SpGistState *state, Relation index)
/* Set XID to use in redirection tuples */
state->myXid = GetTopTransactionIdIfAny();
/* Assume we're not in an index build (spgbuild will override) */
state->isBuild = false;
}
@ -153,46 +191,6 @@ SpGistNewBuffer(Relation index)
return buffer;
}
/*
* Fetch local cache of lastUsedPages info, initializing it from the metapage
* if necessary
*/
static SpGistCache *
spgGetCache(Relation index)
{
SpGistCache *cache;
if (index->rd_amcache == NULL)
{
Buffer metabuffer;
SpGistMetaPageData *metadata;
cache = MemoryContextAlloc(index->rd_indexcxt,
sizeof(SpGistCache));
metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
elog(ERROR, "index \"%s\" is not an SP-GiST index",
RelationGetRelationName(index));
*cache = metadata->lastUsedPages;
UnlockReleaseBuffer(metabuffer);
index->rd_amcache = cache;
}
else
{
cache = (SpGistCache *) index->rd_amcache;
}
return cache;
}
/*
* Update index metapage's lastUsedPages info from local cache, if possible
*
@ -215,7 +213,7 @@ SpGistUpdateMetaPage(Relation index)
if (ConditionalLockBuffer(metabuffer))
{
metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
metadata->lastUsedPages = *cache;
metadata->lastUsedPages = cache->lastUsedPages;
MarkBufferDirty(metabuffer);
UnlockReleaseBuffer(metabuffer);
@ -229,8 +227,8 @@ SpGistUpdateMetaPage(Relation index)
/* Macro to select proper element of lastUsedPages cache depending on flags */
#define GET_LUP(c, f) (((f) & GBUF_LEAF) ? \
&(c)->leafPage : \
&(c)->innerPage[(f) & GBUF_PARITY_MASK])
&(c)->lastUsedPages.leafPage : \
&(c)->lastUsedPages.innerPage[(f) & GBUF_PARITY_MASK])
/*
* Allocate and initialize a new buffer of the type and parity specified by
@ -282,8 +280,8 @@ allocNewBuffer(Relation index, int flags)
else
{
/* Page has wrong parity, record it in cache and try again */
cache->innerPage[blkParity].blkno = blkno;
cache->innerPage[blkParity].freeSpace =
cache->lastUsedPages.innerPage[blkParity].blkno = blkno;
cache->lastUsedPages.innerPage[blkParity].freeSpace =
PageGetExactFreeSpace(BufferGetPage(buffer));
UnlockReleaseBuffer(buffer);
}