Teach SP-GiST to do index-only scans.

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

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

View File

@ -145,6 +145,7 @@ typedef struct spgConfigOut
{ {
Oid prefixType; /* Data type of inner-tuple prefixes */ Oid prefixType; /* Data type of inner-tuple prefixes */
Oid labelType; /* Data type of inner-tuple node labels */ Oid labelType; /* Data type of inner-tuple node labels */
bool canReturnData; /* Opclass can reconstruct original data */
bool longValuesOK; /* Opclass can cope with values > 1 page */ bool longValuesOK; /* Opclass can cope with values > 1 page */
} spgConfigOut; } spgConfigOut;
</programlisting> </programlisting>
@ -159,6 +160,8 @@ typedef struct spgConfigOut
<structfield>prefixType</> can be set to <literal>VOIDOID</>. <structfield>prefixType</> can be set to <literal>VOIDOID</>.
Likewise, for operator classes that do not use node labels, Likewise, for operator classes that do not use node labels,
<structfield>labelType</> can be set to <literal>VOIDOID</>. <structfield>labelType</> can be set to <literal>VOIDOID</>.
<structfield>canReturnData</> should be set true if the operator class
is capable of reconstructing the originally-supplied index value.
<structfield>longValuesOK</> should be set true only when the <structfield>longValuesOK</> should be set true only when the
<structfield>attType</> is of variable length and the operator <structfield>attType</> is of variable length and the operator
class is capable of segmenting long values by repeated suffixing class is capable of segmenting long values by repeated suffixing
@ -441,6 +444,7 @@ typedef struct spgInnerConsistentIn
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
int level; /* current level (counting from zero) */ int level; /* current level (counting from zero) */
bool returnData; /* original data must be returned? */
/* Data from current inner tuple */ /* Data from current inner tuple */
bool allTheSame; /* tuple is marked all-the-same? */ bool allTheSame; /* tuple is marked all-the-same? */
@ -467,6 +471,9 @@ typedef struct spgInnerConsistentOut
parent level. parent level.
<structfield>level</> is the current inner tuple's level, starting at <structfield>level</> is the current inner tuple's level, starting at
zero for the root level. zero for the root level.
<structfield>returnData</> is <literal>true</> if reconstructed data is
required for this query; this will only be so if the
<function>config</> function asserted <structfield>canReturnData</>.
<structfield>allTheSame</> is true if the current inner tuple is <structfield>allTheSame</> is true if the current inner tuple is
marked <quote>all-the-same</>; in this case all the nodes have the marked <quote>all-the-same</>; in this case all the nodes have the
same label (if any) and so either all or none of them match the query same label (if any) and so either all or none of them match the query
@ -525,12 +532,14 @@ typedef struct spgLeafConsistentIn
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
int level; /* current level (counting from zero) */ int level; /* current level (counting from zero) */
bool returnData; /* original data must be returned? */
Datum leafDatum; /* datum in leaf tuple */ Datum leafDatum; /* datum in leaf tuple */
} spgLeafConsistentIn; } spgLeafConsistentIn;
typedef struct spgLeafConsistentOut typedef struct spgLeafConsistentOut
{ {
Datum leafValue; /* reconstructed original data, if any */
bool recheck; /* set true if operator must be rechecked */ bool recheck; /* set true if operator must be rechecked */
} spgLeafConsistentOut; } spgLeafConsistentOut;
</programlisting> </programlisting>
@ -543,6 +552,9 @@ typedef struct spgLeafConsistentOut
parent level. parent level.
<structfield>level</> is the current leaf tuple's level, starting at <structfield>level</> is the current leaf tuple's level, starting at
zero for the root level. zero for the root level.
<structfield>returnData</> is <literal>true</> if reconstructed data is
required for this query; this will only be so if the
<function>config</> function asserted <structfield>canReturnData</>.
<structfield>leafDatum</> is the key value stored in the current <structfield>leafDatum</> is the key value stored in the current
leaf tuple. leaf tuple.
</para> </para>
@ -550,6 +562,9 @@ typedef struct spgLeafConsistentOut
<para> <para>
The function must return <literal>true</> if the leaf tuple matches the The function must return <literal>true</> if the leaf tuple matches the
query, or <literal>false</> if not. In the <literal>true</> case, query, or <literal>false</> if not. In the <literal>true</> case,
if <structfield>returnData</> is <literal>true</> then
<structfield>leafValue</> must be set to the value originally supplied
to be indexed for this leaf tuple. Also,
<structfield>recheck</> may be set to <literal>true</> if the match <structfield>recheck</> may be set to <literal>true</> if the match
is uncertain and so the operator must be re-applied to the actual heap is uncertain and so the operator must be re-applied to the actual heap
tuple to verify the match. tuple to verify the match.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,6 +43,7 @@ typedef struct spgConfigOut
{ {
Oid prefixType; /* Data type of inner-tuple prefixes */ Oid prefixType; /* Data type of inner-tuple prefixes */
Oid labelType; /* Data type of inner-tuple node labels */ Oid labelType; /* Data type of inner-tuple node labels */
bool canReturnData; /* Opclass can reconstruct original data */
bool longValuesOK; /* Opclass can cope with values > 1 page */ bool longValuesOK; /* Opclass can cope with values > 1 page */
} spgConfigOut; } spgConfigOut;
@ -132,6 +133,7 @@ typedef struct spgInnerConsistentIn
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
int level; /* current level (counting from zero) */ int level; /* current level (counting from zero) */
bool returnData; /* original data must be returned? */
/* Data from current inner tuple */ /* Data from current inner tuple */
bool allTheSame; /* tuple is marked all-the-same? */ bool allTheSame; /* tuple is marked all-the-same? */
@ -159,12 +161,14 @@ typedef struct spgLeafConsistentIn
Datum reconstructedValue; /* value reconstructed at parent */ Datum reconstructedValue; /* value reconstructed at parent */
int level; /* current level (counting from zero) */ int level; /* current level (counting from zero) */
bool returnData; /* original data must be returned? */
Datum leafDatum; /* datum in leaf tuple */ Datum leafDatum; /* datum in leaf tuple */
} spgLeafConsistentIn; } spgLeafConsistentIn;
typedef struct spgLeafConsistentOut typedef struct spgLeafConsistentOut
{ {
Datum leafValue; /* reconstructed original data, if any */
bool recheck; /* set true if operator must be rechecked */ bool recheck; /* set true if operator must be rechecked */
} spgLeafConsistentOut; } spgLeafConsistentOut;

View File

@ -71,11 +71,11 @@ typedef struct SpGistLastUsedPage
int freeSpace; /* its free space (could be obsolete!) */ int freeSpace; /* its free space (could be obsolete!) */
} SpGistLastUsedPage; } SpGistLastUsedPage;
typedef struct SpGistCache typedef struct SpGistLUPCache
{ {
SpGistLastUsedPage innerPage[3]; /* one per triple-parity group */ SpGistLastUsedPage innerPage[3]; /* one per triple-parity group */
SpGistLastUsedPage leafPage; SpGistLastUsedPage leafPage;
} SpGistCache; } SpGistLUPCache;
/* /*
* metapage * metapage
@ -83,7 +83,7 @@ typedef struct SpGistCache
typedef struct SpGistMetaPageData typedef struct SpGistMetaPageData
{ {
uint32 magicNumber; /* for identity cross-check */ uint32 magicNumber; /* for identity cross-check */
SpGistCache lastUsedPages; /* shared storage of last-used info */ SpGistLUPCache lastUsedPages; /* shared storage of last-used info */
} SpGistMetaPageData; } SpGistMetaPageData;
#define SPGIST_MAGIC_NUMBER (0xBA0BABED) #define SPGIST_MAGIC_NUMBER (0xBA0BABED)
@ -112,12 +112,6 @@ typedef struct SpGistState
SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */ SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */
SpGistTypeDesc attLabelType; /* type of node label values */ SpGistTypeDesc attLabelType; /* type of node label values */
/* lookup data for the opclass support functions, except config */
FmgrInfo chooseFn;
FmgrInfo picksplitFn;
FmgrInfo innerConsistentFn;
FmgrInfo leafConsistentFn;
char *deadTupleStorage; /* workspace for spgFormDeadTuple */ char *deadTupleStorage; /* workspace for spgFormDeadTuple */
TransactionId myXid; /* XID to use when creating a redirect tuple */ TransactionId myXid; /* XID to use when creating a redirect tuple */
@ -144,10 +138,13 @@ typedef struct SpGistScanOpaqueData
int64 ntids; /* number of TIDs passed to bitmap */ int64 ntids; /* number of TIDs passed to bitmap */
/* These fields are only used in amgettuple scans: */ /* These fields are only used in amgettuple scans: */
bool want_itup; /* are we reconstructing tuples? */
TupleDesc indexTupDesc; /* if so, tuple descriptor for them */
int nPtrs; /* number of TIDs found on current page */ int nPtrs; /* number of TIDs found on current page */
int iPtr; /* index for scanning through same */ int iPtr; /* index for scanning through same */
ItemPointerData heapPtrs[MaxIndexTuplesPerPage]; /* TIDs from cur page */ ItemPointerData heapPtrs[MaxIndexTuplesPerPage]; /* TIDs from cur page */
bool recheck[MaxIndexTuplesPerPage]; /* their recheck flags */ bool recheck[MaxIndexTuplesPerPage]; /* their recheck flags */
IndexTuple indexTups[MaxIndexTuplesPerPage]; /* reconstructed tuples */
/* /*
* Note: using MaxIndexTuplesPerPage above is a bit hokey since * Note: using MaxIndexTuplesPerPage above is a bit hokey since
@ -158,6 +155,21 @@ typedef struct SpGistScanOpaqueData
typedef SpGistScanOpaqueData *SpGistScanOpaque; typedef SpGistScanOpaqueData *SpGistScanOpaque;
/*
* This struct is what we actually keep in index->rd_amcache. It includes
* static configuration information as well as the lastUsedPages cache.
*/
typedef struct SpGistCache
{
spgConfigOut config; /* filled in by opclass config method */
SpGistTypeDesc attType; /* type of input data and leaf values */
SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */
SpGistTypeDesc attLabelType; /* type of node label values */
SpGistLUPCache lastUsedPages; /* local storage of last-used info */
} SpGistCache;
/* /*
* SPGiST tuple types. Note: inner, leaf, and dead tuple structs * SPGiST tuple types. Note: inner, leaf, and dead tuple structs
@ -570,6 +582,7 @@ typedef struct spgxlogVacuumRedirect
#define GBUF_INNER_PARITY(x) ((x) % 3) #define GBUF_INNER_PARITY(x) ((x) % 3)
/* spgutils.c */ /* spgutils.c */
extern SpGistCache *spgGetCache(Relation index);
extern void initSpGistState(SpGistState *state, Relation index); extern void initSpGistState(SpGistState *state, Relation index);
extern Buffer SpGistNewBuffer(Relation index); extern Buffer SpGistNewBuffer(Relation index);
extern void SpGistUpdateMetaPage(Relation index); extern void SpGistUpdateMetaPage(Relation index);

View File

@ -681,9 +681,9 @@ SELECT * FROM point_tbl WHERE f1 <@ '(-10,-10),(10,10)':: box ORDER BY f1 <-> '0
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)'; SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
QUERY PLAN QUERY PLAN
--------------------------------------------------------- -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p <@ '(1000,1000),(200,200)'::box) Index Cond: (p <@ '(1000,1000),(200,200)'::box)
(3 rows) (3 rows)
@ -696,10 +696,10 @@ SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p; SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
QUERY PLAN QUERY PLAN
--------------------------------------------------------- -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: ('(1000,1000),(200,200)'::box @> p) Index Cond: (p <@ '(1000,1000),(200,200)'::box)
(3 rows) (3 rows)
SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p; SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
@ -711,9 +711,9 @@ SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)'; SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
QUERY PLAN QUERY PLAN
------------------------------------------------------ -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p << '(5000,4000)'::point) Index Cond: (p << '(5000,4000)'::point)
(3 rows) (3 rows)
@ -726,9 +726,9 @@ SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)'; SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
QUERY PLAN QUERY PLAN
------------------------------------------------------ -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p >> '(5000,4000)'::point) Index Cond: (p >> '(5000,4000)'::point)
(3 rows) (3 rows)
@ -741,9 +741,9 @@ SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p <^ '(5000, 4000)'; SELECT count(*) FROM quad_point_tbl WHERE p <^ '(5000, 4000)';
QUERY PLAN QUERY PLAN
------------------------------------------------------ -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p <^ '(5000,4000)'::point) Index Cond: (p <^ '(5000,4000)'::point)
(3 rows) (3 rows)
@ -756,9 +756,9 @@ SELECT count(*) FROM quad_point_tbl WHERE p <^ '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)'; SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)';
QUERY PLAN QUERY PLAN
------------------------------------------------------ -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p >^ '(5000,4000)'::point) Index Cond: (p >^ '(5000,4000)'::point)
(3 rows) (3 rows)
@ -771,9 +771,9 @@ SELECT count(*) FROM quad_point_tbl WHERE p >^ '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM quad_point_tbl WHERE p ~= '(4585, 365)';
QUERY PLAN QUERY PLAN
------------------------------------------------------ -----------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_quad_ind on quad_point_tbl -> Index Only Scan using sp_quad_ind on quad_point_tbl
Index Cond: (p ~= '(4585,365)'::point) Index Cond: (p ~= '(4585,365)'::point)
(3 rows) (3 rows)
@ -788,7 +788,7 @@ SELECT count(*) FROM kd_point_tbl WHERE p <@ box '(200,200,1000,1000)';
QUERY PLAN QUERY PLAN
--------------------------------------------------------- ---------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p <@ '(1000,1000),(200,200)'::box) Index Cond: (p <@ '(1000,1000),(200,200)'::box)
(3 rows) (3 rows)
@ -803,8 +803,8 @@ SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
QUERY PLAN QUERY PLAN
--------------------------------------------------------- ---------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: ('(1000,1000),(200,200)'::box @> p) Index Cond: (p <@ '(1000,1000),(200,200)'::box)
(3 rows) (3 rows)
SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p; SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
@ -816,9 +816,9 @@ SELECT count(*) FROM kd_point_tbl WHERE box '(200,200,1000,1000)' @> p;
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)'; SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
QUERY PLAN QUERY PLAN
-------------------------------------------------- -------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p << '(5000,4000)'::point) Index Cond: (p << '(5000,4000)'::point)
(3 rows) (3 rows)
@ -831,9 +831,9 @@ SELECT count(*) FROM kd_point_tbl WHERE p << '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)'; SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
QUERY PLAN QUERY PLAN
-------------------------------------------------- -------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p >> '(5000,4000)'::point) Index Cond: (p >> '(5000,4000)'::point)
(3 rows) (3 rows)
@ -846,9 +846,9 @@ SELECT count(*) FROM kd_point_tbl WHERE p >> '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p <^ '(5000, 4000)'; SELECT count(*) FROM kd_point_tbl WHERE p <^ '(5000, 4000)';
QUERY PLAN QUERY PLAN
-------------------------------------------------- -------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p <^ '(5000,4000)'::point) Index Cond: (p <^ '(5000,4000)'::point)
(3 rows) (3 rows)
@ -861,9 +861,9 @@ SELECT count(*) FROM kd_point_tbl WHERE p <^ '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p >^ '(5000, 4000)'; SELECT count(*) FROM kd_point_tbl WHERE p >^ '(5000, 4000)';
QUERY PLAN QUERY PLAN
-------------------------------------------------- -------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p >^ '(5000,4000)'::point) Index Cond: (p >^ '(5000,4000)'::point)
(3 rows) (3 rows)
@ -876,9 +876,9 @@ SELECT count(*) FROM kd_point_tbl WHERE p >^ '(5000, 4000)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)'; SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
QUERY PLAN QUERY PLAN
-------------------------------------------------- -------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_kd_ind on kd_point_tbl -> Index Only Scan using sp_kd_ind on kd_point_tbl
Index Cond: (p ~= '(4585,365)'::point) Index Cond: (p ~= '(4585,365)'::point)
(3 rows) (3 rows)
@ -891,9 +891,9 @@ SELECT count(*) FROM kd_point_tbl WHERE p ~= '(4585, 365)';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef'; SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
QUERY PLAN QUERY PLAN
------------------------------------------------------- ------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t = '0123456789abcdef'::text) Index Cond: (t = '0123456789abcdef'::text)
(3 rows) (3 rows)
@ -906,9 +906,9 @@ SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdef';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde'; SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
QUERY PLAN QUERY PLAN
------------------------------------------------------- ------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t = '0123456789abcde'::text) Index Cond: (t = '0123456789abcde'::text)
(3 rows) (3 rows)
@ -921,9 +921,9 @@ SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcde';
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)
SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF'; SELECT count(*) FROM suffix_text_tbl WHERE t = '0123456789abcdefF';
QUERY PLAN QUERY PLAN
------------------------------------------------------- ------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t = '0123456789abcdefF'::text) Index Cond: (t = '0123456789abcdefF'::text)
(3 rows) (3 rows)
@ -938,7 +938,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t < 'Aztec
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------- ----------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t < 'Aztec Ct '::text) Index Cond: (t < 'Aztec Ct '::text)
(3 rows) (3 rows)
@ -953,7 +953,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~<~ 'Aztec
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------ ------------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t ~<~ 'Aztec Ct '::text) Index Cond: (t ~<~ 'Aztec Ct '::text)
(3 rows) (3 rows)
@ -968,7 +968,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t <= 'Aztec
QUERY PLAN QUERY PLAN
----------------------------------------------------------------------- -----------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t <= 'Aztec Ct '::text) Index Cond: (t <= 'Aztec Ct '::text)
(3 rows) (3 rows)
@ -983,7 +983,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~<=~ 'Aztec
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------- -------------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t ~<=~ 'Aztec Ct '::text) Index Cond: (t ~<=~ 'Aztec Ct '::text)
(3 rows) (3 rows)
@ -998,7 +998,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t = 'Aztec
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------- ----------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t = 'Aztec Ct '::text) Index Cond: (t = 'Aztec Ct '::text)
(3 rows) (3 rows)
@ -1013,7 +1013,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t = 'Worth
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------- ----------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t = 'Worth St '::text) Index Cond: (t = 'Worth St '::text)
(3 rows) (3 rows)
@ -1028,7 +1028,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t >= 'Worth
QUERY PLAN QUERY PLAN
----------------------------------------------------------------------- -----------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t >= 'Worth St '::text) Index Cond: (t >= 'Worth St '::text)
(3 rows) (3 rows)
@ -1043,7 +1043,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~>=~ 'Worth
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------- -------------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t ~>=~ 'Worth St '::text) Index Cond: (t ~>=~ 'Worth St '::text)
(3 rows) (3 rows)
@ -1058,7 +1058,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t > 'Worth
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------- ----------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t > 'Worth St '::text) Index Cond: (t > 'Worth St '::text)
(3 rows) (3 rows)
@ -1073,7 +1073,7 @@ SELECT count(*) FROM suffix_text_tbl WHERE t ~>~ 'Worth
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------ ------------------------------------------------------------------------
Aggregate Aggregate
-> Index Scan using sp_suff_ind on suffix_text_tbl -> Index Only Scan using sp_suff_ind on suffix_text_tbl
Index Cond: (t ~>~ 'Worth St '::text) Index Cond: (t ~>~ 'Worth St '::text)
(3 rows) (3 rows)