1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-26 12:21:12 +03:00

Add support for index-only scans in GiST.

This adds a new GiST opclass method, 'fetch', which is used to reconstruct
the original Datum from the value stored in the index. Also, the 'canreturn'
index AM interface function gains a new 'attno' argument. That makes it
possible to use index-only scans on a multi-column index where some of the
opclasses support index-only scans but some do not.

This patch adds support in the box and point opclasses. Other opclasses
can added later as follow-on patches (btree_gist would be particularly
interesting).

Anastasia Lubennikova, with additional fixes and modifications by me.
This commit is contained in:
Heikki Linnakangas
2015-03-26 19:12:00 +02:00
parent 8fa393a6d7
commit d04c8ed904
24 changed files with 575 additions and 73 deletions

View File

@ -1404,6 +1404,14 @@ initGISTstate(Relation index)
else
giststate->distanceFn[i].fn_oid = InvalidOid;
/* opclasses are not required to provide a Fetch method */
if (OidIsValid(index_getprocid(index, i + 1, GIST_FETCH_PROC)))
fmgr_info_copy(&(giststate->fetchFn[i]),
index_getprocinfo(index, i + 1, GIST_FETCH_PROC),
scanCxt);
else
giststate->fetchFn[i].fn_oid = InvalidOid;
/*
* If the index column has a specified collation, we should honor that
* while doing comparisons. However, we may have a collatable storage

View File

@ -228,7 +228,9 @@ gistindex_keytest(IndexScanDesc scan,
* 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.
* heap tuple TIDs are pushed into individual search queue items. In an
* index-only scan, reconstructed index tuples are returned along with the
* TIDs.
*
* 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
@ -239,6 +241,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
TIDBitmap *tbm, int64 *ntids)
{
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
GISTSTATE *giststate = so->giststate;
Relation r = scan->indexRelation;
Buffer buffer;
Page page;
GISTPageOpaque opaque;
@ -288,6 +292,8 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
}
so->nPageData = so->curPageData = 0;
if (so->pageDataCxt)
MemoryContextReset(so->pageDataCxt);
/*
* check all tuples on page
@ -326,10 +332,21 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
{
/*
* Non-ordered scan, so report heap tuples in so->pageData[]
* Non-ordered scan, so report tuples in so->pageData[]
*/
so->pageData[so->nPageData].heapPtr = it->t_tid;
so->pageData[so->nPageData].recheck = recheck;
/*
* In an index-only scan, also fetch the data from the tuple.
*/
if (scan->xs_want_itup)
{
oldcxt = MemoryContextSwitchTo(so->pageDataCxt);
so->pageData[so->nPageData].ftup =
gistFetchTuple(giststate, r, it);
MemoryContextSwitchTo(oldcxt);
}
so->nPageData++;
}
else
@ -352,6 +369,12 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
item->blkno = InvalidBlockNumber;
item->data.heap.heapPtr = it->t_tid;
item->data.heap.recheck = recheck;
/*
* In an index-only scan, also fetch the data from the tuple.
*/
if (scan->xs_want_itup)
item->data.heap.ftup = gistFetchTuple(giststate, r, it);
}
else
{
@ -412,6 +435,13 @@ getNextNearest(IndexScanDesc scan)
GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
bool res = false;
if (scan->xs_itup)
{
/* free previously returned tuple */
pfree(scan->xs_itup);
scan->xs_itup = NULL;
}
do
{
GISTSearchItem *item = getNextGISTSearchItem(so);
@ -424,6 +454,10 @@ getNextNearest(IndexScanDesc scan)
/* found a heap item at currently minimal distance */
scan->xs_ctup.t_self = item->data.heap.heapPtr;
scan->xs_recheck = item->data.heap.recheck;
/* in an index-only scan, also return the reconstructed tuple. */
if (scan->xs_want_itup)
scan->xs_itup = item->data.heap.ftup;
res = true;
}
else
@ -465,6 +499,8 @@ gistgettuple(PG_FUNCTION_ARGS)
so->firstCall = false;
so->curPageData = so->nPageData = 0;
if (so->pageDataCxt)
MemoryContextReset(so->pageDataCxt);
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
@ -483,10 +519,17 @@ gistgettuple(PG_FUNCTION_ARGS)
{
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;
/* in an index-only scan, also return the reconstructed tuple */
if (scan->xs_want_itup)
scan->xs_itup = so->pageData[so->curPageData].ftup;
so->curPageData++;
PG_RETURN_BOOL(true);
}
@ -533,6 +576,8 @@ gistgetbitmap(PG_FUNCTION_ARGS)
/* Begin the scan by processing the root page */
so->curPageData = so->nPageData = 0;
if (so->pageDataCxt)
MemoryContextReset(so->pageDataCxt);
fakeItem.blkno = GIST_ROOT_BLKNO;
memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
@ -558,3 +603,20 @@ gistgetbitmap(PG_FUNCTION_ARGS)
PG_RETURN_INT64(ntids);
}
/*
* Can we do index-only scans on the given index column?
*
* Opclasses that implement a fetch function support index-only scans.
*/
Datum
gistcanreturn(PG_FUNCTION_ARGS)
{
Relation index = (Relation) PG_GETARG_POINTER(0);
int attno = PG_GETARG_INT32(1);
if (OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)))
PG_RETURN_BOOL(true);
else
PG_RETURN_BOOL(false);
}

View File

@ -151,6 +151,16 @@ gist_box_decompress(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
/*
* GiST Fetch method for boxes
* do not do anything --- we just return the stored box as is.
*/
Datum
gist_box_fetch(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
/*
* The GiST Penalty method for boxes (also used for points)
*
@ -1186,6 +1196,33 @@ gist_point_compress(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entry);
}
/*
* GiST Fetch method for point
*
* Get point coordinates from its bounding box coordinates and form new
* gistentry.
*/
Datum
gist_point_fetch(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
BOX *in = DatumGetBoxP(entry->key);
Point *r;
GISTENTRY *retval;
retval = palloc(sizeof(GISTENTRY));
r = (Point *) palloc(sizeof(Point));
r->x = in->high.x;
r->y = in->high.y;
gistentryinit(*retval, PointerGetDatum(r),
entry->rel, entry->page,
entry->offset, FALSE);
PG_RETURN_POINTER(retval);
}
#define point_point_distance(p1,p2) \
DatumGetFloat8(DirectFunctionCall2(point_distance, \
PointPGetDatum(p1), PointPGetDatum(p2)))

View File

@ -88,6 +88,13 @@ gistbeginscan(PG_FUNCTION_ARGS)
scan->opaque = so;
/*
* All fields required for index-only scans are null until gistrescan.
* However, we set up scan->xs_itupdesc whether we'll need it or not,
* since that's cheap.
*/
scan->xs_itupdesc = RelationGetDescr(r);
MemoryContextSwitchTo(oldCxt);
PG_RETURN_POINTER(scan);
@ -141,6 +148,17 @@ gistrescan(PG_FUNCTION_ARGS)
first_time = false;
}
/*
* If we're doing an index-only scan, also create a memory context to hold
* the returned tuples.
*/
if (scan->xs_want_itup && so->pageDataCxt == NULL)
so->pageDataCxt = AllocSetContextCreate(so->giststate->scanCxt,
"GiST page data context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/* create new, empty RBTree for search queue */
oldCxt = MemoryContextSwitchTo(so->queueCxt);
so->queue = pairingheap_allocate(pairingheap_GISTSearchItem_cmp, scan);

View File

@ -294,8 +294,9 @@ gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
for (i = 0; i < r->rd_att->natts; i++)
{
Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
Datum datum;
datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
gistdentryinit(giststate, i, &attdata[i],
datum, r, p, o,
FALSE, isnull[i]);
@ -598,6 +599,67 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
return res;
}
/*
* initialize a GiST entry with fetched value in key field
*/
static Datum
gistFetchAtt(GISTSTATE *giststate, int nkey, Datum k, Relation r)
{
GISTENTRY fentry;
GISTENTRY *fep;
gistentryinit(fentry, k, r, NULL, (OffsetNumber) 0, false);
fep = (GISTENTRY *)
DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey],
giststate->supportCollation[nkey],
PointerGetDatum(&fentry)));
/* fetchFn set 'key', return it to the caller */
return fep->key;
}
/*
* Fetch all keys in tuple.
* returns new IndexTuple that contains GISTENTRY with fetched data
*/
IndexTuple
gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple)
{
MemoryContext oldcxt = MemoryContextSwitchTo(giststate->tempCxt);
Datum fetchatt[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
int i;
for (i = 0; i < r->rd_att->natts; i++)
{
Datum datum;
datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
if (giststate->fetchFn[i].fn_oid != InvalidOid)
{
if (!isnull[i])
fetchatt[i] = gistFetchAtt(giststate, i, datum, r);
else
fetchatt[i] = (Datum) 0;
}
else
{
/*
* Index-only scans not supported for this column. Since the
* planner chose an index-only scan anyway, it is not interested
* in this column, and we can replace it with a NULL.
*/
isnull[i] = true;
fetchatt[i] = (Datum) 0;
}
}
MemoryContextSwitchTo(oldcxt);
return index_form_tuple(giststate->tupdesc, fetchatt, isnull);
}
float
gistpenalty(GISTSTATE *giststate, int attno,
GISTENTRY *orig, bool isNullOrig,