mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-28 23:11:59 +02:00
27b77ecf9f
Backpatch-through: 10
359 lines
10 KiB
C
359 lines
10 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* gistscan.c
|
|
* routines to manage scans on GiST 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/gist/gistscan.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/gist_private.h"
|
|
#include "access/gistscan.h"
|
|
#include "access/relscan.h"
|
|
#include "utils/float.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
|
|
|
|
/*
|
|
* Pairing heap comparison function for the GISTSearchItem queue
|
|
*/
|
|
static int
|
|
pairingheap_GISTSearchItem_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
|
{
|
|
const GISTSearchItem *sa = (const GISTSearchItem *) a;
|
|
const GISTSearchItem *sb = (const GISTSearchItem *) b;
|
|
IndexScanDesc scan = (IndexScanDesc) arg;
|
|
int i;
|
|
|
|
/* Order according to distance comparison */
|
|
for (i = 0; i < scan->numberOfOrderBys; i++)
|
|
{
|
|
if (sa->distances[i].isnull)
|
|
{
|
|
if (!sb->distances[i].isnull)
|
|
return -1;
|
|
}
|
|
else if (sb->distances[i].isnull)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
int cmp = -float8_cmp_internal(sa->distances[i].value,
|
|
sb->distances[i].value);
|
|
|
|
if (cmp != 0)
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
/* Heap items go before inner pages, to ensure a depth-first search */
|
|
if (GISTSearchItemIsHeap(*sa) && !GISTSearchItemIsHeap(*sb))
|
|
return 1;
|
|
if (!GISTSearchItemIsHeap(*sa) && GISTSearchItemIsHeap(*sb))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Index AM API functions for scanning GiST indexes
|
|
*/
|
|
|
|
IndexScanDesc
|
|
gistbeginscan(Relation r, int nkeys, int norderbys)
|
|
{
|
|
IndexScanDesc scan;
|
|
GISTSTATE *giststate;
|
|
GISTScanOpaque so;
|
|
MemoryContext oldCxt;
|
|
|
|
scan = RelationGetIndexScan(r, nkeys, norderbys);
|
|
|
|
/* First, set up a GISTSTATE with a scan-lifespan memory context */
|
|
giststate = initGISTstate(scan->indexRelation);
|
|
|
|
/*
|
|
* Everything made below is in the scanCxt, or is a child of the scanCxt,
|
|
* so it'll all go away automatically in gistendscan.
|
|
*/
|
|
oldCxt = MemoryContextSwitchTo(giststate->scanCxt);
|
|
|
|
/* initialize opaque data */
|
|
so = (GISTScanOpaque) palloc0(sizeof(GISTScanOpaqueData));
|
|
so->giststate = giststate;
|
|
giststate->tempCxt = createTempGistContext();
|
|
so->queue = NULL;
|
|
so->queueCxt = giststate->scanCxt; /* see gistrescan */
|
|
|
|
/* workspaces with size dependent on numberOfOrderBys: */
|
|
so->distances = palloc(sizeof(so->distances[0]) * scan->numberOfOrderBys);
|
|
so->qual_ok = true; /* in case there are zero keys */
|
|
if (scan->numberOfOrderBys > 0)
|
|
{
|
|
scan->xs_orderbyvals = palloc0(sizeof(Datum) * scan->numberOfOrderBys);
|
|
scan->xs_orderbynulls = palloc(sizeof(bool) * scan->numberOfOrderBys);
|
|
memset(scan->xs_orderbynulls, true, sizeof(bool) * scan->numberOfOrderBys);
|
|
}
|
|
|
|
so->killedItems = NULL; /* until needed */
|
|
so->numKilled = 0;
|
|
so->curBlkno = InvalidBlockNumber;
|
|
so->curPageLSN = InvalidXLogRecPtr;
|
|
|
|
scan->opaque = so;
|
|
|
|
/*
|
|
* All fields required for index-only scans are initialized in gistrescan,
|
|
* as we don't know yet if we're doing an index-only scan or not.
|
|
*/
|
|
|
|
MemoryContextSwitchTo(oldCxt);
|
|
|
|
return scan;
|
|
}
|
|
|
|
void
|
|
gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
|
|
ScanKey orderbys, int norderbys)
|
|
{
|
|
/* nkeys and norderbys arguments are ignored */
|
|
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
|
bool first_time;
|
|
int i;
|
|
MemoryContext oldCxt;
|
|
|
|
/* rescan an existing indexscan --- reset state */
|
|
|
|
/*
|
|
* The first time through, we create the search queue in the scanCxt.
|
|
* Subsequent times through, we create the queue in a separate queueCxt,
|
|
* which is created on the second call and reset on later calls. Thus, in
|
|
* the common case where a scan is only rescan'd once, we just put the
|
|
* queue in scanCxt and don't pay the overhead of making a second memory
|
|
* context. If we do rescan more than once, the first queue is just left
|
|
* for dead until end of scan; this small wastage seems worth the savings
|
|
* in the common case.
|
|
*/
|
|
if (so->queue == NULL)
|
|
{
|
|
/* first time through */
|
|
Assert(so->queueCxt == so->giststate->scanCxt);
|
|
first_time = true;
|
|
}
|
|
else if (so->queueCxt == so->giststate->scanCxt)
|
|
{
|
|
/* second time through */
|
|
so->queueCxt = AllocSetContextCreate(so->giststate->scanCxt,
|
|
"GiST queue context",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
first_time = false;
|
|
}
|
|
else
|
|
{
|
|
/* third or later time through */
|
|
MemoryContextReset(so->queueCxt);
|
|
first_time = false;
|
|
}
|
|
|
|
/*
|
|
* If we're doing an index-only scan, on the first call, also initialize a
|
|
* tuple descriptor to represent the returned index tuples and create a
|
|
* memory context to hold them during the scan.
|
|
*/
|
|
if (scan->xs_want_itup && !scan->xs_hitupdesc)
|
|
{
|
|
int natts;
|
|
int nkeyatts;
|
|
int attno;
|
|
|
|
/*
|
|
* The storage type of the index can be different from the original
|
|
* datatype being indexed, so we cannot just grab the index's tuple
|
|
* descriptor. Instead, construct a descriptor with the original data
|
|
* types.
|
|
*/
|
|
natts = RelationGetNumberOfAttributes(scan->indexRelation);
|
|
nkeyatts = IndexRelationGetNumberOfKeyAttributes(scan->indexRelation);
|
|
so->giststate->fetchTupdesc = CreateTemplateTupleDesc(natts);
|
|
for (attno = 1; attno <= nkeyatts; attno++)
|
|
{
|
|
TupleDescInitEntry(so->giststate->fetchTupdesc, attno, NULL,
|
|
scan->indexRelation->rd_opcintype[attno - 1],
|
|
-1, 0);
|
|
}
|
|
|
|
for (; attno <= natts; attno++)
|
|
{
|
|
/* taking opcintype from giststate->tupdesc */
|
|
TupleDescInitEntry(so->giststate->fetchTupdesc, attno, NULL,
|
|
TupleDescAttr(so->giststate->leafTupdesc,
|
|
attno - 1)->atttypid,
|
|
-1, 0);
|
|
}
|
|
scan->xs_hitupdesc = so->giststate->fetchTupdesc;
|
|
|
|
/* Also create a memory context that will hold the returned tuples */
|
|
so->pageDataCxt = AllocSetContextCreate(so->giststate->scanCxt,
|
|
"GiST page data context",
|
|
ALLOCSET_DEFAULT_SIZES);
|
|
}
|
|
|
|
/* create new, empty pairing heap for search queue */
|
|
oldCxt = MemoryContextSwitchTo(so->queueCxt);
|
|
so->queue = pairingheap_allocate(pairingheap_GISTSearchItem_cmp, scan);
|
|
MemoryContextSwitchTo(oldCxt);
|
|
|
|
so->firstCall = true;
|
|
|
|
/* Update scan key, if a new one is given */
|
|
if (key && scan->numberOfKeys > 0)
|
|
{
|
|
void **fn_extras = NULL;
|
|
|
|
/*
|
|
* If this isn't the first time through, preserve the fn_extra
|
|
* pointers, so that if the consistentFns are using them to cache
|
|
* data, that data is not leaked across a rescan.
|
|
*/
|
|
if (!first_time)
|
|
{
|
|
fn_extras = (void **) palloc(scan->numberOfKeys * sizeof(void *));
|
|
for (i = 0; i < scan->numberOfKeys; i++)
|
|
fn_extras[i] = scan->keyData[i].sk_func.fn_extra;
|
|
}
|
|
|
|
memmove(scan->keyData, key,
|
|
scan->numberOfKeys * sizeof(ScanKeyData));
|
|
|
|
/*
|
|
* Modify the scan key so that the Consistent method is called for all
|
|
* comparisons. The original operator is passed to the Consistent
|
|
* function in the form of its strategy number, which is available
|
|
* from the sk_strategy field, and its subtype from the sk_subtype
|
|
* field.
|
|
*
|
|
* Next, if any of keys is a NULL and that key is not marked with
|
|
* SK_SEARCHNULL/SK_SEARCHNOTNULL then nothing can be found (ie, we
|
|
* assume all indexable operators are strict).
|
|
*/
|
|
so->qual_ok = true;
|
|
|
|
for (i = 0; i < scan->numberOfKeys; i++)
|
|
{
|
|
ScanKey skey = scan->keyData + i;
|
|
|
|
/*
|
|
* Copy consistent support function to ScanKey structure instead
|
|
* of function implementing filtering operator.
|
|
*/
|
|
fmgr_info_copy(&(skey->sk_func),
|
|
&(so->giststate->consistentFn[skey->sk_attno - 1]),
|
|
so->giststate->scanCxt);
|
|
|
|
/* Restore prior fn_extra pointers, if not first time */
|
|
if (!first_time)
|
|
skey->sk_func.fn_extra = fn_extras[i];
|
|
|
|
if (skey->sk_flags & SK_ISNULL)
|
|
{
|
|
if (!(skey->sk_flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL)))
|
|
so->qual_ok = false;
|
|
}
|
|
}
|
|
|
|
if (!first_time)
|
|
pfree(fn_extras);
|
|
}
|
|
|
|
/* Update order-by key, if a new one is given */
|
|
if (orderbys && scan->numberOfOrderBys > 0)
|
|
{
|
|
void **fn_extras = NULL;
|
|
|
|
/* As above, preserve fn_extra if not first time through */
|
|
if (!first_time)
|
|
{
|
|
fn_extras = (void **) palloc(scan->numberOfOrderBys * sizeof(void *));
|
|
for (i = 0; i < scan->numberOfOrderBys; i++)
|
|
fn_extras[i] = scan->orderByData[i].sk_func.fn_extra;
|
|
}
|
|
|
|
memmove(scan->orderByData, orderbys,
|
|
scan->numberOfOrderBys * sizeof(ScanKeyData));
|
|
|
|
so->orderByTypes = (Oid *) palloc(scan->numberOfOrderBys * sizeof(Oid));
|
|
|
|
/*
|
|
* Modify the order-by key so that the Distance method is called for
|
|
* all comparisons. The original operator is passed to the Distance
|
|
* function in the form of its strategy number, which is available
|
|
* from the sk_strategy field, and its subtype from the sk_subtype
|
|
* field.
|
|
*/
|
|
for (i = 0; i < scan->numberOfOrderBys; i++)
|
|
{
|
|
ScanKey skey = scan->orderByData + i;
|
|
FmgrInfo *finfo = &(so->giststate->distanceFn[skey->sk_attno - 1]);
|
|
|
|
/* Check we actually have a distance function ... */
|
|
if (!OidIsValid(finfo->fn_oid))
|
|
elog(ERROR, "missing support function %d for attribute %d of index \"%s\"",
|
|
GIST_DISTANCE_PROC, skey->sk_attno,
|
|
RelationGetRelationName(scan->indexRelation));
|
|
|
|
/*
|
|
* Look up the datatype returned by the original ordering
|
|
* operator. GiST always uses a float8 for the distance function,
|
|
* but the ordering operator could be anything else.
|
|
*
|
|
* XXX: The distance function is only allowed to be lossy if the
|
|
* ordering operator's result type is float4 or float8. Otherwise
|
|
* we don't know how to return the distance to the executor. But
|
|
* we cannot check that here, as we won't know if the distance
|
|
* function is lossy until it returns *recheck = true for the
|
|
* first time.
|
|
*/
|
|
so->orderByTypes[i] = get_func_rettype(skey->sk_func.fn_oid);
|
|
|
|
/*
|
|
* Copy distance support function to ScanKey structure instead of
|
|
* function implementing ordering operator.
|
|
*/
|
|
fmgr_info_copy(&(skey->sk_func), finfo, so->giststate->scanCxt);
|
|
|
|
/* Restore prior fn_extra pointers, if not first time */
|
|
if (!first_time)
|
|
skey->sk_func.fn_extra = fn_extras[i];
|
|
}
|
|
|
|
if (!first_time)
|
|
pfree(fn_extras);
|
|
}
|
|
|
|
/* any previous xs_hitup will have been pfree'd in context resets above */
|
|
scan->xs_hitup = NULL;
|
|
}
|
|
|
|
void
|
|
gistendscan(IndexScanDesc scan)
|
|
{
|
|
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
|
|
|
/*
|
|
* freeGISTstate is enough to clean up everything made by gistbeginscan,
|
|
* as well as the queueCxt if there is a separate context for it.
|
|
*/
|
|
freeGISTstate(so->giststate);
|
|
}
|