mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-11 19:20:40 +08:00
KNNGIST, otherwise known as order-by-operator support for GIST.
This commit represents a rather heavily editorialized version of Teodor's builtin_knngist_itself-0.8.2 and builtin_knngist_proc-0.8.1 patches. I redid the opclass API to add a separate Distance method instead of turning the Consistent method into an illogical mess, fixed some bit-rot in the rbtree interfaces, and generally worked over the code style and comments. There's still no non-code documentation to speak of, but I'll work on that separately. Some contrib-module changes are also yet to come (right now, point <-> point is the only KNN-ified operator). Teodor Sigaev and Tom Lane
This commit is contained in:
parent
c0a4d3e051
commit
554506871b
@ -1030,6 +1030,9 @@ gistnewroot(Relation r, Buffer buffer, IndexTuple *itup, int len, ItemPointer ke
|
||||
END_CRIT_SECTION();
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill a GISTSTATE with information about the index
|
||||
*/
|
||||
void
|
||||
initGISTstate(GISTSTATE *giststate, Relation index)
|
||||
{
|
||||
@ -1064,6 +1067,13 @@ initGISTstate(GISTSTATE *giststate, Relation index)
|
||||
fmgr_info_copy(&(giststate->equalFn[i]),
|
||||
index_getprocinfo(index, i + 1, GIST_EQUAL_PROC),
|
||||
CurrentMemoryContext);
|
||||
/* opclasses are not required to provide a Distance method */
|
||||
if (OidIsValid(index_getprocid(index, i + 1, GIST_DISTANCE_PROC)))
|
||||
fmgr_info_copy(&(giststate->distanceFn[i]),
|
||||
index_getprocinfo(index, i + 1, GIST_DISTANCE_PROC),
|
||||
CurrentMemoryContext);
|
||||
else
|
||||
giststate->distanceFn[i].fn_oid = InvalidOid;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,389 +20,67 @@
|
||||
#include "miscadmin.h"
|
||||
#include "pgstat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/memutils.h"
|
||||
|
||||
|
||||
static OffsetNumber gistfindnext(IndexScanDesc scan, OffsetNumber n);
|
||||
static int64 gistnext(IndexScanDesc scan, TIDBitmap *tbm);
|
||||
static bool gistindex_keytest(IndexTuple tuple, IndexScanDesc scan,
|
||||
OffsetNumber offset);
|
||||
|
||||
static void
|
||||
killtuple(Relation r, GISTScanOpaque so, ItemPointer iptr)
|
||||
{
|
||||
Page p;
|
||||
OffsetNumber offset;
|
||||
|
||||
LockBuffer(so->curbuf, GIST_SHARE);
|
||||
gistcheckpage(r, so->curbuf);
|
||||
p = (Page) BufferGetPage(so->curbuf);
|
||||
|
||||
if (XLByteEQ(so->stack->lsn, PageGetLSN(p)))
|
||||
{
|
||||
/* page unchanged, so all is simple */
|
||||
offset = ItemPointerGetOffsetNumber(iptr);
|
||||
ItemIdMarkDead(PageGetItemId(p, offset));
|
||||
SetBufferCommitInfoNeedsSave(so->curbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
OffsetNumber maxoff = PageGetMaxOffsetNumber(p);
|
||||
|
||||
for (offset = FirstOffsetNumber; offset <= maxoff; offset = OffsetNumberNext(offset))
|
||||
{
|
||||
IndexTuple ituple = (IndexTuple) PageGetItem(p, PageGetItemId(p, offset));
|
||||
|
||||
if (ItemPointerEquals(&(ituple->t_tid), iptr))
|
||||
{
|
||||
/* found */
|
||||
ItemIdMarkDead(PageGetItemId(p, offset));
|
||||
SetBufferCommitInfoNeedsSave(so->curbuf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockBuffer(so->curbuf, GIST_UNLOCK);
|
||||
}
|
||||
|
||||
/*
|
||||
* gistgettuple() -- Get the next tuple in the scan
|
||||
*/
|
||||
Datum
|
||||
gistgettuple(PG_FUNCTION_ARGS)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||
ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
|
||||
GISTScanOpaque so;
|
||||
bool res;
|
||||
|
||||
so = (GISTScanOpaque) scan->opaque;
|
||||
|
||||
if (dir != ForwardScanDirection)
|
||||
elog(ERROR, "GiST doesn't support other scan directions than forward");
|
||||
|
||||
/*
|
||||
* If we have produced an index tuple in the past and the executor has
|
||||
* informed us we need to mark it as "killed", do so now.
|
||||
*/
|
||||
if (scan->kill_prior_tuple && ItemPointerIsValid(&(so->curpos)))
|
||||
killtuple(scan->indexRelation, so, &(so->curpos));
|
||||
|
||||
/*
|
||||
* Get the next tuple that matches the search key.
|
||||
*/
|
||||
res = (gistnext(scan, NULL) > 0);
|
||||
|
||||
PG_RETURN_BOOL(res);
|
||||
}
|
||||
|
||||
Datum
|
||||
gistgetbitmap(PG_FUNCTION_ARGS)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||
TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
|
||||
int64 ntids;
|
||||
|
||||
ntids = gistnext(scan, tbm);
|
||||
|
||||
PG_RETURN_INT64(ntids);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch tuple(s) that match the search key; this can be invoked
|
||||
* either to fetch the first such tuple or subsequent matching tuples.
|
||||
*
|
||||
* This function is used by both gistgettuple and gistgetbitmap. When
|
||||
* invoked from gistgettuple, tbm is null and the next matching tuple
|
||||
* is returned in scan->xs_ctup.t_self. When invoked from getbitmap,
|
||||
* tbm is non-null and all matching tuples are added to tbm before
|
||||
* returning. In both cases, the function result is the number of
|
||||
* returned tuples.
|
||||
*
|
||||
* If scan specifies to skip killed tuples, continue looping until we find a
|
||||
* non-killed tuple that matches the search key.
|
||||
*/
|
||||
static int64
|
||||
gistnext(IndexScanDesc scan, TIDBitmap *tbm)
|
||||
{
|
||||
Page p;
|
||||
OffsetNumber n;
|
||||
GISTScanOpaque so;
|
||||
GISTSearchStack *stk;
|
||||
IndexTuple it;
|
||||
GISTPageOpaque opaque;
|
||||
int64 ntids = 0;
|
||||
|
||||
so = (GISTScanOpaque) scan->opaque;
|
||||
|
||||
if (so->qual_ok == false)
|
||||
return 0;
|
||||
|
||||
if (so->curbuf == InvalidBuffer)
|
||||
{
|
||||
if (ItemPointerIsValid(&so->curpos) == false)
|
||||
{
|
||||
/* Being asked to fetch the first entry, so start at the root */
|
||||
Assert(so->curbuf == InvalidBuffer);
|
||||
Assert(so->stack == NULL);
|
||||
|
||||
so->curbuf = ReadBuffer(scan->indexRelation, GIST_ROOT_BLKNO);
|
||||
|
||||
stk = so->stack = (GISTSearchStack *) palloc0(sizeof(GISTSearchStack));
|
||||
|
||||
stk->next = NULL;
|
||||
stk->block = GIST_ROOT_BLKNO;
|
||||
|
||||
pgstat_count_index_scan(scan->indexRelation);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* scan is finished */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check stored pointers from last visit
|
||||
*/
|
||||
if (so->nPageData > 0)
|
||||
{
|
||||
/*
|
||||
* gistgetmulti never should go here
|
||||
*/
|
||||
Assert(tbm == NULL);
|
||||
|
||||
if (so->curPageData < so->nPageData)
|
||||
{
|
||||
scan->xs_ctup.t_self = so->pageData[so->curPageData].heapPtr;
|
||||
scan->xs_recheck = so->pageData[so->curPageData].recheck;
|
||||
|
||||
ItemPointerSet(&so->curpos,
|
||||
BufferGetBlockNumber(so->curbuf),
|
||||
so->pageData[so->curPageData].pageOffset);
|
||||
|
||||
so->curPageData++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Go to the next page
|
||||
*/
|
||||
stk = so->stack->next;
|
||||
pfree(so->stack);
|
||||
so->stack = stk;
|
||||
|
||||
/* If we're out of stack entries, we're done */
|
||||
if (so->stack == NULL)
|
||||
{
|
||||
ReleaseBuffer(so->curbuf);
|
||||
so->curbuf = InvalidBuffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
so->curbuf = ReleaseAndReadBuffer(so->curbuf,
|
||||
scan->indexRelation,
|
||||
stk->block);
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
/* First of all, we need lock buffer */
|
||||
Assert(so->curbuf != InvalidBuffer);
|
||||
LockBuffer(so->curbuf, GIST_SHARE);
|
||||
gistcheckpage(scan->indexRelation, so->curbuf);
|
||||
p = BufferGetPage(so->curbuf);
|
||||
opaque = GistPageGetOpaque(p);
|
||||
|
||||
/* remember lsn to identify page changed for tuple's killing */
|
||||
so->stack->lsn = PageGetLSN(p);
|
||||
|
||||
/* check page split, occured since visit to parent */
|
||||
if (!XLogRecPtrIsInvalid(so->stack->parentlsn) &&
|
||||
XLByteLT(so->stack->parentlsn, opaque->nsn) &&
|
||||
opaque->rightlink != InvalidBlockNumber /* sanity check */ &&
|
||||
(so->stack->next == NULL || so->stack->next->block != opaque->rightlink) /* check if already
|
||||
added */ )
|
||||
{
|
||||
/* detect page split, follow right link to add pages */
|
||||
|
||||
stk = (GISTSearchStack *) palloc(sizeof(GISTSearchStack));
|
||||
stk->next = so->stack->next;
|
||||
stk->block = opaque->rightlink;
|
||||
stk->parentlsn = so->stack->parentlsn;
|
||||
memset(&(stk->lsn), 0, sizeof(GistNSN));
|
||||
so->stack->next = stk;
|
||||
}
|
||||
|
||||
/* if page is empty, then just skip it */
|
||||
if (PageIsEmpty(p))
|
||||
{
|
||||
LockBuffer(so->curbuf, GIST_UNLOCK);
|
||||
stk = so->stack->next;
|
||||
pfree(so->stack);
|
||||
so->stack = stk;
|
||||
|
||||
if (so->stack == NULL)
|
||||
{
|
||||
ReleaseBuffer(so->curbuf);
|
||||
so->curbuf = InvalidBuffer;
|
||||
return ntids;
|
||||
}
|
||||
|
||||
so->curbuf = ReleaseAndReadBuffer(so->curbuf, scan->indexRelation,
|
||||
stk->block);
|
||||
continue;
|
||||
}
|
||||
|
||||
n = FirstOffsetNumber;
|
||||
|
||||
/* wonderful, we can look at page */
|
||||
so->nPageData = so->curPageData = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
n = gistfindnext(scan, n);
|
||||
|
||||
if (!OffsetNumberIsValid(n))
|
||||
{
|
||||
/*
|
||||
* If we was called from gistgettuple and current buffer
|
||||
* contains something matched then make a recursive call - it
|
||||
* will return ItemPointer from so->pageData. But we save
|
||||
* buffer pinned to support tuple's killing
|
||||
*/
|
||||
if (!tbm && so->nPageData > 0)
|
||||
{
|
||||
LockBuffer(so->curbuf, GIST_UNLOCK);
|
||||
return gistnext(scan, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* We ran out of matching index entries on the current page,
|
||||
* so pop the top stack entry and use it to continue the
|
||||
* search.
|
||||
*/
|
||||
LockBuffer(so->curbuf, GIST_UNLOCK);
|
||||
stk = so->stack->next;
|
||||
pfree(so->stack);
|
||||
so->stack = stk;
|
||||
|
||||
/* If we're out of stack entries, we're done */
|
||||
|
||||
if (so->stack == NULL)
|
||||
{
|
||||
ReleaseBuffer(so->curbuf);
|
||||
so->curbuf = InvalidBuffer;
|
||||
return ntids;
|
||||
}
|
||||
|
||||
so->curbuf = ReleaseAndReadBuffer(so->curbuf,
|
||||
scan->indexRelation,
|
||||
stk->block);
|
||||
/* XXX go up */
|
||||
break;
|
||||
}
|
||||
|
||||
if (GistPageIsLeaf(p))
|
||||
{
|
||||
/*
|
||||
* We've found a matching index entry in a leaf page, so
|
||||
* return success. Note that we keep "curbuf" pinned so that
|
||||
* we can efficiently resume the index scan later.
|
||||
*/
|
||||
|
||||
if (!(scan->ignore_killed_tuples &&
|
||||
ItemIdIsDead(PageGetItemId(p, n))))
|
||||
{
|
||||
it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
|
||||
ntids++;
|
||||
if (tbm != NULL)
|
||||
tbm_add_tuples(tbm, &it->t_tid, 1, scan->xs_recheck);
|
||||
else
|
||||
{
|
||||
so->pageData[so->nPageData].heapPtr = it->t_tid;
|
||||
so->pageData[so->nPageData].pageOffset = n;
|
||||
so->pageData[so->nPageData].recheck = scan->xs_recheck;
|
||||
so->nPageData++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* We've found an entry in an internal node whose key is
|
||||
* consistent with the search key, so push it to stack
|
||||
*/
|
||||
stk = (GISTSearchStack *) palloc(sizeof(GISTSearchStack));
|
||||
|
||||
it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
|
||||
stk->block = ItemPointerGetBlockNumber(&(it->t_tid));
|
||||
memset(&(stk->lsn), 0, sizeof(GistNSN));
|
||||
stk->parentlsn = so->stack->lsn;
|
||||
|
||||
stk->next = so->stack->next;
|
||||
so->stack->next = stk;
|
||||
}
|
||||
|
||||
n = OffsetNumberNext(n);
|
||||
}
|
||||
}
|
||||
|
||||
return ntids;
|
||||
}
|
||||
|
||||
/*
|
||||
* gistindex_keytest() -- does this index tuple satisfy the scan key(s)?
|
||||
*
|
||||
* On success return for a leaf tuple, scan->xs_recheck is set to indicate
|
||||
* The index tuple might represent either a heap tuple or a lower index page,
|
||||
* depending on whether the containing page is a leaf page or not.
|
||||
*
|
||||
* On success return for a heap tuple, *recheck_p is set to indicate
|
||||
* whether recheck is needed. We recheck if any of the consistent() functions
|
||||
* request it.
|
||||
* request it. recheck is not interesting when examining a non-leaf entry,
|
||||
* since we must visit the lower index page if there's any doubt.
|
||||
*
|
||||
* If we are doing an ordered scan, so->distances[] is filled with distance
|
||||
* data from the distance() functions before returning success.
|
||||
*
|
||||
* We must decompress the key in the IndexTuple before passing it to the
|
||||
* sk_func (and we have previously overwritten the sk_func to use the
|
||||
* user-defined Consistent method, so we actually are invoking that).
|
||||
* sk_funcs (which actually are the opclass Consistent or Distance methods).
|
||||
*
|
||||
* Note that this function is always invoked in a short-lived memory context,
|
||||
* so we don't need to worry about cleaning up allocated memory, either here
|
||||
* or in the implementation of any Consistent methods.
|
||||
* or in the implementation of any Consistent or Distance methods.
|
||||
*/
|
||||
static bool
|
||||
gistindex_keytest(IndexTuple tuple,
|
||||
IndexScanDesc scan,
|
||||
OffsetNumber offset)
|
||||
gistindex_keytest(IndexScanDesc scan,
|
||||
IndexTuple tuple,
|
||||
Page page,
|
||||
OffsetNumber offset,
|
||||
bool *recheck_p)
|
||||
{
|
||||
int keySize = scan->numberOfKeys;
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
GISTSTATE *giststate = so->giststate;
|
||||
ScanKey key = scan->keyData;
|
||||
int keySize = scan->numberOfKeys;
|
||||
double *distance_p;
|
||||
Relation r = scan->indexRelation;
|
||||
GISTScanOpaque so;
|
||||
Page p;
|
||||
GISTSTATE *giststate;
|
||||
|
||||
so = (GISTScanOpaque) scan->opaque;
|
||||
giststate = so->giststate;
|
||||
p = BufferGetPage(so->curbuf);
|
||||
|
||||
scan->xs_recheck = false;
|
||||
*recheck_p = false;
|
||||
|
||||
/*
|
||||
* Tuple doesn't restore after crash recovery because of incomplete insert
|
||||
* If it's a leftover invalid tuple from pre-9.1, treat it as a match
|
||||
* with minimum possible distances. This means we'll always follow it
|
||||
* to the referenced page.
|
||||
*/
|
||||
if (!GistPageIsLeaf(p) && GistTupleIsInvalid(tuple))
|
||||
return true;
|
||||
if (GistTupleIsInvalid(tuple))
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < scan->numberOfOrderBys; i++)
|
||||
so->distances[i] = -get_float8_infinity();
|
||||
*recheck_p = true; /* probably unnecessary */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check whether it matches according to the Consistent functions */
|
||||
while (keySize > 0)
|
||||
{
|
||||
Datum datum;
|
||||
bool isNull;
|
||||
Datum test;
|
||||
bool recheck;
|
||||
GISTENTRY de;
|
||||
|
||||
datum = index_getattr(tuple,
|
||||
key->sk_attno,
|
||||
@ -419,7 +97,7 @@ gistindex_keytest(IndexTuple tuple,
|
||||
*/
|
||||
if (key->sk_flags & SK_SEARCHNULL)
|
||||
{
|
||||
if (GistPageIsLeaf(p) && !isNull)
|
||||
if (GistPageIsLeaf(page) && !isNull)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -435,8 +113,12 @@ gistindex_keytest(IndexTuple tuple,
|
||||
}
|
||||
else
|
||||
{
|
||||
Datum test;
|
||||
bool recheck;
|
||||
GISTENTRY de;
|
||||
|
||||
gistdentryinit(giststate, key->sk_attno - 1, &de,
|
||||
datum, r, p, offset,
|
||||
datum, r, page, offset,
|
||||
FALSE, isNull);
|
||||
|
||||
/*
|
||||
@ -463,61 +145,428 @@ gistindex_keytest(IndexTuple tuple,
|
||||
|
||||
if (!DatumGetBool(test))
|
||||
return false;
|
||||
scan->xs_recheck |= recheck;
|
||||
*recheck_p |= recheck;
|
||||
}
|
||||
|
||||
keySize--;
|
||||
key++;
|
||||
keySize--;
|
||||
}
|
||||
|
||||
/* OK, it passes --- now let's compute the distances */
|
||||
key = scan->orderByData;
|
||||
distance_p = so->distances;
|
||||
keySize = scan->numberOfOrderBys;
|
||||
while (keySize > 0)
|
||||
{
|
||||
Datum datum;
|
||||
bool isNull;
|
||||
|
||||
datum = index_getattr(tuple,
|
||||
key->sk_attno,
|
||||
giststate->tupdesc,
|
||||
&isNull);
|
||||
|
||||
if ((key->sk_flags & SK_ISNULL) || isNull)
|
||||
{
|
||||
/* Assume distance computes as null and sorts to the end */
|
||||
*distance_p = get_float8_infinity();
|
||||
}
|
||||
else
|
||||
{
|
||||
Datum dist;
|
||||
GISTENTRY de;
|
||||
|
||||
gistdentryinit(giststate, key->sk_attno - 1, &de,
|
||||
datum, r, page, offset,
|
||||
FALSE, isNull);
|
||||
|
||||
/*
|
||||
* Call the Distance function to evaluate the distance. The
|
||||
* arguments are the index datum (as a GISTENTRY*), the comparison
|
||||
* datum, and the ordering operator's strategy number and subtype
|
||||
* from pg_amop.
|
||||
*
|
||||
* (Presently there's no need to pass the subtype since it'll
|
||||
* always be zero, but might as well pass it for possible future
|
||||
* use.)
|
||||
*
|
||||
* Note that Distance functions don't get a recheck argument.
|
||||
* We can't tolerate lossy distance calculations on leaf tuples;
|
||||
* there is no opportunity to re-sort the tuples afterwards.
|
||||
*/
|
||||
dist = FunctionCall4(&key->sk_func,
|
||||
PointerGetDatum(&de),
|
||||
key->sk_argument,
|
||||
Int32GetDatum(key->sk_strategy),
|
||||
ObjectIdGetDatum(key->sk_subtype));
|
||||
|
||||
*distance_p = DatumGetFloat8(dist);
|
||||
}
|
||||
|
||||
key++;
|
||||
distance_p++;
|
||||
keySize--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the offset of the first index entry that is consistent with
|
||||
* the search key after offset 'n' in the current page. If there are
|
||||
* no more consistent entries, return InvalidOffsetNumber.
|
||||
* On success, scan->xs_recheck is set correctly, too.
|
||||
* Page should be locked....
|
||||
* Scan all items on the GiST index page identified by *pageItem, and insert
|
||||
* them into the queue (or directly to output areas)
|
||||
*
|
||||
* scan: index scan we are executing
|
||||
* pageItem: search queue item identifying an index page to scan
|
||||
* myDistances: distances array associated with pageItem, or NULL at the root
|
||||
* tbm: if not NULL, gistgetbitmap's output bitmap
|
||||
* ntids: if not NULL, gistgetbitmap's output tuple counter
|
||||
*
|
||||
* If tbm/ntids aren't NULL, we are doing an amgetbitmap scan, and heap
|
||||
* tuples should be reported directly into the bitmap. If they are NULL,
|
||||
* we're doing a plain or ordered indexscan. For a plain indexscan, heap
|
||||
* tuple TIDs are returned into so->pageData[]. For an ordered indexscan,
|
||||
* heap tuple TIDs are pushed into individual search queue items.
|
||||
*
|
||||
* If we detect that the index page has split since we saw its downlink
|
||||
* in the parent, we push its new right sibling onto the queue so the
|
||||
* sibling will be processed next.
|
||||
*/
|
||||
static OffsetNumber
|
||||
gistfindnext(IndexScanDesc scan, OffsetNumber n)
|
||||
static void
|
||||
gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
|
||||
TIDBitmap *tbm, int64 *ntids)
|
||||
{
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
Buffer buffer;
|
||||
Page page;
|
||||
GISTPageOpaque opaque;
|
||||
OffsetNumber maxoff;
|
||||
IndexTuple it;
|
||||
GISTScanOpaque so;
|
||||
OffsetNumber i;
|
||||
GISTSearchTreeItem *tmpItem = so->tmpTreeItem;
|
||||
bool isNew;
|
||||
MemoryContext oldcxt;
|
||||
Page p;
|
||||
|
||||
so = (GISTScanOpaque) scan->opaque;
|
||||
p = BufferGetPage(so->curbuf);
|
||||
maxoff = PageGetMaxOffsetNumber(p);
|
||||
Assert(!GISTSearchItemIsHeap(*pageItem));
|
||||
|
||||
/*
|
||||
* Make sure we're in a short-lived memory context when we invoke a
|
||||
* user-supplied GiST method in gistindex_keytest(), so we don't leak
|
||||
* memory
|
||||
*/
|
||||
oldcxt = MemoryContextSwitchTo(so->tempCxt);
|
||||
buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
|
||||
LockBuffer(buffer, GIST_SHARE);
|
||||
gistcheckpage(scan->indexRelation, buffer);
|
||||
page = BufferGetPage(buffer);
|
||||
opaque = GistPageGetOpaque(page);
|
||||
|
||||
while (n >= FirstOffsetNumber && n <= maxoff)
|
||||
/* check if page split occurred since visit to parent */
|
||||
if (!XLogRecPtrIsInvalid(pageItem->data.parentlsn) &&
|
||||
XLByteLT(pageItem->data.parentlsn, opaque->nsn) &&
|
||||
opaque->rightlink != InvalidBlockNumber /* sanity check */ )
|
||||
{
|
||||
it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
|
||||
if (gistindex_keytest(it, scan, n))
|
||||
break;
|
||||
/* There was a page split, follow right link to add pages */
|
||||
GISTSearchItem *item;
|
||||
|
||||
n = OffsetNumberNext(n);
|
||||
/* This can't happen when starting at the root */
|
||||
Assert(myDistances != NULL);
|
||||
|
||||
oldcxt = MemoryContextSwitchTo(so->queueCxt);
|
||||
|
||||
/* Create new GISTSearchItem for the right sibling index page */
|
||||
item = palloc(sizeof(GISTSearchItem));
|
||||
item->next = NULL;
|
||||
item->blkno = opaque->rightlink;
|
||||
item->data.parentlsn = pageItem->data.parentlsn;
|
||||
|
||||
/* Insert it into the queue using same distances as for this page */
|
||||
tmpItem->head = item;
|
||||
tmpItem->lastHeap = NULL;
|
||||
memcpy(tmpItem->distances, myDistances,
|
||||
sizeof(double) * scan->numberOfOrderBys);
|
||||
|
||||
(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
MemoryContextReset(so->tempCxt);
|
||||
so->nPageData = so->curPageData = 0;
|
||||
|
||||
/*
|
||||
* If we found a matching entry, return its offset; otherwise return
|
||||
* InvalidOffsetNumber to inform the caller to go to the next page.
|
||||
* check all tuples on page
|
||||
*/
|
||||
if (n >= FirstOffsetNumber && n <= maxoff)
|
||||
return n;
|
||||
else
|
||||
return InvalidOffsetNumber;
|
||||
maxoff = PageGetMaxOffsetNumber(page);
|
||||
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
|
||||
{
|
||||
IndexTuple it = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
|
||||
bool match;
|
||||
bool recheck;
|
||||
|
||||
/*
|
||||
* Must call gistindex_keytest in tempCxt, and clean up any leftover
|
||||
* junk afterward.
|
||||
*/
|
||||
oldcxt = MemoryContextSwitchTo(so->tempCxt);
|
||||
|
||||
match = gistindex_keytest(scan, it, page, i, &recheck);
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
MemoryContextReset(so->tempCxt);
|
||||
|
||||
/* Ignore tuple if it doesn't match */
|
||||
if (!match)
|
||||
continue;
|
||||
|
||||
if (tbm && GistPageIsLeaf(page))
|
||||
{
|
||||
/*
|
||||
* getbitmap scan, so just push heap tuple TIDs into the bitmap
|
||||
* without worrying about ordering
|
||||
*/
|
||||
tbm_add_tuples(tbm, &it->t_tid, 1, recheck);
|
||||
(*ntids)++;
|
||||
}
|
||||
else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
|
||||
{
|
||||
/*
|
||||
* Non-ordered scan, so report heap tuples in so->pageData[]
|
||||
*/
|
||||
so->pageData[so->nPageData].heapPtr = it->t_tid;
|
||||
so->pageData[so->nPageData].recheck = recheck;
|
||||
so->nPageData++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Must push item into search queue. We get here for any lower
|
||||
* index page, and also for heap tuples if doing an ordered
|
||||
* search.
|
||||
*/
|
||||
GISTSearchItem *item;
|
||||
|
||||
oldcxt = MemoryContextSwitchTo(so->queueCxt);
|
||||
|
||||
/* Create new GISTSearchItem for this item */
|
||||
item = palloc(sizeof(GISTSearchItem));
|
||||
item->next = NULL;
|
||||
|
||||
if (GistPageIsLeaf(page))
|
||||
{
|
||||
/* Creating heap-tuple GISTSearchItem */
|
||||
item->blkno = InvalidBlockNumber;
|
||||
item->data.heap.heapPtr = it->t_tid;
|
||||
item->data.heap.recheck = recheck;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Creating index-page GISTSearchItem */
|
||||
item->blkno = ItemPointerGetBlockNumber(&it->t_tid);
|
||||
/* lsn of current page is lsn of parent page for child */
|
||||
item->data.parentlsn = PageGetLSN(page);
|
||||
}
|
||||
|
||||
/* Insert it into the queue using new distance data */
|
||||
tmpItem->head = item;
|
||||
tmpItem->lastHeap = GISTSearchItemIsHeap(*item) ? item : NULL;
|
||||
memcpy(tmpItem->distances, so->distances,
|
||||
sizeof(double) * scan->numberOfOrderBys);
|
||||
|
||||
(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
}
|
||||
|
||||
UnlockReleaseBuffer(buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract next item (in order) from search queue
|
||||
*
|
||||
* Returns a GISTSearchItem or NULL. Caller must pfree item when done with it.
|
||||
*
|
||||
* NOTE: on successful return, so->curTreeItem is the GISTSearchTreeItem that
|
||||
* contained the result item. Callers can use so->curTreeItem->distances as
|
||||
* the distances value for the item.
|
||||
*/
|
||||
static GISTSearchItem *
|
||||
getNextGISTSearchItem(GISTScanOpaque so)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
GISTSearchItem *item;
|
||||
|
||||
/* Update curTreeItem if we don't have one */
|
||||
if (so->curTreeItem == NULL)
|
||||
{
|
||||
so->curTreeItem = (GISTSearchTreeItem *) rb_leftmost(so->queue);
|
||||
/* Done when tree is empty */
|
||||
if (so->curTreeItem == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
item = so->curTreeItem->head;
|
||||
if (item != NULL)
|
||||
{
|
||||
/* Delink item from chain */
|
||||
so->curTreeItem->head = item->next;
|
||||
/* Return item; caller is responsible to pfree it */
|
||||
return item;
|
||||
}
|
||||
|
||||
/* curTreeItem is exhausted, so remove it from rbtree */
|
||||
rb_delete(so->queue, (RBNode *) so->curTreeItem);
|
||||
so->curTreeItem = NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetch next heap tuple in an ordered search
|
||||
*/
|
||||
static bool
|
||||
getNextNearest(IndexScanDesc scan)
|
||||
{
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
bool res = false;
|
||||
|
||||
do
|
||||
{
|
||||
GISTSearchItem *item = getNextGISTSearchItem(so);
|
||||
|
||||
if (!item)
|
||||
break;
|
||||
|
||||
if (GISTSearchItemIsHeap(*item))
|
||||
{
|
||||
/* found a heap item at currently minimal distance */
|
||||
scan->xs_ctup.t_self = item->data.heap.heapPtr;
|
||||
scan->xs_recheck = item->data.heap.recheck;
|
||||
res = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* visit an index page, extract its items into queue */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
|
||||
}
|
||||
|
||||
pfree(item);
|
||||
} while (!res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* gistgettuple() -- Get the next tuple in the scan
|
||||
*/
|
||||
Datum
|
||||
gistgettuple(PG_FUNCTION_ARGS)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
|
||||
if (!so->qual_ok)
|
||||
PG_RETURN_BOOL(false);
|
||||
|
||||
if (so->firstCall)
|
||||
{
|
||||
/* Begin the scan by processing the root page */
|
||||
GISTSearchItem fakeItem;
|
||||
|
||||
pgstat_count_index_scan(scan->indexRelation);
|
||||
|
||||
so->firstCall = false;
|
||||
so->curTreeItem = NULL;
|
||||
so->curPageData = so->nPageData = 0;
|
||||
|
||||
fakeItem.blkno = GIST_ROOT_BLKNO;
|
||||
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
|
||||
gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
if (scan->numberOfOrderBys > 0)
|
||||
{
|
||||
/* Must fetch tuples in strict distance order */
|
||||
PG_RETURN_BOOL(getNextNearest(scan));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Fetch tuples index-page-at-a-time */
|
||||
for (;;)
|
||||
{
|
||||
if (so->curPageData < so->nPageData)
|
||||
{
|
||||
/* continuing to return tuples from a leaf page */
|
||||
scan->xs_ctup.t_self = so->pageData[so->curPageData].heapPtr;
|
||||
scan->xs_recheck = so->pageData[so->curPageData].recheck;
|
||||
so->curPageData++;
|
||||
PG_RETURN_BOOL(true);
|
||||
}
|
||||
|
||||
/* find and process the next index page */
|
||||
do
|
||||
{
|
||||
GISTSearchItem *item = getNextGISTSearchItem(so);
|
||||
|
||||
if (!item)
|
||||
PG_RETURN_BOOL(false);
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
/*
|
||||
* While scanning a leaf page, ItemPointers of matching heap
|
||||
* tuples are stored in so->pageData. If there are any on
|
||||
* this page, we fall out of the inner "do" and loop around
|
||||
* to return them.
|
||||
*/
|
||||
gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
|
||||
|
||||
pfree(item);
|
||||
} while (so->nPageData == 0);
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_BOOL(false); /* keep compiler quiet */
|
||||
}
|
||||
|
||||
/*
|
||||
* gistgetbitmap() -- Get a bitmap of all heap tuple locations
|
||||
*/
|
||||
Datum
|
||||
gistgetbitmap(PG_FUNCTION_ARGS)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||
TIDBitmap *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
int64 ntids = 0;
|
||||
GISTSearchItem fakeItem;
|
||||
|
||||
if (!so->qual_ok)
|
||||
PG_RETURN_INT64(0);
|
||||
|
||||
pgstat_count_index_scan(scan->indexRelation);
|
||||
|
||||
/* Begin the scan by processing the root page */
|
||||
so->curTreeItem = NULL;
|
||||
so->curPageData = so->nPageData = 0;
|
||||
|
||||
fakeItem.blkno = GIST_ROOT_BLKNO;
|
||||
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
|
||||
gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
|
||||
|
||||
/*
|
||||
* While scanning a leaf page, ItemPointers of matching heap tuples will
|
||||
* be stored directly into tbm, so we don't need to deal with them here.
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
GISTSearchItem *item = getNextGISTSearchItem(so);
|
||||
|
||||
if (!item)
|
||||
break;
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
gistScanPage(scan, item, so->curTreeItem->distances, tbm, &ntids);
|
||||
|
||||
pfree(item);
|
||||
}
|
||||
|
||||
PG_RETURN_INT64(ntids);
|
||||
}
|
||||
|
@ -904,6 +904,76 @@ gist_point_compress(PG_FUNCTION_ARGS)
|
||||
PG_RETURN_POINTER(entry);
|
||||
}
|
||||
|
||||
#define point_point_distance(p1,p2) \
|
||||
DatumGetFloat8(DirectFunctionCall2(point_distance, \
|
||||
PointPGetDatum(p1), PointPGetDatum(p2)))
|
||||
|
||||
static double
|
||||
computeDistance(bool isLeaf, BOX *box, Point *point)
|
||||
{
|
||||
double result = 0.0;
|
||||
|
||||
if (isLeaf)
|
||||
{
|
||||
/* simple point to point distance */
|
||||
result = point_point_distance(point, &box->low);
|
||||
}
|
||||
else if (point->x <= box->high.x && point->x >= box->low.x &&
|
||||
point->y <= box->high.y && point->y >= box->low.y)
|
||||
{
|
||||
/* point inside the box */
|
||||
result = 0.0;
|
||||
}
|
||||
else if (point->x <= box->high.x && point->x >= box->low.x)
|
||||
{
|
||||
/* point is over or below box */
|
||||
Assert(box->low.y <= box->high.y);
|
||||
if (point->y > box->high.y)
|
||||
result = point->y - box->high.y;
|
||||
else if (point->y < box->low.y)
|
||||
result = box->low.y - point->y;
|
||||
else
|
||||
elog(ERROR, "inconsistent point values");
|
||||
}
|
||||
else if (point->y <= box->high.y && point->y >= box->low.y)
|
||||
{
|
||||
/* point is to left or right of box */
|
||||
Assert(box->low.x <= box->high.x);
|
||||
if (point->x > box->high.x)
|
||||
result = point->x - box->high.x;
|
||||
else if (point->x < box->low.x)
|
||||
result = box->low.x - point->x;
|
||||
else
|
||||
elog(ERROR, "inconsistent point values");
|
||||
}
|
||||
else
|
||||
{
|
||||
/* closest point will be a vertex */
|
||||
Point p;
|
||||
double subresult;
|
||||
|
||||
result = point_point_distance(point, &box->low);
|
||||
|
||||
subresult = point_point_distance(point, &box->high);
|
||||
if (result > subresult)
|
||||
result = subresult;
|
||||
|
||||
p.x = box->low.x;
|
||||
p.y = box->high.y;
|
||||
subresult = point_point_distance(point, &p);
|
||||
if (result > subresult)
|
||||
result = subresult;
|
||||
|
||||
p.x = box->high.x;
|
||||
p.y = box->low.y;
|
||||
subresult = point_point_distance(point, &p);
|
||||
if (result > subresult)
|
||||
result = subresult;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
gist_point_consistent_internal(StrategyNumber strategy,
|
||||
bool isLeaf, BOX *key, Point *query)
|
||||
@ -954,8 +1024,8 @@ gist_point_consistent(PG_FUNCTION_ARGS)
|
||||
{
|
||||
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
|
||||
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
|
||||
bool result;
|
||||
bool *recheck = (bool *) PG_GETARG_POINTER(4);
|
||||
bool result;
|
||||
StrategyNumber strategyGroup = strategy / GeoStrategyNumberOffset;
|
||||
|
||||
switch (strategyGroup)
|
||||
@ -1034,9 +1104,32 @@ gist_point_consistent(PG_FUNCTION_ARGS)
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = false; /* silence compiler warning */
|
||||
elog(ERROR, "unknown strategy number: %d", strategy);
|
||||
result = false; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
PG_RETURN_BOOL(result);
|
||||
}
|
||||
|
||||
Datum
|
||||
gist_point_distance(PG_FUNCTION_ARGS)
|
||||
{
|
||||
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
|
||||
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
|
||||
double distance;
|
||||
StrategyNumber strategyGroup = strategy / GeoStrategyNumberOffset;
|
||||
|
||||
switch (strategyGroup)
|
||||
{
|
||||
case PointStrategyNumberGroup:
|
||||
distance = computeDistance(GIST_LEAF(entry),
|
||||
DatumGetBoxP(entry->key),
|
||||
PG_GETARG_POINT_P(1));
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unknown strategy number: %d", strategy);
|
||||
distance = 0.0; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
PG_RETURN_FLOAT8(distance);
|
||||
}
|
||||
|
@ -20,8 +20,84 @@
|
||||
#include "access/relscan.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
|
||||
static void gistfreestack(GISTSearchStack *s);
|
||||
|
||||
/*
|
||||
* RBTree support functions for the GISTSearchTreeItem queue
|
||||
*/
|
||||
|
||||
static int
|
||||
GISTSearchTreeItemComparator(const RBNode *a, const RBNode *b, void *arg)
|
||||
{
|
||||
const GISTSearchTreeItem *sa = (const GISTSearchTreeItem *) a;
|
||||
const GISTSearchTreeItem *sb = (const GISTSearchTreeItem *) b;
|
||||
IndexScanDesc scan = (IndexScanDesc) arg;
|
||||
int i;
|
||||
|
||||
/* Order according to distance comparison */
|
||||
for (i = 0; i < scan->numberOfOrderBys; i++)
|
||||
{
|
||||
if (sa->distances[i] != sb->distances[i])
|
||||
return (sa->distances[i] > sb->distances[i]) ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
GISTSearchTreeItemCombiner(RBNode *existing, const RBNode *newrb, void *arg)
|
||||
{
|
||||
GISTSearchTreeItem *scurrent = (GISTSearchTreeItem *) existing;
|
||||
const GISTSearchTreeItem *snew = (const GISTSearchTreeItem *) newrb;
|
||||
GISTSearchItem *newitem = snew->head;
|
||||
|
||||
/* snew should have just one item in its chain */
|
||||
Assert(newitem && newitem->next == NULL);
|
||||
|
||||
/*
|
||||
* If new item is heap tuple, it goes to front of chain; otherwise insert
|
||||
* it before the first index-page item, so that index pages are visited
|
||||
* in LIFO order, ensuring depth-first search of index pages. See
|
||||
* comments in gist_private.h.
|
||||
*/
|
||||
if (GISTSearchItemIsHeap(*newitem))
|
||||
{
|
||||
newitem->next = scurrent->head;
|
||||
scurrent->head = newitem;
|
||||
if (scurrent->lastHeap == NULL)
|
||||
scurrent->lastHeap = newitem;
|
||||
}
|
||||
else if (scurrent->lastHeap == NULL)
|
||||
{
|
||||
newitem->next = scurrent->head;
|
||||
scurrent->head = newitem;
|
||||
}
|
||||
else
|
||||
{
|
||||
newitem->next = scurrent->lastHeap->next;
|
||||
scurrent->lastHeap->next = newitem;
|
||||
}
|
||||
}
|
||||
|
||||
static RBNode *
|
||||
GISTSearchTreeItemAllocator(void *arg)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) arg;
|
||||
|
||||
return palloc(GSTIHDRSZ + sizeof(double) * scan->numberOfOrderBys);
|
||||
}
|
||||
|
||||
static void
|
||||
GISTSearchTreeItemDeleter(RBNode *rb, void *arg)
|
||||
{
|
||||
pfree(rb);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Index AM API functions for scanning GiST indexes
|
||||
*/
|
||||
|
||||
Datum
|
||||
gistbeginscan(PG_FUNCTION_ARGS)
|
||||
@ -32,18 +108,22 @@ gistbeginscan(PG_FUNCTION_ARGS)
|
||||
IndexScanDesc scan;
|
||||
GISTScanOpaque so;
|
||||
|
||||
/* no order by operators allowed */
|
||||
Assert(norderbys == 0);
|
||||
|
||||
scan = RelationGetIndexScan(r, nkeys, norderbys);
|
||||
|
||||
/* initialize opaque data */
|
||||
so = (GISTScanOpaque) palloc(sizeof(GISTScanOpaqueData));
|
||||
so->stack = NULL;
|
||||
so = (GISTScanOpaque) palloc0(sizeof(GISTScanOpaqueData));
|
||||
so->queueCxt = AllocSetContextCreate(CurrentMemoryContext,
|
||||
"GiST queue context",
|
||||
ALLOCSET_DEFAULT_MINSIZE,
|
||||
ALLOCSET_DEFAULT_INITSIZE,
|
||||
ALLOCSET_DEFAULT_MAXSIZE);
|
||||
so->tempCxt = createTempGistContext();
|
||||
so->curbuf = InvalidBuffer;
|
||||
so->giststate = (GISTSTATE *) palloc(sizeof(GISTSTATE));
|
||||
initGISTstate(so->giststate, scan->indexRelation);
|
||||
/* workspaces with size dependent on numberOfOrderBys: */
|
||||
so->tmpTreeItem = palloc(GSTIHDRSZ + sizeof(double) * scan->numberOfOrderBys);
|
||||
so->distances = palloc(sizeof(double) * scan->numberOfOrderBys);
|
||||
so->qual_ok = true; /* in case there are zero keys */
|
||||
|
||||
scan->opaque = so;
|
||||
|
||||
@ -55,27 +135,27 @@ gistrescan(PG_FUNCTION_ARGS)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||
ScanKey key = (ScanKey) PG_GETARG_POINTER(1);
|
||||
/* remaining arguments are ignored */
|
||||
ScanKey orderbys = (ScanKey) PG_GETARG_POINTER(3);
|
||||
/* nkeys and norderbys arguments are ignored */
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
int i;
|
||||
MemoryContext oldCxt;
|
||||
|
||||
/* rescan an existing indexscan --- reset state */
|
||||
gistfreestack(so->stack);
|
||||
so->stack = NULL;
|
||||
/* drop pins on buffers -- no locks held */
|
||||
if (BufferIsValid(so->curbuf))
|
||||
{
|
||||
ReleaseBuffer(so->curbuf);
|
||||
so->curbuf = InvalidBuffer;
|
||||
}
|
||||
MemoryContextReset(so->queueCxt);
|
||||
so->curTreeItem = NULL;
|
||||
|
||||
/*
|
||||
* Clear all the pointers.
|
||||
*/
|
||||
ItemPointerSetInvalid(&so->curpos);
|
||||
so->nPageData = so->curPageData = 0;
|
||||
/* create new, empty RBTree for search queue */
|
||||
oldCxt = MemoryContextSwitchTo(so->queueCxt);
|
||||
so->queue = rb_create(GSTIHDRSZ + sizeof(double) * scan->numberOfOrderBys,
|
||||
GISTSearchTreeItemComparator,
|
||||
GISTSearchTreeItemCombiner,
|
||||
GISTSearchTreeItemAllocator,
|
||||
GISTSearchTreeItemDeleter,
|
||||
scan);
|
||||
MemoryContextSwitchTo(oldCxt);
|
||||
|
||||
so->qual_ok = true;
|
||||
so->firstCall = true;
|
||||
|
||||
/* Update scan key, if a new one is given */
|
||||
if (key && scan->numberOfKeys > 0)
|
||||
@ -84,7 +164,7 @@ gistrescan(PG_FUNCTION_ARGS)
|
||||
scan->numberOfKeys * sizeof(ScanKeyData));
|
||||
|
||||
/*
|
||||
* Modify the scan key so that all the Consistent method is called for
|
||||
* 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
|
||||
@ -94,9 +174,11 @@ gistrescan(PG_FUNCTION_ARGS)
|
||||
* 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]);
|
||||
ScanKey skey = scan->keyData + i;
|
||||
|
||||
skey->sk_func = so->giststate->consistentFn[skey->sk_attno - 1];
|
||||
|
||||
@ -108,6 +190,33 @@ gistrescan(PG_FUNCTION_ARGS)
|
||||
}
|
||||
}
|
||||
|
||||
/* Update order-by key, if a new one is given */
|
||||
if (orderbys && scan->numberOfOrderBys > 0)
|
||||
{
|
||||
memmove(scan->orderByData, orderbys,
|
||||
scan->numberOfOrderBys * sizeof(ScanKeyData));
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
skey->sk_func = so->giststate->distanceFn[skey->sk_attno - 1];
|
||||
|
||||
/* Check we actually have a distance function ... */
|
||||
if (!OidIsValid(skey->sk_func.fn_oid))
|
||||
elog(ERROR, "missing support function %d for attribute %d of index \"%s\"",
|
||||
GIST_DISTANCE_PROC, skey->sk_attno,
|
||||
RelationGetRelationName(scan->indexRelation));
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
@ -131,26 +240,12 @@ gistendscan(PG_FUNCTION_ARGS)
|
||||
IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
|
||||
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
|
||||
|
||||
gistfreestack(so->stack);
|
||||
if (so->giststate != NULL)
|
||||
freeGISTstate(so->giststate);
|
||||
/* drop pins on buffers -- we aren't holding any locks */
|
||||
if (BufferIsValid(so->curbuf))
|
||||
ReleaseBuffer(so->curbuf);
|
||||
freeGISTstate(so->giststate);
|
||||
MemoryContextDelete(so->queueCxt);
|
||||
MemoryContextDelete(so->tempCxt);
|
||||
pfree(so->tmpTreeItem);
|
||||
pfree(so->distances);
|
||||
pfree(so);
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
static void
|
||||
gistfreestack(GISTSearchStack *s)
|
||||
{
|
||||
while (s != NULL)
|
||||
{
|
||||
GISTSearchStack *p = s->next;
|
||||
|
||||
pfree(s);
|
||||
s = p;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,8 @@
|
||||
#define GIST_PENALTY_PROC 5
|
||||
#define GIST_PICKSPLIT_PROC 6
|
||||
#define GIST_EQUAL_PROC 7
|
||||
#define GISTNProcs 7
|
||||
#define GIST_DISTANCE_PROC 8
|
||||
#define GISTNProcs 8
|
||||
|
||||
/*
|
||||
* strategy numbers for GiST opclasses that want to implement the old
|
||||
@ -52,6 +53,7 @@
|
||||
#define RTOverAboveStrategyNumber 12
|
||||
#define RTOldContainsStrategyNumber 13 /* for old spelling of @> */
|
||||
#define RTOldContainedByStrategyNumber 14 /* for old spelling of <@ */
|
||||
#define RTKNNSearchStrategyNumber 15
|
||||
|
||||
/*
|
||||
* Page opaque data in a GiST index page.
|
||||
@ -131,13 +133,13 @@ typedef struct GISTENTRY
|
||||
#define GistClearTuplesDeleted(page) ( GistPageGetOpaque(page)->flags &= ~F_TUPLES_DELETED)
|
||||
|
||||
/*
|
||||
* Vector of GISTENTRY structs; user-defined methods union and pick
|
||||
* split takes it as one of their arguments
|
||||
* Vector of GISTENTRY structs; user-defined methods union and picksplit
|
||||
* take it as one of their arguments
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int32 n; /* number of elements */
|
||||
GISTENTRY vector[1];
|
||||
GISTENTRY vector[1]; /* variable-length array */
|
||||
} GistEntryVector;
|
||||
|
||||
#define GEVHDRSZ (offsetof(GistEntryVector, vector))
|
||||
|
@ -17,34 +17,19 @@
|
||||
#include "access/gist.h"
|
||||
#include "access/itup.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "utils/rbtree.h"
|
||||
|
||||
#define GIST_UNLOCK BUFFER_LOCK_UNLOCK
|
||||
/* Buffer lock modes */
|
||||
#define GIST_SHARE BUFFER_LOCK_SHARE
|
||||
#define GIST_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE
|
||||
|
||||
#define GIST_UNLOCK BUFFER_LOCK_UNLOCK
|
||||
|
||||
/*
|
||||
* XXX old comment!!!
|
||||
* When we descend a tree, we keep a stack of parent pointers. This
|
||||
* allows us to follow a chain of internal node points until we reach
|
||||
* a leaf node, and then back up the stack to re-examine the internal
|
||||
* nodes.
|
||||
* GISTSTATE: information needed for any GiST index operation
|
||||
*
|
||||
* 'parent' is the previous stack entry -- i.e. the node we arrived
|
||||
* from. 'block' is the node's block number. 'offset' is the offset in
|
||||
* the node's page that we stopped at (i.e. we followed the child
|
||||
* pointer located at the specified offset).
|
||||
* This struct retains call info for the index's opclass-specific support
|
||||
* functions (per index column), plus the index's tuple descriptor.
|
||||
*/
|
||||
typedef struct GISTSearchStack
|
||||
{
|
||||
struct GISTSearchStack *next;
|
||||
BlockNumber block;
|
||||
/* to identify page changed */
|
||||
GistNSN lsn;
|
||||
/* to recognize split occured */
|
||||
GistNSN parentlsn;
|
||||
} GISTSearchStack;
|
||||
|
||||
typedef struct GISTSTATE
|
||||
{
|
||||
FmgrInfo consistentFn[INDEX_MAX_KEYS];
|
||||
@ -54,38 +39,96 @@ typedef struct GISTSTATE
|
||||
FmgrInfo penaltyFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo picksplitFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo equalFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo distanceFn[INDEX_MAX_KEYS];
|
||||
|
||||
TupleDesc tupdesc;
|
||||
} GISTSTATE;
|
||||
|
||||
typedef struct ItemResult
|
||||
{
|
||||
ItemPointerData heapPtr;
|
||||
OffsetNumber pageOffset; /* offset in index page */
|
||||
bool recheck;
|
||||
} ItemResult;
|
||||
|
||||
/*
|
||||
* When we're doing a scan, we need to keep track of the parent stack
|
||||
* for the marked and current items.
|
||||
* During a GiST index search, we must maintain a queue of unvisited items,
|
||||
* which can be either individual heap tuples or whole index pages. If it
|
||||
* is an ordered search, the unvisited items should be visited in distance
|
||||
* order. Unvisited items at the same distance should be visited in
|
||||
* depth-first order, that is heap items first, then lower index pages, then
|
||||
* upper index pages; this rule avoids doing extra work during a search that
|
||||
* ends early due to LIMIT.
|
||||
*
|
||||
* To perform an ordered search, we use an RBTree to manage the distance-order
|
||||
* queue. Each GISTSearchTreeItem stores all unvisited items of the same
|
||||
* distance; they are GISTSearchItems chained together via their next fields.
|
||||
*
|
||||
* In a non-ordered search (no order-by operators), the RBTree degenerates
|
||||
* to a single item, which we use as a queue of unvisited index pages only.
|
||||
* In this case matched heap items from the current index leaf page are
|
||||
* remembered in GISTScanOpaqueData.pageData[] and returned directly from
|
||||
* there, instead of building a separate GISTSearchItem for each one.
|
||||
*/
|
||||
|
||||
/* Individual heap tuple to be visited */
|
||||
typedef struct GISTSearchHeapItem
|
||||
{
|
||||
ItemPointerData heapPtr;
|
||||
bool recheck; /* T if quals must be rechecked */
|
||||
} GISTSearchHeapItem;
|
||||
|
||||
/* Unvisited item, either index page or heap tuple */
|
||||
typedef struct GISTSearchItem
|
||||
{
|
||||
struct GISTSearchItem *next; /* list link */
|
||||
BlockNumber blkno; /* index page number, or InvalidBlockNumber */
|
||||
union
|
||||
{
|
||||
GistNSN parentlsn; /* parent page's LSN, if index page */
|
||||
/* we must store parentlsn to detect whether a split occurred */
|
||||
GISTSearchHeapItem heap; /* heap info, if heap tuple */
|
||||
} data;
|
||||
} GISTSearchItem;
|
||||
|
||||
#define GISTSearchItemIsHeap(item) ((item).blkno == InvalidBlockNumber)
|
||||
|
||||
/*
|
||||
* Within a GISTSearchTreeItem's chain, heap items always appear before
|
||||
* index-page items, since we want to visit heap items first. lastHeap points
|
||||
* to the last heap item in the chain, or is NULL if there are none.
|
||||
*/
|
||||
typedef struct GISTSearchTreeItem
|
||||
{
|
||||
RBNode rbnode; /* this is an RBTree item */
|
||||
GISTSearchItem *head; /* first chain member */
|
||||
GISTSearchItem *lastHeap; /* last heap-tuple member, if any */
|
||||
double distances[1]; /* array with numberOfOrderBys entries */
|
||||
} GISTSearchTreeItem;
|
||||
|
||||
#define GSTIHDRSZ offsetof(GISTSearchTreeItem, distances)
|
||||
|
||||
/*
|
||||
* GISTScanOpaqueData: private state for a scan of a GiST index
|
||||
*/
|
||||
typedef struct GISTScanOpaqueData
|
||||
{
|
||||
GISTSearchStack *stack;
|
||||
GISTSearchStack *markstk;
|
||||
GISTSTATE *giststate; /* index information, see above */
|
||||
RBTree *queue; /* queue of unvisited items */
|
||||
MemoryContext queueCxt; /* context holding the queue */
|
||||
MemoryContext tempCxt; /* workspace context for calling functions */
|
||||
bool qual_ok; /* false if qual can never be satisfied */
|
||||
GISTSTATE *giststate;
|
||||
MemoryContext tempCxt;
|
||||
Buffer curbuf;
|
||||
ItemPointerData curpos;
|
||||
bool firstCall; /* true until first gistgettuple call */
|
||||
|
||||
ItemResult pageData[BLCKSZ / sizeof(IndexTupleData)];
|
||||
OffsetNumber nPageData;
|
||||
OffsetNumber curPageData;
|
||||
GISTSearchTreeItem *curTreeItem; /* current queue item, if any */
|
||||
|
||||
/* pre-allocated workspace arrays */
|
||||
GISTSearchTreeItem *tmpTreeItem; /* workspace to pass to rb_insert */
|
||||
double *distances; /* workspace for computeKeyTupleDistance */
|
||||
|
||||
/* In a non-ordered search, returnable heap items are stored here: */
|
||||
GISTSearchHeapItem pageData[BLCKSZ / sizeof(IndexTupleData)];
|
||||
OffsetNumber nPageData; /* number of valid items in array */
|
||||
OffsetNumber curPageData; /* next item to return */
|
||||
} GISTScanOpaqueData;
|
||||
|
||||
typedef GISTScanOpaqueData *GISTScanOpaque;
|
||||
|
||||
|
||||
/* XLog stuff */
|
||||
|
||||
#define XLOG_GIST_PAGE_UPDATE 0x00
|
||||
|
@ -53,6 +53,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 201012021
|
||||
#define CATALOG_VERSION_NO 201012031
|
||||
|
||||
#endif
|
||||
|
@ -117,7 +117,7 @@ DESCR("b-tree index access method");
|
||||
DATA(insert OID = 405 ( hash 1 1 f f t f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
|
||||
DESCR("hash index access method");
|
||||
#define HASH_AM_OID 405
|
||||
DATA(insert OID = 783 ( gist 0 7 f f f f t t t t t t 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
|
||||
DATA(insert OID = 783 ( gist 0 8 f t f f t t t t t t 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
|
||||
DESCR("GiST index access method");
|
||||
#define GIST_AM_OID 783
|
||||
DATA(insert OID = 2742 ( gin 0 5 f f f f t t f f t f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
|
||||
|
@ -604,6 +604,7 @@ DATA(insert ( 1029 600 600 1 s 507 783 0 ));
|
||||
DATA(insert ( 1029 600 600 5 s 508 783 0 ));
|
||||
DATA(insert ( 1029 600 600 10 s 509 783 0 ));
|
||||
DATA(insert ( 1029 600 600 6 s 510 783 0 ));
|
||||
DATA(insert ( 1029 600 600 15 o 517 783 1970 ));
|
||||
DATA(insert ( 1029 603 600 27 s 433 783 0 ));
|
||||
DATA(insert ( 1029 600 603 28 s 511 783 0 ));
|
||||
DATA(insert ( 1029 604 600 47 s 757 783 0 ));
|
||||
|
@ -205,6 +205,7 @@ DATA(insert ( 1029 600 600 4 2580 ));
|
||||
DATA(insert ( 1029 600 600 5 2581 ));
|
||||
DATA(insert ( 1029 600 600 6 2582 ));
|
||||
DATA(insert ( 1029 600 600 7 2584 ));
|
||||
DATA(insert ( 1029 600 600 8 3064 ));
|
||||
|
||||
|
||||
/* gin */
|
||||
|
@ -4325,7 +4325,9 @@ DATA(insert OID = 2592 ( gist_circle_compress PGNSP PGUID 12 1 0 0 f f f t f i
|
||||
DESCR("GiST support");
|
||||
DATA(insert OID = 1030 ( gist_point_compress PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gist_point_compress _null_ _null_ _null_ ));
|
||||
DESCR("GiST support");
|
||||
DATA(insert OID = 2179 ( gist_point_consistent PGNSP PGUID 12 1 0 0 f f f t f i 5 0 16 "2281 603 23 26 2281" _null_ _null_ _null_ _null_ gist_point_consistent _null_ _null_ _null_ ));
|
||||
DATA(insert OID = 2179 ( gist_point_consistent PGNSP PGUID 12 1 0 0 f f f t f i 5 0 16 "2281 600 23 26 2281" _null_ _null_ _null_ _null_ gist_point_consistent _null_ _null_ _null_ ));
|
||||
DESCR("GiST support");
|
||||
DATA(insert OID = 3064 ( gist_point_distance PGNSP PGUID 12 1 0 0 f f f t f i 4 0 701 "2281 600 23 26" _null_ _null_ _null_ _null_ gist_point_distance _null_ _null_ _null_ ));
|
||||
DESCR("GiST support");
|
||||
|
||||
/* GIN */
|
||||
|
@ -424,6 +424,7 @@ extern Datum gist_circle_compress(PG_FUNCTION_ARGS);
|
||||
extern Datum gist_circle_consistent(PG_FUNCTION_ARGS);
|
||||
extern Datum gist_point_compress(PG_FUNCTION_ARGS);
|
||||
extern Datum gist_point_consistent(PG_FUNCTION_ARGS);
|
||||
extern Datum gist_point_distance(PG_FUNCTION_ARGS);
|
||||
|
||||
/* geo_selfuncs.c */
|
||||
extern Datum areasel(PG_FUNCTION_ARGS);
|
||||
|
@ -51,6 +51,7 @@ CREATE INDEX onek2_stu1_prtl ON onek2 USING btree(stringu1 name_ops)
|
||||
CREATE INDEX grect2ind ON fast_emp4000 USING gist (home_base);
|
||||
CREATE INDEX gpolygonind ON polygon_tbl USING gist (f1);
|
||||
CREATE INDEX gcircleind ON circle_tbl USING gist (f1);
|
||||
INSERT INTO POINT_TBL(f1) VALUES (NULL);
|
||||
CREATE INDEX gpointind ON point_tbl USING gist (f1);
|
||||
CREATE TEMP TABLE gpolygon_tbl AS
|
||||
SELECT polygon(home_base) AS f1 FROM slow_emp4000;
|
||||
@ -60,6 +61,7 @@ CREATE TEMP TABLE gcircle_tbl AS
|
||||
SELECT circle(home_base) AS f1 FROM slow_emp4000;
|
||||
CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
|
||||
CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1);
|
||||
-- get non-indexed results for comparison purposes
|
||||
SET enable_seqscan = ON;
|
||||
SET enable_indexscan = OFF;
|
||||
SET enable_bitmapscan = OFF;
|
||||
@ -167,6 +169,44 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
------------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(-5,-12)
|
||||
(5.1,34.5)
|
||||
|
||||
(7 rows)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 IS NULL;
|
||||
f1
|
||||
----
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
------------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(-5,-12)
|
||||
(5.1,34.5)
|
||||
(6 rows)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
---------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(4 rows)
|
||||
|
||||
SET enable_seqscan = OFF;
|
||||
SET enable_indexscan = ON;
|
||||
SET enable_bitmapscan = ON;
|
||||
@ -435,6 +475,102 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
|
||||
1
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
|
||||
QUERY PLAN
|
||||
-----------------------------------------
|
||||
Index Scan using gpointind on point_tbl
|
||||
Order By: (f1 <-> '(0,1)'::point)
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
------------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(-5,-12)
|
||||
(5.1,34.5)
|
||||
|
||||
(7 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 IS NULL;
|
||||
QUERY PLAN
|
||||
-----------------------------------------
|
||||
Index Scan using gpointind on point_tbl
|
||||
Index Cond: (f1 IS NULL)
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 IS NULL;
|
||||
f1
|
||||
----
|
||||
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
|
||||
QUERY PLAN
|
||||
-----------------------------------------
|
||||
Index Scan using gpointind on point_tbl
|
||||
Index Cond: (f1 IS NOT NULL)
|
||||
Order By: (f1 <-> '(0,1)'::point)
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
------------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(-5,-12)
|
||||
(5.1,34.5)
|
||||
(6 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
QUERY PLAN
|
||||
------------------------------------------------
|
||||
Index Scan using gpointind on point_tbl
|
||||
Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
|
||||
Order By: (f1 <-> '(0,1)'::point)
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
---------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(4 rows)
|
||||
|
||||
SET enable_seqscan = OFF;
|
||||
SET enable_indexscan = OFF;
|
||||
SET enable_bitmapscan = ON;
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
QUERY PLAN
|
||||
------------------------------------------------------------
|
||||
Sort
|
||||
Sort Key: ((f1 <-> '(0,1)'::point))
|
||||
-> Bitmap Heap Scan on point_tbl
|
||||
Recheck Cond: (f1 <@ '(10,10),(-10,-10)'::box)
|
||||
-> Bitmap Index Scan on gpointind
|
||||
Index Cond: (f1 <@ '(10,10),(-10,-10)'::box)
|
||||
(6 rows)
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
f1
|
||||
---------
|
||||
(0,0)
|
||||
(-3,4)
|
||||
(-10,0)
|
||||
(10,10)
|
||||
(4 rows)
|
||||
|
||||
RESET enable_seqscan;
|
||||
RESET enable_indexscan;
|
||||
RESET enable_bitmapscan;
|
||||
|
@ -991,6 +991,7 @@ ORDER BY 1, 2, 3;
|
||||
783 | 12 | |&>
|
||||
783 | 13 | ~
|
||||
783 | 14 | @
|
||||
783 | 15 | <->
|
||||
783 | 27 | @>
|
||||
783 | 28 | <@
|
||||
783 | 47 | @>
|
||||
@ -1003,7 +1004,7 @@ ORDER BY 1, 2, 3;
|
||||
2742 | 2 | @@@
|
||||
2742 | 3 | <@
|
||||
2742 | 4 | =
|
||||
(39 rows)
|
||||
(40 rows)
|
||||
|
||||
-- Check that all opclass search operators have selectivity estimators.
|
||||
-- This is not absolutely required, but it seems a reasonable thing
|
||||
@ -1136,11 +1137,11 @@ WHERE p1.amprocfamily = p3.oid AND p3.opfmethod = p2.oid AND
|
||||
|
||||
-- Detect missing pg_amproc entries: should have as many support functions
|
||||
-- as AM expects for each datatype combination supported by the opfamily.
|
||||
-- GIN is a special case because it has an optional support function.
|
||||
-- GIST/GIN are special cases because each has an optional support function.
|
||||
SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
|
||||
FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
|
||||
WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
|
||||
p1.amname <> 'gin' AND
|
||||
p1.amname <> 'gist' AND p1.amname <> 'gin' AND
|
||||
p1.amsupport != (SELECT count(*) FROM pg_amproc AS p4
|
||||
WHERE p4.amprocfamily = p2.oid AND
|
||||
p4.amproclefttype = p3.amproclefttype AND
|
||||
@ -1149,26 +1150,27 @@ WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
|
||||
--------+---------+----------------+-----------------
|
||||
(0 rows)
|
||||
|
||||
-- Similar check for GIN, allowing one optional proc
|
||||
-- Similar check for GIST/GIN, allowing one optional proc
|
||||
SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
|
||||
FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
|
||||
WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
|
||||
p1.amname = 'gin' AND
|
||||
p1.amsupport - 1 > (SELECT count(*) FROM pg_amproc AS p4
|
||||
WHERE p4.amprocfamily = p2.oid AND
|
||||
p4.amproclefttype = p3.amproclefttype AND
|
||||
p4.amprocrighttype = p3.amprocrighttype);
|
||||
(p1.amname = 'gist' OR p1.amname = 'gin') AND
|
||||
(SELECT count(*) FROM pg_amproc AS p4
|
||||
WHERE p4.amprocfamily = p2.oid AND
|
||||
p4.amproclefttype = p3.amproclefttype AND
|
||||
p4.amprocrighttype = p3.amprocrighttype)
|
||||
NOT IN (p1.amsupport, p1.amsupport - 1);
|
||||
amname | opfname | amproclefttype | amprocrighttype
|
||||
--------+---------+----------------+-----------------
|
||||
(0 rows)
|
||||
|
||||
-- Also, check if there are any pg_opclass entries that don't seem to have
|
||||
-- pg_amproc support. Again, GIN has to be checked separately.
|
||||
-- pg_amproc support. Again, GIST/GIN have to be checked specially.
|
||||
SELECT amname, opcname, count(*)
|
||||
FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
|
||||
LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
|
||||
amproclefttype = amprocrighttype AND amproclefttype = opcintype
|
||||
WHERE am.amname <> 'gin'
|
||||
WHERE am.amname <> 'gist' AND am.amname <> 'gin'
|
||||
GROUP BY amname, amsupport, opcname, amprocfamily
|
||||
HAVING count(*) != amsupport OR amprocfamily IS NULL;
|
||||
amname | opcname | count
|
||||
@ -1179,9 +1181,10 @@ SELECT amname, opcname, count(*)
|
||||
FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
|
||||
LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
|
||||
amproclefttype = amprocrighttype AND amproclefttype = opcintype
|
||||
WHERE am.amname = 'gin'
|
||||
WHERE am.amname = 'gist' OR am.amname = 'gin'
|
||||
GROUP BY amname, amsupport, opcname, amprocfamily
|
||||
HAVING count(*) < amsupport - 1 OR amprocfamily IS NULL;
|
||||
HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
|
||||
OR amprocfamily IS NULL;
|
||||
amname | opcname | count
|
||||
--------+---------+-------
|
||||
(0 rows)
|
||||
|
@ -76,6 +76,8 @@ CREATE INDEX gpolygonind ON polygon_tbl USING gist (f1);
|
||||
|
||||
CREATE INDEX gcircleind ON circle_tbl USING gist (f1);
|
||||
|
||||
INSERT INTO POINT_TBL(f1) VALUES (NULL);
|
||||
|
||||
CREATE INDEX gpointind ON point_tbl USING gist (f1);
|
||||
|
||||
CREATE TEMP TABLE gpolygon_tbl AS
|
||||
@ -90,6 +92,8 @@ CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
|
||||
|
||||
CREATE INDEX ggcircleind ON gcircle_tbl USING gist (f1);
|
||||
|
||||
-- get non-indexed results for comparison purposes
|
||||
|
||||
SET enable_seqscan = ON;
|
||||
SET enable_indexscan = OFF;
|
||||
SET enable_bitmapscan = OFF;
|
||||
@ -130,6 +134,14 @@ SELECT count(*) FROM point_tbl p WHERE p.f1 >^ '(0.0, 0.0)';
|
||||
|
||||
SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
|
||||
|
||||
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 IS NULL;
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
|
||||
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
|
||||
SET enable_seqscan = OFF;
|
||||
SET enable_indexscan = ON;
|
||||
SET enable_bitmapscan = ON;
|
||||
@ -206,6 +218,30 @@ EXPLAIN (COSTS OFF)
|
||||
SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
|
||||
SELECT count(*) FROM point_tbl p WHERE p.f1 ~= '(-5, -12)';
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
|
||||
SELECT * FROM point_tbl ORDER BY f1 <-> '0,1';
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 IS NULL;
|
||||
SELECT * FROM point_tbl WHERE f1 IS NULL;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
|
||||
SELECT * FROM point_tbl WHERE f1 IS NOT NULL ORDER BY f1 <-> '0,1';
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
|
||||
SET enable_seqscan = OFF;
|
||||
SET enable_indexscan = OFF;
|
||||
SET enable_bitmapscan = ON;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0,1';
|
||||
|
||||
RESET enable_seqscan;
|
||||
RESET enable_indexscan;
|
||||
RESET enable_bitmapscan;
|
||||
|
@ -888,36 +888,37 @@ WHERE p1.amprocfamily = p3.oid AND p3.opfmethod = p2.oid AND
|
||||
|
||||
-- Detect missing pg_amproc entries: should have as many support functions
|
||||
-- as AM expects for each datatype combination supported by the opfamily.
|
||||
-- GIN is a special case because it has an optional support function.
|
||||
-- GIST/GIN are special cases because each has an optional support function.
|
||||
|
||||
SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
|
||||
FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
|
||||
WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
|
||||
p1.amname <> 'gin' AND
|
||||
p1.amname <> 'gist' AND p1.amname <> 'gin' AND
|
||||
p1.amsupport != (SELECT count(*) FROM pg_amproc AS p4
|
||||
WHERE p4.amprocfamily = p2.oid AND
|
||||
p4.amproclefttype = p3.amproclefttype AND
|
||||
p4.amprocrighttype = p3.amprocrighttype);
|
||||
|
||||
-- Similar check for GIN, allowing one optional proc
|
||||
-- Similar check for GIST/GIN, allowing one optional proc
|
||||
|
||||
SELECT p1.amname, p2.opfname, p3.amproclefttype, p3.amprocrighttype
|
||||
FROM pg_am AS p1, pg_opfamily AS p2, pg_amproc AS p3
|
||||
WHERE p2.opfmethod = p1.oid AND p3.amprocfamily = p2.oid AND
|
||||
p1.amname = 'gin' AND
|
||||
p1.amsupport - 1 > (SELECT count(*) FROM pg_amproc AS p4
|
||||
WHERE p4.amprocfamily = p2.oid AND
|
||||
p4.amproclefttype = p3.amproclefttype AND
|
||||
p4.amprocrighttype = p3.amprocrighttype);
|
||||
(p1.amname = 'gist' OR p1.amname = 'gin') AND
|
||||
(SELECT count(*) FROM pg_amproc AS p4
|
||||
WHERE p4.amprocfamily = p2.oid AND
|
||||
p4.amproclefttype = p3.amproclefttype AND
|
||||
p4.amprocrighttype = p3.amprocrighttype)
|
||||
NOT IN (p1.amsupport, p1.amsupport - 1);
|
||||
|
||||
-- Also, check if there are any pg_opclass entries that don't seem to have
|
||||
-- pg_amproc support. Again, GIN has to be checked separately.
|
||||
-- pg_amproc support. Again, GIST/GIN have to be checked specially.
|
||||
|
||||
SELECT amname, opcname, count(*)
|
||||
FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
|
||||
LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
|
||||
amproclefttype = amprocrighttype AND amproclefttype = opcintype
|
||||
WHERE am.amname <> 'gin'
|
||||
WHERE am.amname <> 'gist' AND am.amname <> 'gin'
|
||||
GROUP BY amname, amsupport, opcname, amprocfamily
|
||||
HAVING count(*) != amsupport OR amprocfamily IS NULL;
|
||||
|
||||
@ -925,9 +926,10 @@ SELECT amname, opcname, count(*)
|
||||
FROM pg_am am JOIN pg_opclass op ON opcmethod = am.oid
|
||||
LEFT JOIN pg_amproc p ON amprocfamily = opcfamily AND
|
||||
amproclefttype = amprocrighttype AND amproclefttype = opcintype
|
||||
WHERE am.amname = 'gin'
|
||||
WHERE am.amname = 'gist' OR am.amname = 'gin'
|
||||
GROUP BY amname, amsupport, opcname, amprocfamily
|
||||
HAVING count(*) < amsupport - 1 OR amprocfamily IS NULL;
|
||||
HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
|
||||
OR amprocfamily IS NULL;
|
||||
|
||||
-- Unfortunately, we can't check the amproc link very well because the
|
||||
-- signature of the function may be different for different support routines
|
||||
|
Loading…
Reference in New Issue
Block a user