]> granicus.if.org Git - postgresql/commitdiff
Support for INCLUDE attributes in GiST indexes
authorAlexander Korotkov <akorotkov@postgresql.org>
Sun, 10 Mar 2019 08:36:47 +0000 (11:36 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sun, 10 Mar 2019 08:37:17 +0000 (11:37 +0300)
Similarly to B-tree, GiST index access method gets support of INCLUDE
attributes.  These attributes aren't used for tree navigation and aren't
present in non-leaf pages.  But they are present in leaf pages and can be
fetched during index-only scan.

The point of having INCLUDE attributes in GiST indexes is slightly different
from the point of having them in B-tree.  The main point of INCLUDE attributes
in B-tree is to define UNIQUE constraint over part of attributes enabled for
index-only scan.  In GiST the main point of INCLUDE attributes is to use
index-only scan for attributes, whose data types don't have GiST opclasses.

Discussion: https://postgr.es/m/73A1A452-AD5F-40D4-BD61-978622FF75C1%40yandex-team.ru
Author: Andrey Borodin, with small changes by me
Reviewed-by: Andreas Karlsson
16 files changed:
doc/src/sgml/indices.sgml
doc/src/sgml/ref/create_index.sgml
doc/src/sgml/textsearch.sgml
src/backend/access/gist/gist.c
src/backend/access/gist/gistget.c
src/backend/access/gist/gistscan.c
src/backend/access/gist/gistsplit.c
src/backend/access/gist/gistutil.c
src/include/access/gist_private.h
src/test/regress/expected/amutils.out
src/test/regress/expected/index_including.out
src/test/regress/expected/index_including_gist.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/index_including.sql
src/test/regress/sql/index_including_gist.sql [new file with mode: 0644]

index 46f427b3124745bcc46a697980e35de334e357dc..9943e8ecd4cfb0bde739e6076e0ad2bcc81769e4 100644 (file)
@@ -1145,8 +1145,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
    anyway, it costs nothing more to get the column's value from there.
    Other restrictions are that expressions are not currently supported as
-   included columns, and that only B-tree indexes currently support included
-   columns.
+   included columns, and that only B-tree and GiST indexes currently support
+   included columns.
   </para>
 
   <para>
@@ -1165,7 +1165,7 @@ CREATE INDEX tab_x_y ON tab(x, y);
    enforce uniqueness on the key column(s).  Also, explicitly marking
    non-searchable columns as <literal>INCLUDE</literal> columns makes the
    index slightly smaller, because such columns need not be stored in upper
-   B-tree levels.
+   tree levels.
   </para>
 
   <para>
index ad619cdcfe486cd1edb785debe43863b8b979d18..d8f018f4dacf571caa2d73df92bec8389f617db5 100644 (file)
@@ -181,10 +181,10 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
        </para>
 
        <para>
-        Currently, only the B-tree index access method supports this feature.
-        In B-tree indexes, the values of columns listed in the
-        <literal>INCLUDE</literal> clause are included in leaf tuples which
-        correspond to heap tuples, but are not included in upper-level
+        Currently, the B-tree and the GiST index access methods supports this
+        feature.  In B-tree and the GiST indexes, the values of columns listed
+        in the <literal>INCLUDE</literal> clause are included in leaf tuples
+        which correspond to heap tuples, but are not included in upper-level
         index entries used for tree navigation.
        </para>
       </listitem>
index ecebade767db3961687577a5ae379a586086dd25..3281f7cd33af64547d5cb49aedafa5af49ec9b9d 100644 (file)
@@ -3674,6 +3674,12 @@ SELECT plainto_tsquery('supernovae stars');
    retrieved to see if the match is correct.
   </para>
 
+  <para>
+   A GiST index can be covering, i.e. use the <literal>INCLUDE</literal>
+   clause.  Included columns can have data types without any GiST operator
+   class.  Included attributes will be stored uncompressed.
+  </para>
+
   <para>
    Lossiness causes performance degradation due to unnecessary fetches of table
    records that turn out to be false matches.  Since random access to table
index 5ea774661a9bff365804acd1b18220096dd5dd52..2ce5425ef988ba8bcf3c8e976cf995fb44499c15 100644 (file)
@@ -75,7 +75,7 @@ gisthandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = true;
        amroutine->ampredlocks = true;
        amroutine->amcanparallel = false;
-       amroutine->amcaninclude = false;
+       amroutine->amcaninclude = true;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = gistbuild;
@@ -1382,8 +1382,10 @@ gistSplit(Relation r,
                                                IndexTupleSize(itup[0]), GiSTPageSize,
                                                RelationGetRelationName(r))));
 
-       memset(v.spl_lisnull, true, sizeof(bool) * giststate->tupdesc->natts);
-       memset(v.spl_risnull, true, sizeof(bool) * giststate->tupdesc->natts);
+       memset(v.spl_lisnull, true,
+                  sizeof(bool) * giststate->nonLeafTupdesc->natts);
+       memset(v.spl_risnull, true,
+                  sizeof(bool) * giststate->nonLeafTupdesc->natts);
        gistSplitByKey(r, page, itup, len, giststate, &v, 0);
 
        /* form left and right vector */
@@ -1461,9 +1463,23 @@ initGISTstate(Relation index)
 
        giststate->scanCxt = scanCxt;
        giststate->tempCxt = scanCxt;   /* caller must change this if needed */
-       giststate->tupdesc = index->rd_att;
+       giststate->leafTupdesc = index->rd_att;
 
-       for (i = 0; i < index->rd_att->natts; i++)
+       /*
+        * The truncated tupdesc for non-leaf index tuples, which doesn't contain
+        * the INCLUDE attributes.
+        *
+        * It is used to form tuples during tuple adjustement and page split.
+        * B-tree creates shortened tuple descriptor for every truncated tuple,
+        * because it is doing this less often: it does not have to form truncated
+        * tuples during page split.  Also, B-tree is not adjusting tuples on
+        * internal pages the way GiST does.
+        */
+       giststate->nonLeafTupdesc = CreateTupleDescCopyConstr(index->rd_att);
+       giststate->nonLeafTupdesc->natts =
+               IndexRelationGetNumberOfKeyAttributes(index);
+
+       for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(index); i++)
        {
                fmgr_info_copy(&(giststate->consistentFn[i]),
                                           index_getprocinfo(index, i + 1, GIST_CONSISTENT_PROC),
@@ -1531,6 +1547,21 @@ initGISTstate(Relation index)
                        giststate->supportCollation[i] = DEFAULT_COLLATION_OID;
        }
 
+       /* No opclass information for INCLUDE attributes */
+       for (; i < index->rd_att->natts; i++)
+       {
+               giststate->consistentFn[i].fn_oid = InvalidOid;
+               giststate->unionFn[i].fn_oid = InvalidOid;
+               giststate->compressFn[i].fn_oid = InvalidOid;
+               giststate->decompressFn[i].fn_oid = InvalidOid;
+               giststate->penaltyFn[i].fn_oid = InvalidOid;
+               giststate->picksplitFn[i].fn_oid = InvalidOid;
+               giststate->equalFn[i].fn_oid = InvalidOid;
+               giststate->distanceFn[i].fn_oid = InvalidOid;
+               giststate->fetchFn[i].fn_oid = InvalidOid;
+               giststate->supportCollation[i] = InvalidOid;
+       }
+
        MemoryContextSwitchTo(oldCxt);
 
        return giststate;
index a96ef5c3accfa77eac45072fba6be47791b1badd..156b9d699f775c0d4474a6984fca66763b0a33a0 100644 (file)
@@ -164,7 +164,7 @@ gistindex_keytest(IndexScanDesc scan,
 
                datum = index_getattr(tuple,
                                                          key->sk_attno,
-                                                         giststate->tupdesc,
+                                                         giststate->leafTupdesc,
                                                          &isNull);
 
                if (key->sk_flags & SK_ISNULL)
@@ -244,7 +244,7 @@ gistindex_keytest(IndexScanDesc scan,
 
                datum = index_getattr(tuple,
                                                          key->sk_attno,
-                                                         giststate->tupdesc,
+                                                         giststate->leafTupdesc,
                                                          &isNull);
 
                if ((key->sk_flags & SK_ISNULL) || isNull)
@@ -769,11 +769,13 @@ gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
  *
  * Opclasses that implement a fetch function support index-only scans.
  * Opclasses without compression functions also support index-only scans.
+ * Included attributes always can be fetched for index-only scans.
  */
 bool
 gistcanreturn(Relation index, int attno)
 {
-       if (OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)) ||
+       if (attno > IndexRelationGetNumberOfKeyAttributes(index) ||
+               OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)) ||
                !OidIsValid(index_getprocid(index, attno, GIST_COMPRESS_PROC)))
                return true;
        else
index 78a8ede7949782fe7c3a165e3904ddd9bc75a619..893d7765b6cd77208f83180ed6c8feec4a9a2c10 100644 (file)
@@ -158,6 +158,7 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
        if (scan->xs_want_itup && !scan->xs_hitupdesc)
        {
                int                     natts;
+               int                     nkeyatts;
                int                     attno;
 
                /*
@@ -167,13 +168,23 @@ gistrescan(IndexScanDesc scan, ScanKey key, int nkeys,
                 * types.
                 */
                natts = RelationGetNumberOfAttributes(scan->indexRelation);
+               nkeyatts = IndexRelationGetNumberOfKeyAttributes(scan->indexRelation);
                so->giststate->fetchTupdesc = CreateTemplateTupleDesc(natts);
-               for (attno = 1; attno <= natts; attno++)
+               for (attno = 1; attno <= nkeyatts; attno++)
                {
                        TupleDescInitEntry(so->giststate->fetchTupdesc, attno, NULL,
                                                           scan->indexRelation->rd_opcintype[attno - 1],
                                                           -1, 0);
                }
+
+               for (; attno <= natts; attno++)
+               {
+                       /* taking opcintype from giststate->tupdesc */
+                       TupleDescInitEntry(so->giststate->fetchTupdesc, attno, NULL,
+                                                          TupleDescAttr(so->giststate->leafTupdesc,
+                                                                                        attno - 1)->atttypid,
+                                                          -1, 0);
+               }
                scan->xs_hitupdesc = so->giststate->fetchTupdesc;
 
                /* Also create a memory context that will hold the returned tuples */
index f210e0c39f4753fbfaedad1243ccb8f493c0dd36..6a9c54d86ced36dc93f025061a8f1a4add08a0f2 100644 (file)
@@ -207,7 +207,7 @@ placeOne(Relation r, GISTSTATE *giststate, GistSplitVector *v,
        gistDeCompressAtt(giststate, r, itup, NULL, (OffsetNumber) 0,
                                          identry, isnull);
 
-       for (; attno < giststate->tupdesc->natts; attno++)
+       for (; attno < giststate->nonLeafTupdesc->natts; attno++)
        {
                float           lpenalty,
                                        rpenalty;
@@ -485,7 +485,7 @@ gistUserPicksplit(Relation r, GistEntryVector *entryvec, int attno, GistSplitVec
         */
        v->spl_dontcare = NULL;
 
-       if (attno + 1 < giststate->tupdesc->natts)
+       if (attno + 1 < giststate->nonLeafTupdesc->natts)
        {
                int                     NumDontCare;
 
@@ -639,7 +639,7 @@ gistSplitByKey(Relation r, Page page, IndexTuple *itup, int len,
                Datum           datum;
                bool            IsNull;
 
-               datum = index_getattr(itup[i - 1], attno + 1, giststate->tupdesc,
+               datum = index_getattr(itup[i - 1], attno + 1, giststate->leafTupdesc,
                                                          &IsNull);
                gistdentryinit(giststate, attno, &(entryvec->vector[i]),
                                           datum, r, page, i,
@@ -657,7 +657,7 @@ gistSplitByKey(Relation r, Page page, IndexTuple *itup, int len,
                 */
                v->spl_risnull[attno] = v->spl_lisnull[attno] = true;
 
-               if (attno + 1 < giststate->tupdesc->natts)
+               if (attno + 1 < giststate->nonLeafTupdesc->natts)
                        gistSplitByKey(r, page, itup, len, giststate, v, attno + 1);
                else
                        gistSplitHalf(&v->splitVector, len);
@@ -683,7 +683,7 @@ gistSplitByKey(Relation r, Page page, IndexTuple *itup, int len,
                                v->splitVector.spl_left[v->splitVector.spl_nleft++] = i;
 
                /* Compute union keys, unless outer recursion level will handle it */
-               if (attno == 0 && giststate->tupdesc->natts == 1)
+               if (attno == 0 && giststate->nonLeafTupdesc->natts == 1)
                {
                        v->spl_dontcare = NULL;
                        gistunionsubkey(giststate, itup, v);
@@ -700,7 +700,7 @@ gistSplitByKey(Relation r, Page page, IndexTuple *itup, int len,
                         * Splitting on attno column is not optimal, so consider
                         * redistributing don't-care tuples according to the next column
                         */
-                       Assert(attno + 1 < giststate->tupdesc->natts);
+                       Assert(attno + 1 < giststate->nonLeafTupdesc->natts);
 
                        if (v->spl_dontcare == NULL)
                        {
@@ -771,7 +771,7 @@ gistSplitByKey(Relation r, Page page, IndexTuple *itup, int len,
         * that PickSplit (or the special cases above) produced correct union
         * datums.
         */
-       if (attno == 0 && giststate->tupdesc->natts > 1)
+       if (attno == 0 && giststate->nonLeafTupdesc->natts > 1)
        {
                v->spl_dontcare = NULL;
                gistunionsubkey(giststate, itup, v);
index 8d3dfad27bd1707ac104e68c0d857c9a2282c4e2..f32e16eed58dc09c9dff65934e54f583960b6b61 100644 (file)
@@ -160,7 +160,7 @@ gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len,
 
        evec = (GistEntryVector *) palloc((len + 2) * sizeof(GISTENTRY) + GEVHDRSZ);
 
-       for (i = 0; i < giststate->tupdesc->natts; i++)
+       for (i = 0; i < giststate->nonLeafTupdesc->natts; i++)
        {
                int                     j;
 
@@ -171,7 +171,8 @@ gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len,
                        Datum           datum;
                        bool            IsNull;
 
-                       datum = index_getattr(itvec[j], i + 1, giststate->tupdesc, &IsNull);
+                       datum = index_getattr(itvec[j], i + 1, giststate->leafTupdesc,
+                                                                 &IsNull);
                        if (IsNull)
                                continue;
 
@@ -296,11 +297,11 @@ gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
 {
        int                     i;
 
-       for (i = 0; i < r->rd_att->natts; i++)
+       for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
        {
                Datum           datum;
 
-               datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+               datum = index_getattr(tuple, i + 1, giststate->leafTupdesc, &isnull[i]);
                gistdentryinit(giststate, i, &attdata[i],
                                           datum, r, p, o,
                                           false, isnull[i]);
@@ -329,7 +330,7 @@ gistgetadjusted(Relation r, IndexTuple oldtup, IndexTuple addtup, GISTSTATE *gis
        gistDeCompressAtt(giststate, r, addtup, NULL,
                                          (OffsetNumber) 0, addentries, addisnull);
 
-       for (i = 0; i < r->rd_att->natts; i++)
+       for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
        {
                gistMakeUnionKey(giststate, i,
                                                 oldentries + i, oldisnull[i],
@@ -442,14 +443,15 @@ gistchoose(Relation r, Page p, IndexTuple it,     /* it has compressed entry */
                zero_penalty = true;
 
                /* Loop over index attributes. */
-               for (j = 0; j < r->rd_att->natts; j++)
+               for (j = 0; j < IndexRelationGetNumberOfKeyAttributes(r); j++)
                {
                        Datum           datum;
                        float           usize;
                        bool            IsNull;
 
                        /* Compute penalty for this column. */
-                       datum = index_getattr(itup, j + 1, giststate->tupdesc, &IsNull);
+                       datum = index_getattr(itup, j + 1, giststate->leafTupdesc,
+                                                                 &IsNull);
                        gistdentryinit(giststate, j, &entry, datum, r, p, i,
                                                   false, IsNull);
                        usize = gistpenalty(giststate, j, &entry, IsNull,
@@ -470,7 +472,7 @@ gistchoose(Relation r, Page p, IndexTuple it,       /* it has compressed entry */
                                result = i;
                                best_penalty[j] = usize;
 
-                               if (j < r->rd_att->natts - 1)
+                               if (j < IndexRelationGetNumberOfKeyAttributes(r) - 1)
                                        best_penalty[j + 1] = -1;
 
                                /* we have new best, so reset keep-it decision */
@@ -500,7 +502,7 @@ gistchoose(Relation r, Page p, IndexTuple it,       /* it has compressed entry */
                 * If we looped past the last column, and did not update "result",
                 * then this tuple is exactly as good as the prior best tuple.
                 */
-               if (j == r->rd_att->natts && result != i)
+               if (j == IndexRelationGetNumberOfKeyAttributes(r) && result != i)
                {
                        if (keep_current_best == -1)
                        {
@@ -579,7 +581,7 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
        /*
         * Call the compress method on each attribute.
         */
-       for (i = 0; i < r->rd_att->natts; i++)
+       for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
        {
                if (isnull[i])
                        compatt[i] = (Datum) 0;
@@ -602,7 +604,23 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
                }
        }
 
-       res = index_form_tuple(giststate->tupdesc, compatt, isnull);
+       if (isleaf)
+       {
+               /*
+                * Emplace each included attribute if any.
+                */
+               for (; i < r->rd_att->natts; i++)
+               {
+                       if (isnull[i])
+                               compatt[i] = (Datum) 0;
+                       else
+                               compatt[i] = attdata[i];
+               }
+       }
+
+       res = index_form_tuple(isleaf ? giststate->leafTupdesc :
+                                                  giststate->nonLeafTupdesc,
+                                                  compatt, isnull);
 
        /*
         * The offset number on tuples on internal pages is unused. For historical
@@ -644,11 +662,11 @@ gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple)
        bool            isnull[INDEX_MAX_KEYS];
        int                     i;
 
-       for (i = 0; i < r->rd_att->natts; i++)
+       for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
        {
                Datum           datum;
 
-               datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
+               datum = index_getattr(tuple, i + 1, giststate->leafTupdesc, &isnull[i]);
 
                if (giststate->fetchFn[i].fn_oid != InvalidOid)
                {
@@ -679,6 +697,15 @@ gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple)
                        fetchatt[i] = (Datum) 0;
                }
        }
+
+       /*
+        * Get each included attribute.
+        */
+       for (; i < r->rd_att->natts; i++)
+       {
+               fetchatt[i] = index_getattr(tuple, i + 1, giststate->leafTupdesc,
+                                                                       &isnull[i]);
+       }
        MemoryContextSwitchTo(oldcxt);
 
        return heap_form_tuple(giststate->fetchTupdesc, fetchatt, isnull);
index 3698942f9daad71afaece7bf632b5197e93c55c5..463d2bfc7b9240f04bcb51733dcffdd2c5470295 100644 (file)
@@ -78,7 +78,9 @@ typedef struct GISTSTATE
        MemoryContext scanCxt;          /* context for scan-lifespan data */
        MemoryContext tempCxt;          /* short-term context for calling functions */
 
-       TupleDesc       tupdesc;                /* index's tuple descriptor */
+       TupleDesc       leafTupdesc;    /* index's tuple descriptor */
+       TupleDesc       nonLeafTupdesc; /* truncated tuple descriptor for non-leaf
+                                                                * pages */
        TupleDesc       fetchTupdesc;   /* tuple descriptor for tuples returned in an
                                                                 * index-only scan */
 
index 4570a39b058795d26ad61f4f1d46411988cb80b9..d92a6d12c622c2c8117992c0ef58d7c5903b6cc7 100644 (file)
@@ -75,7 +75,7 @@ select prop,
  can_unique         | f  |       | 
  can_multi_col      | t  |       | 
  can_exclude        | t  |       | 
- can_include        | f  |       | 
+ can_include        | t  |       | 
  bogus              |    |       | 
 (19 rows)
 
@@ -159,7 +159,7 @@ select amname, prop, pg_indexam_has_property(a.oid, prop) as p
  gist   | can_unique    | f
  gist   | can_multi_col | t
  gist   | can_exclude   | t
- gist   | can_include   | f
+ gist   | can_include   | t
  gist   | bogus         | 
  hash   | can_order     | f
  hash   | can_unique    | f
index f86e5953facc6931fa4c51a35e49e46fb16b6864..77ec29f2a33ef7be8410153847ccce3edaaac01e 100644 (file)
@@ -330,22 +330,20 @@ SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
 
 DROP TABLE tbl;
 /*
- * 7. Check various AMs. All but btree must fail.
+ * 7. Check various AMs. All but btree and gist must fail.
  */
 CREATE TABLE tbl (c1 int,c2 int, c3 box, c4 box);
 CREATE INDEX on tbl USING brin(c1, c2) INCLUDE (c3, c4);
 ERROR:  access method "brin" does not support included columns
-CREATE INDEX on tbl USING gist(c3) INCLUDE (c4);
-ERROR:  access method "gist" does not support included columns
+CREATE INDEX on tbl USING gist(c3) INCLUDE (c1, 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);
 ERROR:  access method "gin" does not support included columns
 CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4);
 ERROR:  access method "hash" does not support included columns
-CREATE INDEX on tbl USING rtree(c1, c2) INCLUDE (c3, c4);
+CREATE INDEX on tbl USING rtree(c3) INCLUDE (c1, c4);
 NOTICE:  substituting access method "gist" for obsolete method "rtree"
-ERROR:  access method "gist" does not support included columns
 CREATE INDEX on tbl USING btree(c1, c2) INCLUDE (c3, c4);
 DROP TABLE tbl;
 /*
diff --git a/src/test/regress/expected/index_including_gist.out b/src/test/regress/expected/index_including_gist.out
new file mode 100644 (file)
index 0000000..ed9906d
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 1.1. test CREATE INDEX with buffered build
+ */
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+                                  pg_get_indexdef                                  
+-----------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ c1 | c2 | c3 |     c4      
+----+----+----+-------------
+  1 |  2 |  3 | (2,3),(1,2)
+  2 |  4 |  6 | (4,5),(2,3)
+  3 |  6 |  9 | (6,7),(3,4)
+  4 |  8 | 12 | (8,9),(4,5)
+(4 rows)
+
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using tbl_gist_idx on tbl_gist
+   Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+/*
+ * 1.2. test CREATE INDEX with inserts
+ */
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+                                  pg_get_indexdef                                  
+-----------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+ c1 | c2 | c3 |     c4      
+----+----+----+-------------
+  1 |  2 |  3 | (2,3),(1,2)
+  2 |  4 |  6 | (4,5),(2,3)
+  3 |  6 |  9 | (6,7),(3,4)
+  4 |  8 | 12 | (8,9),(4,5)
+(4 rows)
+
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+                   QUERY PLAN                   
+------------------------------------------------
+ Index Only Scan using tbl_gist_idx on tbl_gist
+   Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+/*
+ * 2. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX CONCURRENTLY tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+                                     indexdef                                      
+-----------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3)
+(1 row)
+
+DROP TABLE tbl_gist;
+/*
+ * 3. REINDEX
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+                                   indexdef                                    
+-------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c3)
+(1 row)
+
+REINDEX INDEX tbl_gist_idx;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+                                   indexdef                                    
+-------------------------------------------------------------------------------
+ CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c3)
+(1 row)
+
+ALTER TABLE tbl_gist DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+DROP TABLE tbl_gist;
+/*
+ * 4. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+UPDATE tbl_gist SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl_gist SET c1 = 1 WHERE c1 = 3;
+DELETE FROM tbl_gist WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl_gist;
+/*
+ * 5. Alter column type.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+ALTER TABLE tbl_gist ALTER c1 TYPE bigint;
+ALTER TABLE tbl_gist ALTER c3 TYPE bigint;
+\d tbl_gist
+              Table "public.tbl_gist"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ c1     | bigint  |           |          | 
+ c2     | integer |           |          | 
+ c3     | bigint  |           |          | 
+ c4     | box     |           |          | 
+Indexes:
+    "tbl_gist_idx" gist (c4) INCLUDE (c1, c3)
+
+DROP TABLE tbl_gist;
+/*
+ * 6. EXCLUDE constraint.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box, EXCLUDE USING gist (c4 WITH &&) INCLUDE (c1, c2, c3));
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+ERROR:  conflicting key value violates exclusion constraint "tbl_gist_c4_c1_c2_c3_excl"
+DETAIL:  Key (c4)=((4,5),(2,3)) conflicts with existing key (c4)=((2,3),(1,2)).
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(3*x,2*x),point(3*x+1,2*x+1)) FROM generate_series(1,10) AS x;
+EXPLAIN  (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Index Only Scan using tbl_gist_c4_c1_c2_c3_excl on tbl_gist
+   Index Cond: (c4 <@ '(10,10),(1,1)'::box)
+(2 rows)
+
+\d tbl_gist
+              Table "public.tbl_gist"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ c1     | integer |           |          | 
+ c2     | integer |           |          | 
+ c3     | integer |           |          | 
+ c4     | box     |           |          | 
+Indexes:
+    "tbl_gist_c4_c1_c2_c3_excl" EXCLUDE USING gist (c4 WITH &&) INCLUDE (c1, c2, c3)
+
+DROP TABLE tbl_gist;
index 4051a4ad4e15b8a1122d92fd7e9201a0b1e6438b..ace703179e8fef1eeb3948f588a4d3fd40ca93a9 100644 (file)
@@ -55,7 +55,7 @@ test: copy copyselect copydml
 # ----------
 test: create_misc create_operator create_procedure
 # These depend on the above two
-test: create_index create_view index_including
+test: create_index create_view index_including index_including_gist
 
 # ----------
 # Another group of parallel tests
index ac1ea622d65997e5b1822faf77d69334626afacd..309a907ece853a299f731388d9ed10a06a011774 100644 (file)
@@ -63,6 +63,7 @@ test: create_operator
 test: create_procedure
 test: create_index
 test: index_including
+test: index_including_gist
 test: create_view
 test: create_aggregate
 test: create_function_3
index 2c6dd6211e650b4ac47d8366af5461124ca99386..c0ae71d0cbd798931b400967d6f86b5df915ffa2 100644 (file)
@@ -176,15 +176,15 @@ SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
 DROP TABLE tbl;
 
 /*
- * 7. Check various AMs. All but btree must fail.
+ * 7. Check various AMs. All but btree and gist must fail.
  */
 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 gist(c3) INCLUDE (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 gin(c1, c2) INCLUDE (c3, c4);
 CREATE INDEX on tbl USING hash(c1, c2) INCLUDE (c3, c4);
-CREATE INDEX on tbl USING rtree(c1, c2) INCLUDE (c3, c4);
+CREATE INDEX on tbl USING rtree(c3) INCLUDE (c1, c4);
 CREATE INDEX on tbl USING btree(c1, c2) INCLUDE (c3, c4);
 DROP TABLE tbl;
 
diff --git a/src/test/regress/sql/index_including_gist.sql b/src/test/regress/sql/index_including_gist.sql
new file mode 100644 (file)
index 0000000..7d5c99b
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 1.1. test CREATE INDEX with buffered build
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+
+/*
+ * 1.2. test CREATE INDEX with inserts
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+-- size is chosen to exceed page size and trigger actual truncation
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname;
+SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO off;
+EXPLAIN  (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+SET enable_bitmapscan TO default;
+DROP TABLE tbl_gist;
+
+/*
+ * 2. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX CONCURRENTLY tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+DROP TABLE tbl_gist;
+
+
+/*
+ * 3. REINDEX
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+REINDEX INDEX tbl_gist_idx;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+ALTER TABLE tbl_gist DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl_gist' ORDER BY indexname;
+DROP TABLE tbl_gist;
+
+/*
+ * 4. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+UPDATE tbl_gist SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl_gist SET c1 = 1 WHERE c1 = 3;
+DELETE FROM tbl_gist WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl_gist;
+
+/*
+ * 5. Alter column type.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c3);
+ALTER TABLE tbl_gist ALTER c1 TYPE bigint;
+ALTER TABLE tbl_gist ALTER c3 TYPE bigint;
+\d tbl_gist
+DROP TABLE tbl_gist;
+
+/*
+ * 6. EXCLUDE constraint.
+ */
+CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box, EXCLUDE USING gist (c4 WITH &&) INCLUDE (c1, c2, c3));
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,10) AS x;
+INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(3*x,2*x),point(3*x+1,2*x+1)) FROM generate_series(1,10) AS x;
+EXPLAIN  (costs off) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10));
+\d tbl_gist
+DROP TABLE tbl_gist;