Commit 09c1c6ab authored by Tom Lane's avatar Tom Lane

Support INCLUDE'd columns in SP-GiST.

Not much to say here: does what it says on the tin.
We steal a previously-always-zero bit from the nextOffset
field of leaf index tuples in order to track whether there
is a nulls bitmap.  Otherwise it works about like included
columns in other index types.

Pavel Borisov, reviewed by Andrey Borodin and Anastasia Lubennikova,
and rather heavily editorialized on by me

Discussion: https://postgr.es/m/CALT9ZEFi-vMp4faht9f9Junb1nO3NOSjhpxTmbm1UGLMsLqiEQ@mail.gmail.com
parent 49f49def
...@@ -409,9 +409,11 @@ CREATE INDEX test2_mm_idx ON test2 (major, minor); ...@@ -409,9 +409,11 @@ CREATE INDEX test2_mm_idx ON test2 (major, minor);
</para> </para>
<para> <para>
Currently, only the B-tree, GiST, GIN, and BRIN Currently, only the B-tree, GiST, GIN, and BRIN index types support
index types support multicolumn multiple-key-column indexes. Whether there can be multiple key
indexes. Up to 32 columns can be specified. (This limit can be columns is independent of whether <literal>INCLUDE</literal> columns
can be added to the index. Indexes can have up to 32 columns,
including <literal>INCLUDE</literal> columns. (This limit can be
altered when building <productname>PostgreSQL</productname>; see the altered when building <productname>PostgreSQL</productname>; see the
file <filename>pg_config_manual.h</filename>.) file <filename>pg_config_manual.h</filename>.)
</para> </para>
...@@ -1208,8 +1210,8 @@ CREATE UNIQUE INDEX tab_x_y ON tab(x) INCLUDE (y); ...@@ -1208,8 +1210,8 @@ CREATE UNIQUE INDEX tab_x_y ON tab(x) INCLUDE (y);
likely to not need to access the heap. If the heap tuple must be visited likely to not need to access the heap. If the heap tuple must be visited
anyway, it costs nothing more to get the column's value from there. anyway, it costs nothing more to get the column's value from there.
Other restrictions are that expressions are not currently supported as Other restrictions are that expressions are not currently supported as
included columns, and that only B-tree and GiST indexes currently support included columns, and that only B-tree, GiST and SP-GiST indexes currently
included columns. support included columns.
</para> </para>
<para> <para>
......
...@@ -187,8 +187,8 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class= ...@@ -187,8 +187,8 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</para> </para>
<para> <para>
Currently, the B-tree and the GiST index access methods support this Currently, the B-tree, GiST and SP-GiST index access methods support
feature. In B-tree and the GiST indexes, the values of columns listed this feature. In these indexes, the values of columns listed
in the <literal>INCLUDE</literal> clause are included in leaf tuples in the <literal>INCLUDE</literal> clause are included in leaf tuples
which correspond to heap tuples, but are not included in upper-level which correspond to heap tuples, but are not included in upper-level
index entries used for tree navigation. index entries used for tree navigation.
...@@ -695,7 +695,10 @@ Indexes: ...@@ -695,7 +695,10 @@ Indexes:
<para> <para>
Currently, only the B-tree, GiST, GIN, and BRIN index methods support Currently, only the B-tree, GiST, GIN, and BRIN index methods support
multicolumn indexes. Up to 32 fields can be specified by default. multiple-key-column indexes. Whether there can be multiple key
columns is independent of whether <literal>INCLUDE</literal> columns
can be added to the index. Indexes can have up to 32 columns,
including <literal>INCLUDE</literal> columns.
(This limit can be altered when building (This limit can be altered when building
<productname>PostgreSQL</productname>.) Only B-tree currently <productname>PostgreSQL</productname>.) Only B-tree currently
supports unique indexes. supports unique indexes.
......
...@@ -216,6 +216,14 @@ ...@@ -216,6 +216,14 @@
inner tuples that are passed through to reach the leaf level. inner tuples that are passed through to reach the leaf level.
</para> </para>
<para>
When an <acronym>SP-GiST</acronym> index is created with
<literal>INCLUDE</literal> columns, the values of those columns are also
stored in leaf tuples. The <literal>INCLUDE</literal> columns are of no
concern to the <acronym>SP-GiST</acronym> operator class, so they are
not discussed further here.
</para>
<para> <para>
Inner tuples are more complex, since they are branching points in the Inner tuples are more complex, since they are branching points in the
search tree. Each inner tuple contains a set of one or more search tree. Each inner tuple contains a set of one or more
......
...@@ -1426,11 +1426,15 @@ CREATE OPERATOR CLASS polygon_ops ...@@ -1426,11 +1426,15 @@ CREATE OPERATOR CLASS polygon_ops
STORAGE box; STORAGE box;
</programlisting> </programlisting>
At present, only the GiST, GIN and BRIN index methods support a At present, only the GiST, SP-GiST, GIN and BRIN index methods support a
<literal>STORAGE</literal> type that's different from the column data type. <literal>STORAGE</literal> type that's different from the column data type.
The GiST <function>compress</function> and <function>decompress</function> support The GiST <function>compress</function> and <function>decompress</function> support
routines must deal with data-type conversion when <literal>STORAGE</literal> routines must deal with data-type conversion when <literal>STORAGE</literal>
is used. In GIN, the <literal>STORAGE</literal> type identifies the type of is used. SP-GiST likewise requires a <function>compress</function>
support function to convert to the storage type, when that is different;
if an SP-GiST opclass also supports retrieving data, the reverse
conversion must be handled by the <function>consistent</function> function.
In GIN, the <literal>STORAGE</literal> type identifies the type of
the <quote>key</quote> values, which normally is different from the type the <quote>key</quote> values, which normally is different from the type
of the indexed column &mdash; for example, an operator class for of the indexed column &mdash; for example, an operator class for
integer-array columns might have keys that are just integers. The integer-array columns might have keys that are just integers. The
......
...@@ -446,22 +446,37 @@ void ...@@ -446,22 +446,37 @@ void
index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor, index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
Datum *values, bool *isnull) Datum *values, bool *isnull)
{ {
int hasnulls = IndexTupleHasNulls(tup);
int natts = tupleDescriptor->natts; /* number of atts to extract */
int attnum;
char *tp; /* ptr to tuple data */ char *tp; /* ptr to tuple data */
int off; /* offset in tuple data */
bits8 *bp; /* ptr to null bitmap in tuple */ bits8 *bp; /* ptr to null bitmap in tuple */
bool slow = false; /* can we use/set attcacheoff? */
/* Assert to protect callers who allocate fixed-size arrays */
Assert(natts <= INDEX_MAX_KEYS);
/* XXX "knows" t_bits are just after fixed tuple header! */ /* XXX "knows" t_bits are just after fixed tuple header! */
bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData)); bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info); tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
off = 0;
index_deform_tuple_internal(tupleDescriptor, values, isnull,
tp, bp, IndexTupleHasNulls(tup));
}
/*
* Convert an index tuple into Datum/isnull arrays,
* without assuming any specific layout of the index tuple header.
*
* Caller must supply pointer to data area, pointer to nulls bitmap
* (which can be NULL if !hasnulls), and hasnulls flag.
*/
void
index_deform_tuple_internal(TupleDesc tupleDescriptor,
Datum *values, bool *isnull,
char *tp, bits8 *bp, int hasnulls)
{
int natts = tupleDescriptor->natts; /* number of atts to extract */
int attnum;
int off = 0; /* offset in tuple data */
bool slow = false; /* can we use/set attcacheoff? */
/* Assert to protect callers who allocate fixed-size arrays */
Assert(natts <= INDEX_MAX_KEYS);
for (attnum = 0; attnum < natts; attnum++) for (attnum = 0; attnum < natts; attnum++)
{ {
......
...@@ -56,7 +56,7 @@ list and there is no free space on page, then SP-GiST creates a new inner ...@@ -56,7 +56,7 @@ list and there is no free space on page, then SP-GiST creates a new inner
tuple and distributes leaf tuples into a set of lists on, perhaps, several tuple and distributes leaf tuples into a set of lists on, perhaps, several
pages. pages.
Inner tuple consists of: An inner tuple consists of:
optional prefix value - all successors must be consistent with it. optional prefix value - all successors must be consistent with it.
Example: Example:
...@@ -67,14 +67,26 @@ Inner tuple consists of: ...@@ -67,14 +67,26 @@ Inner tuple consists of:
list of nodes, where node is a (label, pointer) pair. list of nodes, where node is a (label, pointer) pair.
Example of a label: a single character for radix tree Example of a label: a single character for radix tree
Leaf tuple consists of: A leaf tuple consists of:
a leaf value a leaf value
Example: Example:
radix tree - the rest of string (postfix) radix tree - the rest of string (postfix)
quad and k-d tree - the point itself quad and k-d tree - the point itself
ItemPointer to the heap ItemPointer to the corresponding heap tuple
nextOffset number of next leaf tuple in a chain on a leaf page
optional nulls bitmask
optional INCLUDE-column values
For compatibility with pre-v14 indexes, a leaf tuple has a nulls bitmask
only if there are null values (among the leaf value and the INCLUDE values)
*and* there is at least one INCLUDE column. The null-ness of the leaf
value can be inferred from whether the tuple is on a "nulls page" (see below)
so it is not necessary to represent it explicitly. But we include it anyway
in a bitmask used with INCLUDE values, so that standard tuple deconstruction
code can be used.
NULLS HANDLING NULLS HANDLING
......
This diff is collapsed.
...@@ -56,7 +56,7 @@ spgistBuildCallback(Relation index, ItemPointer tid, Datum *values, ...@@ -56,7 +56,7 @@ spgistBuildCallback(Relation index, ItemPointer tid, Datum *values,
* any temp data when retrying. * any temp data when retrying.
*/ */
while (!spgdoinsert(index, &buildstate->spgstate, tid, while (!spgdoinsert(index, &buildstate->spgstate, tid,
*values, *isnull)) values, isnull))
{ {
MemoryContextReset(buildstate->tmpCtx); MemoryContextReset(buildstate->tmpCtx);
} }
...@@ -227,7 +227,7 @@ spginsert(Relation index, Datum *values, bool *isnull, ...@@ -227,7 +227,7 @@ spginsert(Relation index, Datum *values, bool *isnull,
* to avoid cumulative memory consumption. That means we also have to * to avoid cumulative memory consumption. That means we also have to
* redo initSpGistState(), but it's cheap enough not to matter. * redo initSpGistState(), but it's cheap enough not to matter.
*/ */
while (!spgdoinsert(index, &spgstate, ht_ctid, *values, *isnull)) while (!spgdoinsert(index, &spgstate, ht_ctid, values, isnull))
{ {
MemoryContextReset(insertCtx); MemoryContextReset(insertCtx);
initSpGistState(&spgstate, index); initSpGistState(&spgstate, index);
......
...@@ -27,7 +27,8 @@ ...@@ -27,7 +27,8 @@
#include "utils/rel.h" #include "utils/rel.h"
typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr, typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
Datum leafValue, bool isNull, bool recheck, Datum leafValue, bool isNull,
SpGistLeafTuple leafTuple, bool recheck,
bool recheckDistances, double *distances); bool recheckDistances, double *distances);
/* /*
...@@ -88,6 +89,9 @@ spgFreeSearchItem(SpGistScanOpaque so, SpGistSearchItem *item) ...@@ -88,6 +89,9 @@ spgFreeSearchItem(SpGistScanOpaque so, SpGistSearchItem *item)
DatumGetPointer(item->value) != NULL) DatumGetPointer(item->value) != NULL)
pfree(DatumGetPointer(item->value)); pfree(DatumGetPointer(item->value));
if (item->leafTuple)
pfree(item->leafTuple);
if (item->traversalValue) if (item->traversalValue)
pfree(item->traversalValue); pfree(item->traversalValue);
...@@ -133,6 +137,7 @@ spgAddStartItem(SpGistScanOpaque so, bool isnull) ...@@ -133,6 +137,7 @@ spgAddStartItem(SpGistScanOpaque so, bool isnull)
startEntry->isLeaf = false; startEntry->isLeaf = false;
startEntry->level = 0; startEntry->level = 0;
startEntry->value = (Datum) 0; startEntry->value = (Datum) 0;
startEntry->leafTuple = NULL;
startEntry->traversalValue = NULL; startEntry->traversalValue = NULL;
startEntry->recheck = false; startEntry->recheck = false;
startEntry->recheckDistances = false; startEntry->recheckDistances = false;
...@@ -299,7 +304,6 @@ spgbeginscan(Relation rel, int keysz, int orderbysz) ...@@ -299,7 +304,6 @@ spgbeginscan(Relation rel, int keysz, int orderbysz)
{ {
IndexScanDesc scan; IndexScanDesc scan;
SpGistScanOpaque so; SpGistScanOpaque so;
TupleDesc outTupDesc;
int i; int i;
scan = RelationGetIndexScan(rel, keysz, orderbysz); scan = RelationGetIndexScan(rel, keysz, orderbysz);
...@@ -319,20 +323,13 @@ spgbeginscan(Relation rel, int keysz, int orderbysz) ...@@ -319,20 +323,13 @@ spgbeginscan(Relation rel, int keysz, int orderbysz)
ALLOCSET_DEFAULT_SIZES); ALLOCSET_DEFAULT_SIZES);
/* /*
* Set up indexTupDesc and xs_hitupdesc in case it's an index-only scan. * Set up reconTupDesc and xs_hitupdesc in case it's an index-only scan,
* making sure that the key column is shown as being of type attType.
* (It's rather annoying to do this work when it might be wasted, but for * (It's rather annoying to do this work when it might be wasted, but for
* most opclasses we can re-use the index reldesc instead of making one.) * most opclasses we can re-use the index reldesc instead of making one.)
*/ */
if (so->state.attType.type == so->reconTupDesc = scan->xs_hitupdesc =
TupleDescAttr(RelationGetDescr(rel), 0)->atttypid) getSpGistTupleDesc(rel, &so->state.attType);
outTupDesc = RelationGetDescr(rel);
else
{
outTupDesc = CreateTemplateTupleDesc(1);
TupleDescInitEntry(outTupDesc, 1, NULL,
so->state.attType.type, -1, 0);
}
so->indexTupDesc = scan->xs_hitupdesc = outTupDesc;
/* Allocate various arrays needed for order-by scans */ /* Allocate various arrays needed for order-by scans */
if (scan->numberOfOrderBys > 0) if (scan->numberOfOrderBys > 0)
...@@ -435,6 +432,10 @@ spgendscan(IndexScanDesc scan) ...@@ -435,6 +432,10 @@ spgendscan(IndexScanDesc scan)
if (so->keyData) if (so->keyData)
pfree(so->keyData); pfree(so->keyData);
if (so->state.leafTupDesc &&
so->state.leafTupDesc != RelationGetDescr(so->state.index))
FreeTupleDesc(so->state.leafTupDesc);
if (so->state.deadTupleStorage) if (so->state.deadTupleStorage)
pfree(so->state.deadTupleStorage); pfree(so->state.deadTupleStorage);
...@@ -455,14 +456,14 @@ spgendscan(IndexScanDesc scan) ...@@ -455,14 +456,14 @@ spgendscan(IndexScanDesc scan)
* Leaf SpGistSearchItem constructor, called in queue context * Leaf SpGistSearchItem constructor, called in queue context
*/ */
static SpGistSearchItem * static SpGistSearchItem *
spgNewHeapItem(SpGistScanOpaque so, int level, ItemPointer heapPtr, spgNewHeapItem(SpGistScanOpaque so, int level, SpGistLeafTuple leafTuple,
Datum leafValue, bool recheck, bool recheckDistances, Datum leafValue, bool recheck, bool recheckDistances,
bool isnull, double *distances) bool isnull, double *distances)
{ {
SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances); SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
item->level = level; item->level = level;
item->heapPtr = *heapPtr; item->heapPtr = leafTuple->heapPtr;
/* /*
* If we need the reconstructed value, copy it to queue cxt out of tmp * If we need the reconstructed value, copy it to queue cxt out of tmp
...@@ -471,11 +472,28 @@ spgNewHeapItem(SpGistScanOpaque so, int level, ItemPointer heapPtr, ...@@ -471,11 +472,28 @@ spgNewHeapItem(SpGistScanOpaque so, int level, ItemPointer heapPtr,
* the wrong type. The correct leafValue type is attType not leafType. * the wrong type. The correct leafValue type is attType not leafType.
*/ */
if (so->want_itup) if (so->want_itup)
{
item->value = isnull ? (Datum) 0 : item->value = isnull ? (Datum) 0 :
datumCopy(leafValue, so->state.attType.attbyval, datumCopy(leafValue, so->state.attType.attbyval,
so->state.attType.attlen); so->state.attType.attlen);
/*
* If we're going to need to reconstruct INCLUDE attributes, store the
* whole leaf tuple so we can get the INCLUDE attributes out of it.
*/
if (so->state.leafTupDesc->natts > 1)
{
item->leafTuple = palloc(leafTuple->size);
memcpy(item->leafTuple, leafTuple, leafTuple->size);
}
else
item->leafTuple = NULL;
}
else else
{
item->value = (Datum) 0; item->value = (Datum) 0;
item->leafTuple = NULL;
}
item->traversalValue = NULL; item->traversalValue = NULL;
item->isLeaf = true; item->isLeaf = true;
item->recheck = recheck; item->recheck = recheck;
...@@ -555,7 +573,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item, ...@@ -555,7 +573,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
/* the scan is ordered -> add the item to the queue */ /* the scan is ordered -> add the item to the queue */
MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt); MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt);
SpGistSearchItem *heapItem = spgNewHeapItem(so, item->level, SpGistSearchItem *heapItem = spgNewHeapItem(so, item->level,
&leafTuple->heapPtr, leafTuple,
leafValue, leafValue,
recheck, recheck,
recheckDistances, recheckDistances,
...@@ -571,7 +589,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item, ...@@ -571,7 +589,7 @@ spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item,
/* non-ordered scan, so report the item right away */ /* non-ordered scan, so report the item right away */
Assert(!recheckDistances); Assert(!recheckDistances);
storeRes(so, &leafTuple->heapPtr, leafValue, isnull, storeRes(so, &leafTuple->heapPtr, leafValue, isnull,
recheck, false, NULL); leafTuple, recheck, false, NULL);
*reportedSome = true; *reportedSome = true;
} }
} }
...@@ -624,6 +642,8 @@ spgMakeInnerItem(SpGistScanOpaque so, ...@@ -624,6 +642,8 @@ spgMakeInnerItem(SpGistScanOpaque so,
so->state.attLeafType.attlen) so->state.attLeafType.attlen)
: (Datum) 0; : (Datum) 0;
item->leafTuple = NULL;
/* /*
* Elements of out.traversalValues should be allocated in * Elements of out.traversalValues should be allocated in
* in.traversalMemoryContext, which is actually a long lived context of * in.traversalMemoryContext, which is actually a long lived context of
...@@ -765,7 +785,7 @@ spgTestLeafTuple(SpGistScanOpaque so, ...@@ -765,7 +785,7 @@ spgTestLeafTuple(SpGistScanOpaque so,
/* dead tuple should be first in chain */ /* dead tuple should be first in chain */
Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr)); Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr));
/* No live entries on this page */ /* No live entries on this page */
Assert(leafTuple->nextOffset == InvalidOffsetNumber); Assert(SGLT_GET_NEXTOFFSET(leafTuple) == InvalidOffsetNumber);
return SpGistBreakOffsetNumber; return SpGistBreakOffsetNumber;
} }
} }
...@@ -779,7 +799,7 @@ spgTestLeafTuple(SpGistScanOpaque so, ...@@ -779,7 +799,7 @@ spgTestLeafTuple(SpGistScanOpaque so,
spgLeafTest(so, item, leafTuple, isnull, reportedSome, storeRes); spgLeafTest(so, item, leafTuple, isnull, reportedSome, storeRes);
return leafTuple->nextOffset; return SGLT_GET_NEXTOFFSET(leafTuple);
} }
/* /*
...@@ -812,7 +832,8 @@ redirect: ...@@ -812,7 +832,8 @@ redirect:
/* We store heap items in the queue only in case of ordered search */ /* We store heap items in the queue only in case of ordered search */
Assert(so->numberOfNonNullOrderBys > 0); Assert(so->numberOfNonNullOrderBys > 0);
storeRes(so, &item->heapPtr, item->value, item->isNull, storeRes(so, &item->heapPtr, item->value, item->isNull,
item->recheck, item->recheckDistances, item->distances); item->leafTuple, item->recheck,
item->recheckDistances, item->distances);
reportedSome = true; reportedSome = true;
} }
else else
...@@ -905,8 +926,9 @@ redirect: ...@@ -905,8 +926,9 @@ redirect:
/* storeRes subroutine for getbitmap case */ /* storeRes subroutine for getbitmap case */
static void static void
storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr, storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
Datum leafValue, bool isnull, bool recheck, bool recheckDistances, Datum leafValue, bool isnull,
double *distances) SpGistLeafTuple leafTuple, bool recheck,
bool recheckDistances, double *distances)
{ {
Assert(!recheckDistances && !distances); Assert(!recheckDistances && !distances);
tbm_add_tuples(so->tbm, heapPtr, 1, recheck); tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
...@@ -932,8 +954,9 @@ spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm) ...@@ -932,8 +954,9 @@ spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
/* storeRes subroutine for gettuple case */ /* storeRes subroutine for gettuple case */
static void static void
storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr, storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
Datum leafValue, bool isnull, bool recheck, bool recheckDistances, Datum leafValue, bool isnull,
double *nonNullDistances) SpGistLeafTuple leafTuple, bool recheck,
bool recheckDistances, double *nonNullDistances)
{ {
Assert(so->nPtrs < MaxIndexTuplesPerPage); Assert(so->nPtrs < MaxIndexTuplesPerPage);
so->heapPtrs[so->nPtrs] = *heapPtr; so->heapPtrs[so->nPtrs] = *heapPtr;
...@@ -978,9 +1001,20 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr, ...@@ -978,9 +1001,20 @@ storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
* Reconstruct index data. We have to copy the datum out of the temp * Reconstruct index data. We have to copy the datum out of the temp
* context anyway, so we may as well create the tuple here. * context anyway, so we may as well create the tuple here.
*/ */
so->reconTups[so->nPtrs] = heap_form_tuple(so->indexTupDesc, Datum leafDatums[INDEX_MAX_KEYS];
&leafValue, bool leafIsnulls[INDEX_MAX_KEYS];
&isnull);
/* We only need to deform the old tuple if it has INCLUDE attributes */
if (so->state.leafTupDesc->natts > 1)
spgDeformLeafTuple(leafTuple, so->state.leafTupDesc,
leafDatums, leafIsnulls, isnull);
leafDatums[spgKeyColumn] = leafValue;
leafIsnulls[spgKeyColumn] = isnull;
so->reconTups[so->nPtrs] = heap_form_tuple(so->reconTupDesc,
leafDatums,
leafIsnulls);
} }
so->nPtrs++; so->nPtrs++;
} }
...@@ -1048,6 +1082,10 @@ spgcanreturn(Relation index, int attno) ...@@ -1048,6 +1082,10 @@ spgcanreturn(Relation index, int attno)
{ {
SpGistCache *cache; SpGistCache *cache;
/* INCLUDE attributes can always be fetched for index-only scans */
if (attno > 1)
return true;
/* We can do it if the opclass config function says so */ /* We can do it if the opclass config function says so */
cache = spgGetCache(index); cache = spgGetCache(index);
......
This diff is collapsed.
...@@ -168,23 +168,23 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, ...@@ -168,23 +168,23 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
} }
/* Form predecessor map, too */ /* Form predecessor map, too */
if (lt->nextOffset != InvalidOffsetNumber) if (SGLT_GET_NEXTOFFSET(lt) != InvalidOffsetNumber)
{ {
/* paranoia about corrupted chain links */ /* paranoia about corrupted chain links */
if (lt->nextOffset < FirstOffsetNumber || if (SGLT_GET_NEXTOFFSET(lt) < FirstOffsetNumber ||
lt->nextOffset > max || SGLT_GET_NEXTOFFSET(lt) > max ||
predecessor[lt->nextOffset] != InvalidOffsetNumber) predecessor[SGLT_GET_NEXTOFFSET(lt)] != InvalidOffsetNumber)
elog(ERROR, "inconsistent tuple chain links in page %u of index \"%s\"", elog(ERROR, "inconsistent tuple chain links in page %u of index \"%s\"",
BufferGetBlockNumber(buffer), BufferGetBlockNumber(buffer),
RelationGetRelationName(index)); RelationGetRelationName(index));
predecessor[lt->nextOffset] = i; predecessor[SGLT_GET_NEXTOFFSET(lt)] = i;
} }
} }
else if (lt->tupstate == SPGIST_REDIRECT) else if (lt->tupstate == SPGIST_REDIRECT)
{ {
SpGistDeadTuple dt = (SpGistDeadTuple) lt; SpGistDeadTuple dt = (SpGistDeadTuple) lt;
Assert(dt->nextOffset == InvalidOffsetNumber); Assert(SGLT_GET_NEXTOFFSET(dt) == InvalidOffsetNumber);
Assert(ItemPointerIsValid(&dt->pointer)); Assert(ItemPointerIsValid(&dt->pointer));
/* /*
...@@ -201,7 +201,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, ...@@ -201,7 +201,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
} }
else else
{ {
Assert(lt->nextOffset == InvalidOffsetNumber); Assert(SGLT_GET_NEXTOFFSET(lt) == InvalidOffsetNumber);
} }
} }
...@@ -250,7 +250,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, ...@@ -250,7 +250,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
prevLive = deletable[i] ? InvalidOffsetNumber : i; prevLive = deletable[i] ? InvalidOffsetNumber : i;
/* scan down the chain ... */ /* scan down the chain ... */
j = head->nextOffset; j = SGLT_GET_NEXTOFFSET(head);
while (j != InvalidOffsetNumber) while (j != InvalidOffsetNumber)
{ {
SpGistLeafTuple lt; SpGistLeafTuple lt;
...@@ -301,7 +301,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, ...@@ -301,7 +301,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
interveningDeletable = false; interveningDeletable = false;
} }
j = lt->nextOffset; j = SGLT_GET_NEXTOFFSET(lt);
} }
if (prevLive == InvalidOffsetNumber) if (prevLive == InvalidOffsetNumber)
...@@ -366,7 +366,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, ...@@ -366,7 +366,7 @@ vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer,
lt = (SpGistLeafTuple) PageGetItem(page, lt = (SpGistLeafTuple) PageGetItem(page,
PageGetItemId(page, chainSrc[i])); PageGetItemId(page, chainSrc[i]));
Assert(lt->tupstate == SPGIST_LIVE); Assert(lt->tupstate == SPGIST_LIVE);
lt->nextOffset = chainDest[i]; SGLT_SET_NEXTOFFSET(lt, chainDest[i]);
} }
MarkBufferDirty(buffer); MarkBufferDirty(buffer);
......
...@@ -122,8 +122,8 @@ spgRedoAddLeaf(XLogReaderState *record) ...@@ -122,8 +122,8 @@ spgRedoAddLeaf(XLogReaderState *record)
head = (SpGistLeafTuple) PageGetItem(page, head = (SpGistLeafTuple) PageGetItem(page,
PageGetItemId(page, xldata->offnumHeadLeaf)); PageGetItemId(page, xldata->offnumHeadLeaf));
Assert(head->nextOffset == leafTupleHdr.nextOffset); Assert(SGLT_GET_NEXTOFFSET(head) == SGLT_GET_NEXTOFFSET(&leafTupleHdr));
head->nextOffset = xldata->offnumLeaf; SGLT_SET_NEXTOFFSET(head, xldata->offnumLeaf);
} }
} }
else else
...@@ -822,7 +822,7 @@ spgRedoVacuumLeaf(XLogReaderState *record) ...@@ -822,7 +822,7 @@ spgRedoVacuumLeaf(XLogReaderState *record)
lt = (SpGistLeafTuple) PageGetItem(page, lt = (SpGistLeafTuple) PageGetItem(page,
PageGetItemId(page, chainSrc[i])); PageGetItemId(page, chainSrc[i]));
Assert(lt->tupstate == SPGIST_LIVE); Assert(lt->tupstate == SPGIST_LIVE);
lt->nextOffset = chainDest[i]; SGLT_SET_NEXTOFFSET(lt, chainDest[i]);
} }
PageSetLSN(page, lsn); PageSetLSN(page, lsn);
......
...@@ -154,6 +154,9 @@ extern Datum nocache_index_getattr(IndexTuple tup, int attnum, ...@@ -154,6 +154,9 @@ extern Datum nocache_index_getattr(IndexTuple tup, int attnum,
TupleDesc tupleDesc); TupleDesc tupleDesc);
extern void index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor, extern void index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
Datum *values, bool *isnull); Datum *values, bool *isnull);
extern void index_deform_tuple_internal(TupleDesc tupleDescriptor,
Datum *values, bool *isnull,
char *tp, bits8 *bp, int hasnulls);
extern IndexTuple CopyIndexTuple(IndexTuple source); extern IndexTuple CopyIndexTuple(IndexTuple source);
extern IndexTuple index_truncate_tuple(TupleDesc sourceDescriptor, extern IndexTuple index_truncate_tuple(TupleDesc sourceDescriptor,
IndexTuple source, int leavenatts); IndexTuple source, int leavenatts);
......
...@@ -39,6 +39,10 @@ typedef struct SpGistOptions ...@@ -39,6 +39,10 @@ typedef struct SpGistOptions
(BLCKSZ * (100 - SpGistGetFillFactor(relation)) / 100) (BLCKSZ * (100 - SpGistGetFillFactor(relation)) / 100)
/* SPGiST leaf tuples have one key column, optionally have included columns */
#define spgKeyColumn 0
#define spgFirstIncludeColumn 1
/* Page numbers of fixed-location pages */ /* Page numbers of fixed-location pages */
#define SPGIST_METAPAGE_BLKNO (0) /* metapage */ #define SPGIST_METAPAGE_BLKNO (0) /* metapage */
#define SPGIST_ROOT_BLKNO (1) /* root for normal entries */ #define SPGIST_ROOT_BLKNO (1) /* root for normal entries */
...@@ -125,16 +129,22 @@ typedef struct SpGistMetaPageData ...@@ -125,16 +129,22 @@ typedef struct SpGistMetaPageData
* search code; SpGistScanOpaque is for searches only. * search code; SpGistScanOpaque is for searches only.
*/ */
typedef struct SpGistLeafTupleData *SpGistLeafTuple; /* forward reference */
/* Per-datatype info needed in SpGistState */ /* Per-datatype info needed in SpGistState */
typedef struct SpGistTypeDesc typedef struct SpGistTypeDesc
{ {
Oid type; Oid type;
bool attbyval;
int16 attlen; int16 attlen;
bool attbyval;
char attstorage;
char attalign;
} SpGistTypeDesc; } SpGistTypeDesc;
typedef struct SpGistState typedef struct SpGistState
{ {
Relation index; /* index we're working with */
spgConfigOut config; /* filled in by opclass config method */ spgConfigOut config; /* filled in by opclass config method */
SpGistTypeDesc attType; /* type of values to be indexed/restored */ SpGistTypeDesc attType; /* type of values to be indexed/restored */
...@@ -142,17 +152,22 @@ typedef struct SpGistState ...@@ -142,17 +152,22 @@ 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 */
/* leafTupDesc typically points to index's tupdesc, but not always */
TupleDesc leafTupDesc; /* descriptor for leaf-level tuples */
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 */
bool isBuild; /* true if doing index build */ bool isBuild; /* true if doing index build */
} SpGistState; } SpGistState;
/* Item to be re-examined later during a search */
typedef struct SpGistSearchItem typedef struct SpGistSearchItem
{ {
pairingheap_node phNode; /* pairing heap node */ pairingheap_node phNode; /* pairing heap node */
Datum value; /* value reconstructed from parent, or Datum value; /* value reconstructed from parent, or
* leafValue if isLeaf */ * leafValue if isLeaf */
SpGistLeafTuple leafTuple; /* whole leaf tuple, if needed */
void *traversalValue; /* opclass-specific traverse value */ void *traversalValue; /* opclass-specific traverse value */
int level; /* level of items on this page */ int level; /* level of items on this page */
ItemPointerData heapPtr; /* heap info, if heap tuple */ ItemPointerData heapPtr; /* heap info, if heap tuple */
...@@ -208,7 +223,7 @@ typedef struct SpGistScanOpaqueData ...@@ -208,7 +223,7 @@ typedef struct SpGistScanOpaqueData
/* These fields are only used in amgettuple scans: */ /* These fields are only used in amgettuple scans: */
bool want_itup; /* are we reconstructing tuples? */ bool want_itup; /* are we reconstructing tuples? */
TupleDesc indexTupDesc; /* if so, descriptor for reconstructed tuples */ TupleDesc reconTupDesc; /* if so, descriptor for reconstructed tuples */
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 */
...@@ -329,23 +344,36 @@ typedef SpGistNodeTupleData *SpGistNodeTuple; ...@@ -329,23 +344,36 @@ typedef SpGistNodeTupleData *SpGistNodeTuple;
PointerGetDatum(SGNTDATAPTR(x))) PointerGetDatum(SGNTDATAPTR(x)))
/* /*
* SPGiST leaf tuple: carries a leaf datum and a heap tuple TID * SPGiST leaf tuple: carries a leaf datum and a heap tuple TID,
* and optionally some "included" columns.
* *
* In the simplest case, the leaf datum is the same as the indexed value; * In the simplest case, the leaf datum is the same as the indexed value;
* but it could also be a suffix or some other sort of delta that permits * but it could also be a suffix or some other sort of delta that permits
* reconstruction given knowledge of the prefix path traversed to get here. * reconstruction given knowledge of the prefix path traversed to get here.
* Any included columns are stored without modification.
* *
* If the leaf datum is NULL, it's not stored. This is not represented * A nulls bitmap is present if there are included columns AND any of the
* explicitly; we infer it from the tuple being stored on a nulls page. * datums are NULL. We do not need a nulls bitmap for the case of a null
* leaf datum without included columns, as we can infer whether the leaf
* datum is null from whether the tuple is stored on a nulls page. (This
* provision is mostly for backwards compatibility, but it does save space
* on 32-bit machines.) As with other PG index tuple designs, if the nulls
* bitmap exists then it's of size INDEX_MAX_KEYS bits regardless of the
* actual number of attributes. For the usual choice of INDEX_MAX_KEYS,
* this costs nothing because of alignment considerations.
* *
* The size field is wider than could possibly be needed for an on-disk leaf * The size field is wider than could possibly be needed for an on-disk leaf
* tuple, but this allows us to form leaf tuples even when the datum is too * tuple, but this allows us to form leaf tuples even when the datum is too
* wide to be stored immediately, and it costs nothing because of alignment * wide to be stored immediately, and it costs nothing because of alignment
* considerations. * considerations.
* *
* t_info holds the nextOffset field (14 bits wide, enough for supported
* page sizes) plus the has-nulls-bitmap flag bit; another flag bit is free.
*
* Normally, nextOffset links to the next tuple belonging to the same parent * Normally, nextOffset links to the next tuple belonging to the same parent
* node (which must be on the same page). But when the root page is a leaf * node (which must be on the same page), or it's 0 if there is no next tuple.
* page, we don't chain its tuples, so nextOffset is always 0 on the root. * But when the root page is a leaf page, we don't chain its tuples,
* so nextOffset is always 0 on the root.
* *
* size must be a multiple of MAXALIGN; also, it must be at least SGDTSIZE * size must be a multiple of MAXALIGN; also, it must be at least SGDTSIZE
* so that the tuple can be converted to REDIRECT status later. (This * so that the tuple can be converted to REDIRECT status later. (This
...@@ -356,15 +384,30 @@ typedef struct SpGistLeafTupleData ...@@ -356,15 +384,30 @@ typedef struct SpGistLeafTupleData
{ {
unsigned int tupstate:2, /* LIVE/REDIRECT/DEAD/PLACEHOLDER */ unsigned int tupstate:2, /* LIVE/REDIRECT/DEAD/PLACEHOLDER */
size:30; /* large enough for any palloc'able value */ size:30; /* large enough for any palloc'able value */
OffsetNumber nextOffset; /* next tuple in chain, or InvalidOffsetNumber */ uint16 t_info; /* nextOffset, which links to the next tuple
* in chain, plus two flag bits */
ItemPointerData heapPtr; /* TID of represented heap tuple */ ItemPointerData heapPtr; /* TID of represented heap tuple */
/* leaf datum follows on a MAXALIGN boundary */ /* nulls bitmap follows if the flag bit for it is set */
/* leaf datum, then any included datums, follows on a MAXALIGN boundary */
} SpGistLeafTupleData; } SpGistLeafTupleData;
typedef SpGistLeafTupleData *SpGistLeafTuple; /* Macros to access nextOffset and bit fields inside t_info */
#define SGLT_GET_NEXTOFFSET(spgLeafTuple) \
#define SGLTHDRSZ MAXALIGN(sizeof(SpGistLeafTupleData)) ((spgLeafTuple)->t_info & 0x3FFF)
#define SGLTDATAPTR(x) (((char *) (x)) + SGLTHDRSZ) #define SGLT_GET_HASNULLMASK(spgLeafTuple) \
(((spgLeafTuple)->t_info & 0x8000) ? true : false)
#define SGLT_SET_NEXTOFFSET(spgLeafTuple, offsetNumber) \
((spgLeafTuple)->t_info = \
((spgLeafTuple)->t_info & 0xC000) | ((offsetNumber) & 0x3FFF))
#define SGLT_SET_HASNULLMASK(spgLeafTuple, hasnulls) \
((spgLeafTuple)->t_info = \
((spgLeafTuple)->t_info & 0x7FFF) | ((hasnulls) ? 0x8000 : 0))
#define SGLTHDRSZ(hasnulls) \
((hasnulls) ? MAXALIGN(sizeof(SpGistLeafTupleData) + \
sizeof(IndexAttributeBitMapData)) : \
MAXALIGN(sizeof(SpGistLeafTupleData)))
#define SGLTDATAPTR(x) (((char *) (x)) + SGLTHDRSZ(SGLT_GET_HASNULLMASK(x)))
#define SGLTDATUM(x, s) fetch_att(SGLTDATAPTR(x), \ #define SGLTDATUM(x, s) fetch_att(SGLTDATAPTR(x), \
(s)->attLeafType.attbyval, \ (s)->attLeafType.attbyval, \
(s)->attLeafType.attlen) (s)->attLeafType.attlen)
...@@ -377,14 +420,14 @@ typedef SpGistLeafTupleData *SpGistLeafTuple; ...@@ -377,14 +420,14 @@ typedef SpGistLeafTupleData *SpGistLeafTuple;
* Also, the pointer field must be in the same place as a leaf tuple's heapPtr * Also, the pointer field must be in the same place as a leaf tuple's heapPtr
* field, to satisfy some Asserts that we make when replacing a leaf tuple * field, to satisfy some Asserts that we make when replacing a leaf tuple
* with a dead tuple. * with a dead tuple.
* We don't use nextOffset, but it's needed to align the pointer field. * We don't use t_info, but it's needed to align the pointer field.
* pointer and xid are only valid when tupstate = REDIRECT. * pointer and xid are only valid when tupstate = REDIRECT.
*/ */
typedef struct SpGistDeadTupleData typedef struct SpGistDeadTupleData
{ {
unsigned int tupstate:2, /* LIVE/REDIRECT/DEAD/PLACEHOLDER */ unsigned int tupstate:2, /* LIVE/REDIRECT/DEAD/PLACEHOLDER */
size:30; size:30;
OffsetNumber nextOffset; /* not used in dead tuples */ uint16 t_info; /* not used in dead tuples */
ItemPointerData pointer; /* redirection inside index */ ItemPointerData pointer; /* redirection inside index */
TransactionId xid; /* ID of xact that inserted this tuple */ TransactionId xid; /* ID of xact that inserted this tuple */
} SpGistDeadTupleData; } SpGistDeadTupleData;
...@@ -451,6 +494,7 @@ typedef SpGistDeadTupleData *SpGistDeadTuple; ...@@ -451,6 +494,7 @@ typedef SpGistDeadTupleData *SpGistDeadTuple;
#define SPGIST_DEFAULT_FILLFACTOR 80 #define SPGIST_DEFAULT_FILLFACTOR 80
extern SpGistCache *spgGetCache(Relation index); extern SpGistCache *spgGetCache(Relation index);
extern TupleDesc getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType);
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);
...@@ -461,10 +505,11 @@ extern void SpGistInitPage(Page page, uint16 f); ...@@ -461,10 +505,11 @@ extern void SpGistInitPage(Page page, uint16 f);
extern void SpGistInitBuffer(Buffer b, uint16 f); extern void SpGistInitBuffer(Buffer b, uint16 f);
extern void SpGistInitMetapage(Page page); extern void SpGistInitMetapage(Page page);
extern unsigned int SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum); extern unsigned int SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum);
extern unsigned int SpGistGetLeafTypeSize(SpGistTypeDesc *att, Datum datum); extern Size SpGistGetLeafTupleSize(TupleDesc tupleDescriptor,
Datum *datums, bool *isnulls);
extern SpGistLeafTuple spgFormLeafTuple(SpGistState *state, extern SpGistLeafTuple spgFormLeafTuple(SpGistState *state,
ItemPointer heapPtr, ItemPointer heapPtr,
Datum datum, bool isnull); Datum *datums, bool *isnulls);
extern SpGistNodeTuple spgFormNodeTuple(SpGistState *state, extern SpGistNodeTuple spgFormNodeTuple(SpGistState *state,
Datum label, bool isnull); Datum label, bool isnull);
extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state, extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state,
...@@ -472,6 +517,9 @@ extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state, ...@@ -472,6 +517,9 @@ extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state,
int nNodes, SpGistNodeTuple *nodes); int nNodes, SpGistNodeTuple *nodes);
extern SpGistDeadTuple spgFormDeadTuple(SpGistState *state, int tupstate, extern SpGistDeadTuple spgFormDeadTuple(SpGistState *state, int tupstate,
BlockNumber blkno, OffsetNumber offnum); BlockNumber blkno, OffsetNumber offnum);
extern void spgDeformLeafTuple(SpGistLeafTuple tup, TupleDesc tupleDescriptor,
Datum *datums, bool *isnulls,
bool keyColumnIsNull);
extern Datum *spgExtractNodeLabels(SpGistState *state, extern Datum *spgExtractNodeLabels(SpGistState *state,
SpGistInnerTuple innerTuple); SpGistInnerTuple innerTuple);
extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page, extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page,
...@@ -490,7 +538,7 @@ extern void spgPageIndexMultiDelete(SpGistState *state, Page page, ...@@ -490,7 +538,7 @@ extern void spgPageIndexMultiDelete(SpGistState *state, Page page,
int firststate, int reststate, int firststate, int reststate,
BlockNumber blkno, OffsetNumber offnum); BlockNumber blkno, OffsetNumber offnum);
extern bool spgdoinsert(Relation index, SpGistState *state, extern bool spgdoinsert(Relation index, SpGistState *state,
ItemPointer heapPtr, Datum datum, bool isnull); ItemPointer heapPtr, Datum *datums, bool *isnulls);
/* spgproc.c */ /* spgproc.c */
extern double *spg_key_orderbys_distances(Datum key, bool isLeaf, extern double *spg_key_orderbys_distances(Datum key, bool isLeaf,
......
...@@ -17,16 +17,21 @@ INFO: SP-GiST leaf data type text does not match declared type name ...@@ -17,16 +17,21 @@ INFO: SP-GiST leaf data type text does not match declared type name
name_ops_old | f name_ops_old | f
(1 row) (1 row)
create table t(f1 name); create table t(f1 name, f2 integer, f3 text);
create index on t using spgist(f1); create index on t using spgist(f1) include(f2, f3);
\d+ t_f1_idx \d+ t_f1_f2_f3_idx
Index "public.t_f1_idx" Index "public.t_f1_f2_f3_idx"
Column | Type | Key? | Definition | Storage | Stats target Column | Type | Key? | Definition | Storage | Stats target
--------+------+------+------------+----------+-------------- --------+---------+------+------------+----------+--------------
f1 | text | yes | f1 | extended | f1 | text | yes | f1 | extended |
f2 | integer | no | f2 | plain |
f3 | text | no | f3 | extended |
spgist, for table "public.t" spgist, for table "public.t"
insert into t select proname from pg_proc; insert into t select
proname,
case when length(proname) % 2 = 0 then pronargs else null end,
prosrc from pg_proc;
vacuum analyze t; vacuum analyze t;
explain (costs off) explain (costs off)
select * from t select * from t
...@@ -36,33 +41,35 @@ select * from t ...@@ -36,33 +41,35 @@ select * from t
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
Sort Sort
Sort Key: f1 Sort Key: f1
-> Index Only Scan using t_f1_idx on t -> Index Only Scan using t_f1_f2_f3_idx on t
Index Cond: ((f1 > 'binary_upgrade_set_n'::name) AND (f1 < 'binary_upgrade_set_p'::name)) Index Cond: ((f1 > 'binary_upgrade_set_n'::name) AND (f1 < 'binary_upgrade_set_p'::name))
(4 rows) (4 rows)
select * from t select * from t
where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p' where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
order by 1; order by 1;
f1 f1 | f2 | f3
------------------------------------------------------ ------------------------------------------------------+----+------------------------------------------------------
binary_upgrade_set_next_array_pg_type_oid binary_upgrade_set_next_array_pg_type_oid | | binary_upgrade_set_next_array_pg_type_oid
binary_upgrade_set_next_heap_pg_class_oid binary_upgrade_set_next_heap_pg_class_oid | | binary_upgrade_set_next_heap_pg_class_oid
binary_upgrade_set_next_index_pg_class_oid binary_upgrade_set_next_index_pg_class_oid | 1 | binary_upgrade_set_next_index_pg_class_oid
binary_upgrade_set_next_multirange_array_pg_type_oid binary_upgrade_set_next_multirange_array_pg_type_oid | 1 | binary_upgrade_set_next_multirange_array_pg_type_oid
binary_upgrade_set_next_multirange_pg_type_oid binary_upgrade_set_next_multirange_pg_type_oid | 1 | binary_upgrade_set_next_multirange_pg_type_oid
binary_upgrade_set_next_pg_authid_oid binary_upgrade_set_next_pg_authid_oid | | binary_upgrade_set_next_pg_authid_oid
binary_upgrade_set_next_pg_enum_oid binary_upgrade_set_next_pg_enum_oid | | binary_upgrade_set_next_pg_enum_oid
binary_upgrade_set_next_pg_type_oid binary_upgrade_set_next_pg_type_oid | | binary_upgrade_set_next_pg_type_oid
binary_upgrade_set_next_toast_pg_class_oid binary_upgrade_set_next_toast_pg_class_oid | 1 | binary_upgrade_set_next_toast_pg_class_oid
(9 rows) (9 rows)
drop index t_f1_idx; drop index t_f1_f2_f3_idx;
create index on t using spgist(f1 name_ops_old); create index on t using spgist(f1 name_ops_old) include(f2, f3);
\d+ t_f1_idx \d+ t_f1_f2_f3_idx
Index "public.t_f1_idx" Index "public.t_f1_f2_f3_idx"
Column | Type | Key? | Definition | Storage | Stats target Column | Type | Key? | Definition | Storage | Stats target
--------+------+------+------------+---------+-------------- --------+---------+------+------------+----------+--------------
f1 | name | yes | f1 | plain | f1 | name | yes | f1 | plain |
f2 | integer | no | f2 | plain |
f3 | text | no | f3 | extended |
spgist, for table "public.t" spgist, for table "public.t"
explain (costs off) explain (costs off)
...@@ -73,23 +80,23 @@ select * from t ...@@ -73,23 +80,23 @@ select * from t
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
Sort Sort
Sort Key: f1 Sort Key: f1
-> Index Only Scan using t_f1_idx on t -> Index Only Scan using t_f1_f2_f3_idx on t
Index Cond: ((f1 > 'binary_upgrade_set_n'::name) AND (f1 < 'binary_upgrade_set_p'::name)) Index Cond: ((f1 > 'binary_upgrade_set_n'::name) AND (f1 < 'binary_upgrade_set_p'::name))
(4 rows) (4 rows)
select * from t select * from t
where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p' where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
order by 1; order by 1;
f1 f1 | f2 | f3
------------------------------------------------------ ------------------------------------------------------+----+------------------------------------------------------
binary_upgrade_set_next_array_pg_type_oid binary_upgrade_set_next_array_pg_type_oid | | binary_upgrade_set_next_array_pg_type_oid
binary_upgrade_set_next_heap_pg_class_oid binary_upgrade_set_next_heap_pg_class_oid | | binary_upgrade_set_next_heap_pg_class_oid
binary_upgrade_set_next_index_pg_class_oid binary_upgrade_set_next_index_pg_class_oid | 1 | binary_upgrade_set_next_index_pg_class_oid
binary_upgrade_set_next_multirange_array_pg_type_oid binary_upgrade_set_next_multirange_array_pg_type_oid | 1 | binary_upgrade_set_next_multirange_array_pg_type_oid
binary_upgrade_set_next_multirange_pg_type_oid binary_upgrade_set_next_multirange_pg_type_oid | 1 | binary_upgrade_set_next_multirange_pg_type_oid
binary_upgrade_set_next_pg_authid_oid binary_upgrade_set_next_pg_authid_oid | | binary_upgrade_set_next_pg_authid_oid
binary_upgrade_set_next_pg_enum_oid binary_upgrade_set_next_pg_enum_oid | | binary_upgrade_set_next_pg_enum_oid
binary_upgrade_set_next_pg_type_oid binary_upgrade_set_next_pg_type_oid | | binary_upgrade_set_next_pg_type_oid
binary_upgrade_set_next_toast_pg_class_oid binary_upgrade_set_next_toast_pg_class_oid | 1 | binary_upgrade_set_next_toast_pg_class_oid
(9 rows) (9 rows)
...@@ -9,11 +9,14 @@ select opcname, amvalidate(opc.oid) ...@@ -9,11 +9,14 @@ select opcname, amvalidate(opc.oid)
from pg_opclass opc join pg_am am on am.oid = opcmethod from pg_opclass opc join pg_am am on am.oid = opcmethod
where amname = 'spgist' and opcname = 'name_ops_old'; where amname = 'spgist' and opcname = 'name_ops_old';
create table t(f1 name); create table t(f1 name, f2 integer, f3 text);
create index on t using spgist(f1); create index on t using spgist(f1) include(f2, f3);
\d+ t_f1_idx \d+ t_f1_f2_f3_idx
insert into t select proname from pg_proc; insert into t select
proname,
case when length(proname) % 2 = 0 then pronargs else null end,
prosrc from pg_proc;
vacuum analyze t; vacuum analyze t;
explain (costs off) explain (costs off)
...@@ -24,10 +27,10 @@ select * from t ...@@ -24,10 +27,10 @@ select * from t
where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p' where f1 > 'binary_upgrade_set_n' and f1 < 'binary_upgrade_set_p'
order by 1; order by 1;
drop index t_f1_idx; drop index t_f1_f2_f3_idx;
create index on t using spgist(f1 name_ops_old); create index on t using spgist(f1 name_ops_old) include(f2, f3);
\d+ t_f1_idx \d+ t_f1_f2_f3_idx
explain (costs off) explain (costs off)
select * from t select * from t
......
...@@ -171,7 +171,7 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p ...@@ -171,7 +171,7 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p
spgist | can_unique | f spgist | can_unique | f
spgist | can_multi_col | f spgist | can_multi_col | f
spgist | can_exclude | t spgist | can_exclude | t
spgist | can_include | f spgist | can_include | t
spgist | bogus | spgist | bogus |
(36 rows) (36 rows)
......
...@@ -555,6 +555,35 @@ WHERE seq.dist IS DISTINCT FROM idx.dist; ...@@ -555,6 +555,35 @@ WHERE seq.dist IS DISTINCT FROM idx.dist;
---+------+---+---+------+--- ---+------+---+---+------+---
(0 rows) (0 rows)
-- test KNN scan with included columns
-- the distance numbers are not exactly the same across platforms
SET extra_float_digits = 0;
CREATE INDEX ON quad_point_tbl_ord_seq1 USING spgist(p) INCLUDE(dist);
EXPLAIN (COSTS OFF)
SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
QUERY PLAN
-------------------------------------------------------------------------------------------
Limit
-> Index Only Scan using quad_point_tbl_ord_seq1_p_dist_idx on quad_point_tbl_ord_seq1
Order By: (p <-> '(0,0)'::point)
(3 rows)
SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
p | dist
-----------+------------------
(59,21) | 62.6258732474047
(88,104) | 136.235090927411
(39,143) | 148.222805262888
(139,160) | 211.945747775227
(209,38) | 212.42645786248
(157,156) | 221.325552072055
(175,150) | 230.488611432322
(236,34) | 238.436574375661
(263,28) | 264.486294540946
(322,53) | 326.33265236565
(10 rows)
RESET extra_float_digits;
-- check ORDER BY distance to NULL -- check ORDER BY distance to NULL
SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1) SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1)
FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt); FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
......
...@@ -349,14 +349,13 @@ SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; ...@@ -349,14 +349,13 @@ SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
DROP TABLE tbl; DROP TABLE tbl;
/* /*
* 7. Check various AMs. All but btree and gist must fail. * 7. Check various AMs. All but btree, gist and spgist must fail.
*/ */
CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box); CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box);
CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4); CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4);
ERROR: access method "brin" does not support included columns ERROR: access method "brin" does not support included columns
CREATE INDEX on tbl USING gist(c3) INCLUDE (c1, c4); CREATE INDEX on tbl USING gist(c3) INCLUDE (c1, c4);
CREATE INDEX on tbl USING spgist(c3) INCLUDE (c4); CREATE INDEX on tbl USING spgist(c3) INCLUDE (c4);
ERROR: access method "spgist" does not support included columns
CREATE INDEX on tbl USING gin(c1, c2) INCLUDE (c3, c4); CREATE INDEX on tbl USING gin(c1, c2) INCLUDE (c3, c4);
ERROR: access method "gin" does not support included columns ERROR: access method "gin" does not support included columns
CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4); CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4);
......
...@@ -225,6 +225,15 @@ SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx ...@@ -225,6 +225,15 @@ SELECT * FROM quad_point_tbl_ord_seq3 seq FULL JOIN kd_point_tbl_ord_idx3 idx
ON seq.n = idx.n ON seq.n = idx.n
WHERE seq.dist IS DISTINCT FROM idx.dist; WHERE seq.dist IS DISTINCT FROM idx.dist;
-- test KNN scan with included columns
-- the distance numbers are not exactly the same across platforms
SET extra_float_digits = 0;
CREATE INDEX ON quad_point_tbl_ord_seq1 USING spgist(p) INCLUDE(dist);
EXPLAIN (COSTS OFF)
SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10;
RESET extra_float_digits;
-- check ORDER BY distance to NULL -- check ORDER BY distance to NULL
SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1) SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1)
FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt); FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt);
......
...@@ -182,7 +182,7 @@ SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname; ...@@ -182,7 +182,7 @@ SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
DROP TABLE tbl; DROP TABLE tbl;
/* /*
* 7. Check various AMs. All but btree and gist must fail. * 7. Check various AMs. All but btree, gist and spgist must fail.
*/ */
CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box); CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box);
CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4); CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment