2011-12-17 22:41:16 +01:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* spgscan.c
|
|
|
|
* routines for scanning SP-GiST indexes
|
|
|
|
*
|
|
|
|
*
|
2016-01-02 19:33:40 +01:00
|
|
|
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
|
2011-12-17 22:41:16 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/backend/access/spgist/spgscan.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/relscan.h"
|
|
|
|
#include "access/spgist_private.h"
|
|
|
|
#include "miscadmin.h"
|
|
|
|
#include "storage/bufmgr.h"
|
|
|
|
#include "utils/datum.h"
|
|
|
|
#include "utils/memutils.h"
|
2012-08-31 23:04:31 +02:00
|
|
|
#include "utils/rel.h"
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
|
2012-03-11 21:29:04 +01:00
|
|
|
typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
|
2012-06-10 21:20:04 +02:00
|
|
|
Datum leafValue, bool isnull, bool recheck);
|
2012-03-11 21:29:04 +01:00
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
typedef struct ScanStackEntry
|
|
|
|
{
|
|
|
|
Datum reconstructedValue; /* value reconstructed from parent */
|
|
|
|
int level; /* level of items on this page */
|
|
|
|
ItemPointerData ptr; /* block and offset to scan from */
|
|
|
|
} ScanStackEntry;
|
|
|
|
|
|
|
|
|
|
|
|
/* Free a ScanStackEntry */
|
|
|
|
static void
|
|
|
|
freeScanStackEntry(SpGistScanOpaque so, ScanStackEntry *stackEntry)
|
|
|
|
{
|
|
|
|
if (!so->state.attType.attbyval &&
|
|
|
|
DatumGetPointer(stackEntry->reconstructedValue) != NULL)
|
|
|
|
pfree(DatumGetPointer(stackEntry->reconstructedValue));
|
|
|
|
pfree(stackEntry);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free the entire stack */
|
|
|
|
static void
|
|
|
|
freeScanStack(SpGistScanOpaque so)
|
|
|
|
{
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
foreach(lc, so->scanStack)
|
|
|
|
{
|
|
|
|
freeScanStackEntry(so, (ScanStackEntry *) lfirst(lc));
|
|
|
|
}
|
|
|
|
list_free(so->scanStack);
|
|
|
|
so->scanStack = NIL;
|
|
|
|
}
|
|
|
|
|
2011-12-19 20:58:41 +01:00
|
|
|
/*
|
2012-03-11 00:36:49 +01:00
|
|
|
* Initialize scanStack to search the root page, resetting
|
2011-12-19 20:58:41 +01:00
|
|
|
* any previously active scan
|
|
|
|
*/
|
2011-12-17 22:41:16 +01:00
|
|
|
static void
|
|
|
|
resetSpGistScanOpaque(SpGistScanOpaque so)
|
|
|
|
{
|
2012-03-11 00:36:49 +01:00
|
|
|
ScanStackEntry *startEntry;
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
freeScanStack(so);
|
2012-03-11 00:36:49 +01:00
|
|
|
|
2012-03-11 21:29:04 +01:00
|
|
|
if (so->searchNulls)
|
|
|
|
{
|
|
|
|
/* Stack a work item to scan the null index entries */
|
|
|
|
startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry));
|
|
|
|
ItemPointerSet(&startEntry->ptr, SPGIST_NULL_BLKNO, FirstOffsetNumber);
|
|
|
|
so->scanStack = lappend(so->scanStack, startEntry);
|
|
|
|
}
|
2012-03-11 00:36:49 +01:00
|
|
|
|
|
|
|
if (so->searchNonNulls)
|
|
|
|
{
|
|
|
|
/* Stack a work item to scan the non-null index entries */
|
|
|
|
startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry));
|
2012-03-11 21:29:04 +01:00
|
|
|
ItemPointerSet(&startEntry->ptr, SPGIST_ROOT_BLKNO, FirstOffsetNumber);
|
|
|
|
so->scanStack = lappend(so->scanStack, startEntry);
|
2012-03-11 00:36:49 +01:00
|
|
|
}
|
2011-12-19 20:58:41 +01:00
|
|
|
|
|
|
|
if (so->want_itup)
|
|
|
|
{
|
|
|
|
/* Must pfree IndexTuples to avoid memory leak */
|
2012-06-10 21:20:04 +02:00
|
|
|
int i;
|
2011-12-19 20:58:41 +01:00
|
|
|
|
|
|
|
for (i = 0; i < so->nPtrs; i++)
|
|
|
|
pfree(so->indexTups[i]);
|
|
|
|
}
|
|
|
|
so->iPtr = so->nPtrs = 0;
|
2011-12-17 22:41:16 +01:00
|
|
|
}
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/*
|
|
|
|
* Prepare scan keys in SpGistScanOpaque from caller-given scan keys
|
|
|
|
*
|
|
|
|
* Sets searchNulls, searchNonNulls, numberOfKeys, keyData fields of *so.
|
|
|
|
*
|
|
|
|
* The point here is to eliminate null-related considerations from what the
|
2014-05-06 18:12:18 +02:00
|
|
|
* opclass consistent functions need to deal with. We assume all SPGiST-
|
2012-03-11 00:36:49 +01:00
|
|
|
* indexable operators are strict, so any null RHS value makes the scan
|
|
|
|
* condition unsatisfiable. We also pull out any IS NULL/IS NOT NULL
|
|
|
|
* conditions; their effect is reflected into searchNulls/searchNonNulls.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
spgPrepareScanKeys(IndexScanDesc scan)
|
|
|
|
{
|
|
|
|
SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
|
|
|
|
bool qual_ok;
|
|
|
|
bool haveIsNull;
|
|
|
|
bool haveNotNull;
|
|
|
|
int nkeys;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (scan->numberOfKeys <= 0)
|
|
|
|
{
|
|
|
|
/* If no quals, whole-index scan is required */
|
|
|
|
so->searchNulls = true;
|
|
|
|
so->searchNonNulls = true;
|
|
|
|
so->numberOfKeys = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Examine the given quals */
|
|
|
|
qual_ok = true;
|
|
|
|
haveIsNull = haveNotNull = false;
|
|
|
|
nkeys = 0;
|
|
|
|
for (i = 0; i < scan->numberOfKeys; i++)
|
|
|
|
{
|
|
|
|
ScanKey skey = &scan->keyData[i];
|
|
|
|
|
|
|
|
if (skey->sk_flags & SK_SEARCHNULL)
|
|
|
|
haveIsNull = true;
|
|
|
|
else if (skey->sk_flags & SK_SEARCHNOTNULL)
|
|
|
|
haveNotNull = true;
|
|
|
|
else if (skey->sk_flags & SK_ISNULL)
|
|
|
|
{
|
|
|
|
/* ordinary qual with null argument - unsatisfiable */
|
|
|
|
qual_ok = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* ordinary qual, propagate into so->keyData */
|
|
|
|
so->keyData[nkeys++] = *skey;
|
|
|
|
/* this effectively creates a not-null requirement */
|
|
|
|
haveNotNull = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* IS NULL in combination with something else is unsatisfiable */
|
|
|
|
if (haveIsNull && haveNotNull)
|
|
|
|
qual_ok = false;
|
|
|
|
|
|
|
|
/* Emit results */
|
|
|
|
if (qual_ok)
|
|
|
|
{
|
|
|
|
so->searchNulls = haveIsNull;
|
|
|
|
so->searchNonNulls = haveNotNull;
|
|
|
|
so->numberOfKeys = nkeys;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
so->searchNulls = false;
|
|
|
|
so->searchNonNulls = false;
|
|
|
|
so->numberOfKeys = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
Datum
|
|
|
|
spgbeginscan(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Relation rel = (Relation) PG_GETARG_POINTER(0);
|
|
|
|
int keysz = PG_GETARG_INT32(1);
|
2012-06-10 21:20:04 +02:00
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
/* ScanKey scankey = (ScanKey) PG_GETARG_POINTER(2); */
|
|
|
|
IndexScanDesc scan;
|
|
|
|
SpGistScanOpaque so;
|
|
|
|
|
|
|
|
scan = RelationGetIndexScan(rel, keysz, 0);
|
|
|
|
|
|
|
|
so = (SpGistScanOpaque) palloc0(sizeof(SpGistScanOpaqueData));
|
2012-03-11 00:36:49 +01:00
|
|
|
if (keysz > 0)
|
|
|
|
so->keyData = (ScanKey) palloc(sizeof(ScanKeyData) * keysz);
|
|
|
|
else
|
|
|
|
so->keyData = NULL;
|
2011-12-17 22:41:16 +01:00
|
|
|
initSpGistState(&so->state, scan->indexRelation);
|
|
|
|
so->tempCxt = AllocSetContextCreate(CurrentMemoryContext,
|
|
|
|
"SP-GiST search temporary context",
|
|
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
2011-12-19 20:58:41 +01:00
|
|
|
|
|
|
|
/* Set up indexTupDesc and xs_itupdesc in case it's an index-only scan */
|
|
|
|
so->indexTupDesc = scan->xs_itupdesc = RelationGetDescr(rel);
|
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
scan->opaque = so;
|
|
|
|
|
|
|
|
PG_RETURN_POINTER(scan);
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
spgrescan(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
|
|
|
SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
|
|
|
|
ScanKey scankey = (ScanKey) PG_GETARG_POINTER(1);
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* copy scankeys into local storage */
|
2011-12-17 22:41:16 +01:00
|
|
|
if (scankey && scan->numberOfKeys > 0)
|
|
|
|
{
|
|
|
|
memmove(scan->keyData, scankey,
|
|
|
|
scan->numberOfKeys * sizeof(ScanKeyData));
|
|
|
|
}
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* preprocess scankeys, set up the representation in *so */
|
|
|
|
spgPrepareScanKeys(scan);
|
|
|
|
|
|
|
|
/* set up starting stack entries */
|
2011-12-17 22:41:16 +01:00
|
|
|
resetSpGistScanOpaque(so);
|
|
|
|
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
spgendscan(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
|
|
|
SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
|
|
|
|
|
|
|
|
MemoryContextDelete(so->tempCxt);
|
|
|
|
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
spgmarkpos(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
elog(ERROR, "SPGiST does not support mark/restore");
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
spgrestrpos(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
elog(ERROR, "SPGiST does not support mark/restore");
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-03-11 21:29:04 +01:00
|
|
|
* Test whether a leaf tuple satisfies all the scan keys
|
2011-12-17 22:41:16 +01:00
|
|
|
*
|
2011-12-19 20:58:41 +01:00
|
|
|
* *leafValue is set to the reconstructed datum, if provided
|
2011-12-17 22:41:16 +01:00
|
|
|
* *recheck is set true if any of the operators are lossy
|
|
|
|
*/
|
|
|
|
static bool
|
2012-03-11 21:29:04 +01:00
|
|
|
spgLeafTest(Relation index, SpGistScanOpaque so,
|
|
|
|
SpGistLeafTuple leafTuple, bool isnull,
|
2011-12-17 22:41:16 +01:00
|
|
|
int level, Datum reconstructedValue,
|
2011-12-19 20:58:41 +01:00
|
|
|
Datum *leafValue, bool *recheck)
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
2012-03-11 00:36:49 +01:00
|
|
|
bool result;
|
2012-03-11 21:29:04 +01:00
|
|
|
Datum leafDatum;
|
2011-12-17 22:41:16 +01:00
|
|
|
spgLeafConsistentIn in;
|
|
|
|
spgLeafConsistentOut out;
|
2011-12-19 20:58:41 +01:00
|
|
|
FmgrInfo *procinfo;
|
2011-12-17 22:41:16 +01:00
|
|
|
MemoryContext oldCtx;
|
|
|
|
|
2012-03-11 21:29:04 +01:00
|
|
|
if (isnull)
|
|
|
|
{
|
|
|
|
/* Should not have arrived on a nulls page unless nulls are wanted */
|
|
|
|
Assert(so->searchNulls);
|
|
|
|
*leafValue = (Datum) 0;
|
|
|
|
*recheck = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
leafDatum = SGLTDATUM(leafTuple, &so->state);
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* use temp context for calling leaf_consistent */
|
|
|
|
oldCtx = MemoryContextSwitchTo(so->tempCxt);
|
2011-12-17 22:41:16 +01:00
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
in.scankeys = so->keyData;
|
|
|
|
in.nkeys = so->numberOfKeys;
|
2011-12-17 22:41:16 +01:00
|
|
|
in.reconstructedValue = reconstructedValue;
|
|
|
|
in.level = level;
|
2011-12-19 20:58:41 +01:00
|
|
|
in.returnData = so->want_itup;
|
2011-12-17 22:41:16 +01:00
|
|
|
in.leafDatum = leafDatum;
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
out.leafValue = (Datum) 0;
|
|
|
|
out.recheck = false;
|
2011-12-19 20:58:41 +01:00
|
|
|
|
|
|
|
procinfo = index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC);
|
2012-03-11 00:36:49 +01:00
|
|
|
result = DatumGetBool(FunctionCall2Coll(procinfo,
|
|
|
|
index->rd_indcollation[0],
|
|
|
|
PointerGetDatum(&in),
|
|
|
|
PointerGetDatum(&out)));
|
2011-12-19 20:58:41 +01:00
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
*leafValue = out.leafValue;
|
|
|
|
*recheck = out.recheck;
|
2011-12-18 01:08:28 +01:00
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
MemoryContextSwitchTo(oldCtx);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Walk the tree and report all tuples passing the scan quals to the storeRes
|
|
|
|
* subroutine.
|
|
|
|
*
|
|
|
|
* If scanWholeIndex is true, we'll do just that. If not, we'll stop at the
|
|
|
|
* next page boundary once we have reported at least one tuple.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex,
|
2012-03-11 21:29:04 +01:00
|
|
|
storeRes_func storeRes)
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
|
|
|
Buffer buffer = InvalidBuffer;
|
|
|
|
bool reportedSome = false;
|
|
|
|
|
|
|
|
while (scanWholeIndex || !reportedSome)
|
|
|
|
{
|
|
|
|
ScanStackEntry *stackEntry;
|
|
|
|
BlockNumber blkno;
|
|
|
|
OffsetNumber offset;
|
|
|
|
Page page;
|
2012-03-11 21:29:04 +01:00
|
|
|
bool isnull;
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
/* Pull next to-do item from the list */
|
|
|
|
if (so->scanStack == NIL)
|
|
|
|
break; /* there are no more pages to scan */
|
|
|
|
|
|
|
|
stackEntry = (ScanStackEntry *) linitial(so->scanStack);
|
|
|
|
so->scanStack = list_delete_first(so->scanStack);
|
|
|
|
|
|
|
|
redirect:
|
|
|
|
/* Check for interrupts, just in case of infinite loop */
|
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
|
|
|
|
blkno = ItemPointerGetBlockNumber(&stackEntry->ptr);
|
|
|
|
offset = ItemPointerGetOffsetNumber(&stackEntry->ptr);
|
|
|
|
|
|
|
|
if (buffer == InvalidBuffer)
|
|
|
|
{
|
|
|
|
buffer = ReadBuffer(index, blkno);
|
|
|
|
LockBuffer(buffer, BUFFER_LOCK_SHARE);
|
|
|
|
}
|
|
|
|
else if (blkno != BufferGetBlockNumber(buffer))
|
|
|
|
{
|
|
|
|
UnlockReleaseBuffer(buffer);
|
|
|
|
buffer = ReadBuffer(index, blkno);
|
|
|
|
LockBuffer(buffer, BUFFER_LOCK_SHARE);
|
|
|
|
}
|
|
|
|
/* else new pointer points to the same page, no work needed */
|
|
|
|
|
|
|
|
page = BufferGetPage(buffer);
|
|
|
|
|
2012-03-11 21:29:04 +01:00
|
|
|
isnull = SpGistPageStoresNulls(page) ? true : false;
|
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
if (SpGistPageIsLeaf(page))
|
|
|
|
{
|
|
|
|
SpGistLeafTuple leafTuple;
|
|
|
|
OffsetNumber max = PageGetMaxOffsetNumber(page);
|
2011-12-19 20:58:41 +01:00
|
|
|
Datum leafValue = (Datum) 0;
|
2011-12-17 22:41:16 +01:00
|
|
|
bool recheck = false;
|
|
|
|
|
2012-03-11 21:29:04 +01:00
|
|
|
if (SpGistBlockIsRoot(blkno))
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
|
|
|
/* When root is a leaf, examine all its tuples */
|
|
|
|
for (offset = FirstOffsetNumber; offset <= max; offset++)
|
|
|
|
{
|
|
|
|
leafTuple = (SpGistLeafTuple)
|
|
|
|
PageGetItem(page, PageGetItemId(page, offset));
|
|
|
|
if (leafTuple->tupstate != SPGIST_LIVE)
|
|
|
|
{
|
|
|
|
/* all tuples on root should be live */
|
|
|
|
elog(ERROR, "unexpected SPGiST tuple state: %d",
|
|
|
|
leafTuple->tupstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
Assert(ItemPointerIsValid(&leafTuple->heapPtr));
|
2011-12-19 20:58:41 +01:00
|
|
|
if (spgLeafTest(index, so,
|
2012-03-11 21:29:04 +01:00
|
|
|
leafTuple, isnull,
|
2011-12-17 22:41:16 +01:00
|
|
|
stackEntry->level,
|
|
|
|
stackEntry->reconstructedValue,
|
2011-12-19 20:58:41 +01:00
|
|
|
&leafValue,
|
2011-12-17 22:41:16 +01:00
|
|
|
&recheck))
|
|
|
|
{
|
2012-03-11 21:29:04 +01:00
|
|
|
storeRes(so, &leafTuple->heapPtr,
|
|
|
|
leafValue, isnull, recheck);
|
2011-12-17 22:41:16 +01:00
|
|
|
reportedSome = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Normal case: just examine the chain we arrived at */
|
|
|
|
while (offset != InvalidOffsetNumber)
|
|
|
|
{
|
|
|
|
Assert(offset >= FirstOffsetNumber && offset <= max);
|
|
|
|
leafTuple = (SpGistLeafTuple)
|
|
|
|
PageGetItem(page, PageGetItemId(page, offset));
|
|
|
|
if (leafTuple->tupstate != SPGIST_LIVE)
|
|
|
|
{
|
|
|
|
if (leafTuple->tupstate == SPGIST_REDIRECT)
|
|
|
|
{
|
|
|
|
/* redirection tuple should be first in chain */
|
|
|
|
Assert(offset == ItemPointerGetOffsetNumber(&stackEntry->ptr));
|
|
|
|
/* transfer attention to redirect point */
|
|
|
|
stackEntry->ptr = ((SpGistDeadTuple) leafTuple)->pointer;
|
|
|
|
Assert(ItemPointerGetBlockNumber(&stackEntry->ptr) != SPGIST_METAPAGE_BLKNO);
|
|
|
|
goto redirect;
|
|
|
|
}
|
|
|
|
if (leafTuple->tupstate == SPGIST_DEAD)
|
|
|
|
{
|
|
|
|
/* dead tuple should be first in chain */
|
|
|
|
Assert(offset == ItemPointerGetOffsetNumber(&stackEntry->ptr));
|
|
|
|
/* No live entries on this page */
|
|
|
|
Assert(leafTuple->nextOffset == InvalidOffsetNumber);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* We should not arrive at a placeholder */
|
|
|
|
elog(ERROR, "unexpected SPGiST tuple state: %d",
|
|
|
|
leafTuple->tupstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
Assert(ItemPointerIsValid(&leafTuple->heapPtr));
|
2011-12-19 20:58:41 +01:00
|
|
|
if (spgLeafTest(index, so,
|
2012-03-11 21:29:04 +01:00
|
|
|
leafTuple, isnull,
|
2011-12-17 22:41:16 +01:00
|
|
|
stackEntry->level,
|
|
|
|
stackEntry->reconstructedValue,
|
2011-12-19 20:58:41 +01:00
|
|
|
&leafValue,
|
2011-12-17 22:41:16 +01:00
|
|
|
&recheck))
|
|
|
|
{
|
2012-03-11 21:29:04 +01:00
|
|
|
storeRes(so, &leafTuple->heapPtr,
|
|
|
|
leafValue, isnull, recheck);
|
2011-12-17 22:41:16 +01:00
|
|
|
reportedSome = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = leafTuple->nextOffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else /* page is inner */
|
|
|
|
{
|
|
|
|
SpGistInnerTuple innerTuple;
|
2012-03-11 00:36:49 +01:00
|
|
|
spgInnerConsistentIn in;
|
|
|
|
spgInnerConsistentOut out;
|
|
|
|
FmgrInfo *procinfo;
|
|
|
|
SpGistNodeTuple *nodes;
|
2011-12-17 22:41:16 +01:00
|
|
|
SpGistNodeTuple node;
|
|
|
|
int i;
|
2012-03-11 00:36:49 +01:00
|
|
|
MemoryContext oldCtx;
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
innerTuple = (SpGistInnerTuple) PageGetItem(page,
|
2012-06-10 21:20:04 +02:00
|
|
|
PageGetItemId(page, offset));
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
if (innerTuple->tupstate != SPGIST_LIVE)
|
|
|
|
{
|
|
|
|
if (innerTuple->tupstate == SPGIST_REDIRECT)
|
|
|
|
{
|
|
|
|
/* transfer attention to redirect point */
|
|
|
|
stackEntry->ptr = ((SpGistDeadTuple) innerTuple)->pointer;
|
|
|
|
Assert(ItemPointerGetBlockNumber(&stackEntry->ptr) != SPGIST_METAPAGE_BLKNO);
|
|
|
|
goto redirect;
|
|
|
|
}
|
|
|
|
elog(ERROR, "unexpected SPGiST tuple state: %d",
|
|
|
|
innerTuple->tupstate);
|
|
|
|
}
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* use temp context for calling inner_consistent */
|
|
|
|
oldCtx = MemoryContextSwitchTo(so->tempCxt);
|
|
|
|
|
|
|
|
in.scankeys = so->keyData;
|
|
|
|
in.nkeys = so->numberOfKeys;
|
|
|
|
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);
|
|
|
|
in.nNodes = innerTuple->nNodes;
|
|
|
|
in.nodeLabels = spgExtractNodeLabels(&so->state, innerTuple);
|
|
|
|
|
|
|
|
/* collect node pointers */
|
|
|
|
nodes = (SpGistNodeTuple *) palloc(sizeof(SpGistNodeTuple) * in.nNodes);
|
|
|
|
SGITITERATE(innerTuple, i, node)
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
2012-03-11 00:36:49 +01:00
|
|
|
nodes[i] = node;
|
2011-12-17 22:41:16 +01:00
|
|
|
}
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
memset(&out, 0, sizeof(out));
|
2011-12-17 22:41:16 +01:00
|
|
|
|
2012-03-11 21:29:04 +01:00
|
|
|
if (!isnull)
|
|
|
|
{
|
|
|
|
/* use user-defined inner consistent method */
|
|
|
|
procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC);
|
|
|
|
FunctionCall2Coll(procinfo,
|
|
|
|
index->rd_indcollation[0],
|
|
|
|
PointerGetDatum(&in),
|
|
|
|
PointerGetDatum(&out));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* force all children to be visited */
|
|
|
|
out.nNodes = in.nNodes;
|
|
|
|
out.nodeNumbers = (int *) palloc(sizeof(int) * in.nNodes);
|
|
|
|
for (i = 0; i < in.nNodes; i++)
|
|
|
|
out.nodeNumbers[i] = i;
|
|
|
|
}
|
2011-12-19 20:58:41 +01:00
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
MemoryContextSwitchTo(oldCtx);
|
2011-12-17 22:41:16 +01:00
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* If allTheSame, they should all or none of 'em match */
|
|
|
|
if (innerTuple->allTheSame)
|
|
|
|
if (out.nNodes != 0 && out.nNodes != in.nNodes)
|
|
|
|
elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple");
|
2011-12-17 22:41:16 +01:00
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
for (i = 0; i < out.nNodes; i++)
|
|
|
|
{
|
2012-06-10 21:20:04 +02:00
|
|
|
int nodeN = out.nodeNumbers[i];
|
2011-12-17 22:41:16 +01:00
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
Assert(nodeN >= 0 && nodeN < in.nNodes);
|
|
|
|
if (ItemPointerIsValid(&nodes[nodeN]->t_tid))
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
2012-03-11 00:36:49 +01:00
|
|
|
ScanStackEntry *newEntry;
|
|
|
|
|
|
|
|
/* Create new work item for this node */
|
|
|
|
newEntry = palloc(sizeof(ScanStackEntry));
|
|
|
|
newEntry->ptr = nodes[nodeN]->t_tid;
|
|
|
|
if (out.levelAdds)
|
|
|
|
newEntry->level = stackEntry->level + out.levelAdds[i];
|
|
|
|
else
|
|
|
|
newEntry->level = stackEntry->level;
|
|
|
|
/* Must copy value out of temp context */
|
|
|
|
if (out.reconstructedValues)
|
|
|
|
newEntry->reconstructedValue =
|
|
|
|
datumCopy(out.reconstructedValues[i],
|
|
|
|
so->state.attType.attbyval,
|
|
|
|
so->state.attType.attlen);
|
|
|
|
else
|
|
|
|
newEntry->reconstructedValue = (Datum) 0;
|
|
|
|
|
|
|
|
so->scanStack = lcons(newEntry, so->scanStack);
|
2011-12-17 22:41:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* done with this scan stack entry */
|
|
|
|
freeScanStackEntry(so, stackEntry);
|
|
|
|
/* clear temp context before proceeding to the next one */
|
|
|
|
MemoryContextReset(so->tempCxt);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buffer != InvalidBuffer)
|
|
|
|
UnlockReleaseBuffer(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* storeRes subroutine for getbitmap case */
|
|
|
|
static void
|
2011-12-19 20:58:41 +01:00
|
|
|
storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
|
2012-03-11 21:29:04 +01:00
|
|
|
Datum leafValue, bool isnull, bool recheck)
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
|
|
|
tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
|
|
|
|
so->ntids++;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
spggetbitmap(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
|
|
|
TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
|
|
|
|
SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* Copy want_itup to *so so we don't need to pass it around separately */
|
2011-12-19 20:58:41 +01:00
|
|
|
so->want_itup = false;
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
so->tbm = tbm;
|
|
|
|
so->ntids = 0;
|
|
|
|
|
|
|
|
spgWalk(scan->indexRelation, so, true, storeBitmap);
|
|
|
|
|
|
|
|
PG_RETURN_INT64(so->ntids);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* storeRes subroutine for gettuple case */
|
|
|
|
static void
|
2011-12-19 20:58:41 +01:00
|
|
|
storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
|
2012-03-11 21:29:04 +01:00
|
|
|
Datum leafValue, bool isnull, bool recheck)
|
2011-12-17 22:41:16 +01:00
|
|
|
{
|
|
|
|
Assert(so->nPtrs < MaxIndexTuplesPerPage);
|
|
|
|
so->heapPtrs[so->nPtrs] = *heapPtr;
|
|
|
|
so->recheck[so->nPtrs] = recheck;
|
2011-12-19 20:58:41 +01:00
|
|
|
if (so->want_itup)
|
|
|
|
{
|
|
|
|
/*
|
2014-05-06 18:12:18 +02:00
|
|
|
* Reconstruct desired IndexTuple. We have to copy the datum out of
|
2011-12-19 20:58:41 +01:00
|
|
|
* the temp context anyway, so we may as well create the tuple here.
|
|
|
|
*/
|
|
|
|
so->indexTups[so->nPtrs] = index_form_tuple(so->indexTupDesc,
|
|
|
|
&leafValue,
|
|
|
|
&isnull);
|
|
|
|
}
|
2011-12-17 22:41:16 +01:00
|
|
|
so->nPtrs++;
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
spggettuple(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
|
|
|
ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
|
|
|
|
SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
|
|
|
|
|
|
|
|
if (dir != ForwardScanDirection)
|
|
|
|
elog(ERROR, "SP-GiST only supports forward scan direction");
|
|
|
|
|
2012-03-11 00:36:49 +01:00
|
|
|
/* Copy want_itup to *so so we don't need to pass it around separately */
|
2011-12-19 20:58:41 +01:00
|
|
|
so->want_itup = scan->xs_want_itup;
|
2011-12-17 22:41:16 +01:00
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (so->iPtr < so->nPtrs)
|
|
|
|
{
|
|
|
|
/* continuing to return tuples from a leaf page */
|
|
|
|
scan->xs_ctup.t_self = so->heapPtrs[so->iPtr];
|
|
|
|
scan->xs_recheck = so->recheck[so->iPtr];
|
2011-12-19 20:58:41 +01:00
|
|
|
scan->xs_itup = so->indexTups[so->iPtr];
|
2011-12-17 22:41:16 +01:00
|
|
|
so->iPtr++;
|
|
|
|
PG_RETURN_BOOL(true);
|
|
|
|
}
|
|
|
|
|
2011-12-19 20:58:41 +01:00
|
|
|
if (so->want_itup)
|
|
|
|
{
|
|
|
|
/* Must pfree IndexTuples to avoid memory leak */
|
2012-06-10 21:20:04 +02:00
|
|
|
int i;
|
2011-12-19 20:58:41 +01:00
|
|
|
|
|
|
|
for (i = 0; i < so->nPtrs; i++)
|
|
|
|
pfree(so->indexTups[i]);
|
|
|
|
}
|
2011-12-17 22:41:16 +01:00
|
|
|
so->iPtr = so->nPtrs = 0;
|
2011-12-19 20:58:41 +01:00
|
|
|
|
2011-12-17 22:41:16 +01:00
|
|
|
spgWalk(scan->indexRelation, so, false, storeGettuple);
|
|
|
|
|
|
|
|
if (so->nPtrs == 0)
|
|
|
|
break; /* must have completed scan */
|
|
|
|
}
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(false);
|
|
|
|
}
|
2011-12-18 21:49:00 +01:00
|
|
|
|
|
|
|
Datum
|
|
|
|
spgcanreturn(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2011-12-19 20:58:41 +01:00
|
|
|
Relation index = (Relation) PG_GETARG_POINTER(0);
|
2015-05-24 03:35:49 +02:00
|
|
|
|
2015-03-26 18:12:00 +01:00
|
|
|
/* int i = PG_GETARG_INT32(1); */
|
2011-12-19 20:58:41 +01:00
|
|
|
SpGistCache *cache;
|
|
|
|
|
|
|
|
/* We can do it if the opclass config function says so */
|
|
|
|
cache = spgGetCache(index);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(cache->config.canReturnData);
|
2011-12-18 21:49:00 +01:00
|
|
|
}
|