]> granicus.if.org Git - postgresql/commitdiff
Indexes with INCLUDE columns and their support in B-tree
authorTeodor Sigaev <teodor@sigaev.ru>
Sat, 7 Apr 2018 20:00:39 +0000 (23:00 +0300)
committerTeodor Sigaev <teodor@sigaev.ru>
Sat, 7 Apr 2018 20:00:39 +0000 (23:00 +0300)
This patch introduces INCLUDE clause to index definition.  This clause
specifies a list of columns which will be included as a non-key part in
the index.  The INCLUDE columns exist solely to allow more queries to
benefit from index-only scans.  Also, such columns don't need to have
appropriate operator classes.  Expressions are not supported as INCLUDE
columns since they cannot be used in index-only scans.

Index access methods supporting INCLUDE are indicated by amcaninclude flag
in IndexAmRoutine.  For now, only B-tree indexes support INCLUDE clause.

In B-tree indexes INCLUDE columns are truncated from pivot index tuples
(tuples located in non-leaf pages and high keys).  Therefore, B-tree indexes
now might have variable number of attributes.  This patch also provides
generic facility to support that: pivot tuples contain number of their
attributes in t_tid.ip_posid.  Free 13th bit of t_info is used for indicating
that.  This facility will simplify further support of index suffix truncation.
The changes of above are backward-compatible, pg_upgrade doesn't need special
handling of B-tree indexes for that.

Bump catalog version

Author: Anastasia Lubennikova with contribition by Alexander Korotkov and me
Reviewed by: Peter Geoghegan, Tomas Vondra, Antonin Houska, Jeff Janes,
 David Rowley, Alexander Korotkov
Discussion: https://www.postgresql.org/message-id/flat/56168952.4010101@postgrespro.ru

89 files changed:
contrib/amcheck/expected/check_btree.out
contrib/amcheck/sql/check_btree.sql
contrib/amcheck/verify_nbtree.c
contrib/bloom/blutils.c
contrib/dblink/dblink.c
contrib/dblink/expected/dblink.out
contrib/dblink/sql/dblink.sql
contrib/tcn/tcn.c
doc/src/sgml/btree.sgml
doc/src/sgml/catalogs.sgml
doc/src/sgml/indexam.sgml
doc/src/sgml/indices.sgml
doc/src/sgml/ref/create_index.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/access/brin/brin.c
src/backend/access/common/indextuple.c
src/backend/access/gin/ginutil.c
src/backend/access/gist/gist.c
src/backend/access/hash/hash.c
src/backend/access/heap/heapam.c
src/backend/access/index/genam.c
src/backend/access/nbtree/README
src/backend/access/nbtree/nbtinsert.c
src/backend/access/nbtree/nbtpage.c
src/backend/access/nbtree/nbtree.c
src/backend/access/nbtree/nbtsearch.c
src/backend/access/nbtree/nbtsort.c
src/backend/access/nbtree/nbtutils.c
src/backend/access/nbtree/nbtxlog.c
src/backend/access/rmgrdesc/nbtdesc.c
src/backend/access/spgist/spgutils.c
src/backend/bootstrap/bootparse.y
src/backend/bootstrap/bootstrap.c
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/catalog/indexing.c
src/backend/catalog/pg_constraint.c
src/backend/catalog/toasting.c
src/backend/commands/indexcmds.c
src/backend/commands/matview.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/commands/typecmds.c
src/backend/executor/execIndexing.c
src/backend/executor/execReplication.c
src/backend/executor/nodeIndexscan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/README
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/path/pathkeys.c
src/backend/optimizer/util/plancat.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/parser/parse_utilcmd.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/adt/selfuncs.c
src/backend/utils/cache/relcache.c
src/backend/utils/sort/tuplesort.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/include/access/amapi.h
src/include/access/hash.h
src/include/access/itup.h
src/include/access/nbtree.h
src/include/access/nbtxlog.h
src/include/catalog/catversion.h
src/include/catalog/pg_constraint.h
src/include/catalog/pg_constraint_fn.h
src/include/catalog/pg_index.h
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/include/nodes/relation.h
src/include/parser/kwlist.h
src/include/utils/rel.h
src/test/isolation/specs/insert-conflict-do-nothing-2.spec
src/test/isolation/specs/insert-conflict-do-update-2.spec
src/test/isolation/specs/lock-committed-keyupdate.spec
src/test/isolation/specs/lock-update-traversal.spec
src/test/regress/expected/create_index.out
src/test/regress/expected/index_including.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/create_index.sql
src/test/regress/sql/index_including.sql [new file with mode: 0644]
src/test/subscription/t/001_rep_changes.pl

index 6f5b91754de5df6f8d125f2a172fa28e4534de69..2a06cce9a0e2ffccb36697e6b17e26f5d2e463da 100644 (file)
@@ -1,10 +1,14 @@
 -- minimal test, basically just verifying that amcheck
 CREATE TABLE bttest_a(id int8);
 CREATE TABLE bttest_b(id int8);
+CREATE TABLE bttest_multi(id int8, data int8);
 INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000);
 INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
+INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
 CREATE INDEX bttest_a_idx ON bttest_a USING btree (id);
 CREATE INDEX bttest_b_idx ON bttest_b USING btree (id);
+CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi
+USING btree (id) INCLUDE (data);
 CREATE ROLE bttest_role;
 -- verify permissions are checked (error due to function not callable)
 SET ROLE bttest_role;
@@ -93,8 +97,50 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
 (0 rows)
 
 COMMIT;
+-- normal check outside of xact for index with included columns
+SELECT bt_index_check('bttest_multi_idx');
+ bt_index_check 
+----------------
+(1 row)
+
+-- more expansive test for index with included columns
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+ bt_index_parent_check 
+-----------------------
+(1 row)
+
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+ bt_index_parent_check 
+-----------------------
+(1 row)
+
+-- repeat same checks with index made by insertions
+TRUNCATE bttest_multi;
+INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
+SELECT bt_index_check('bttest_multi_idx');
+ bt_index_check 
+----------------
+(1 row)
+
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+ bt_index_parent_check 
+-----------------------
+(1 row)
+
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+ bt_index_parent_check 
+-----------------------
+(1 row)
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
+DROP TABLE bttest_multi;
 DROP OWNED BY bttest_role; -- permissions
 DROP ROLE bttest_role;
index 03f4c96b9e9c511821389f30f274f9f82238e444..da2f1314e57fc1659e41f5ce3c08c4f6f46169c3 100644 (file)
@@ -1,12 +1,16 @@
 -- minimal test, basically just verifying that amcheck
 CREATE TABLE bttest_a(id int8);
 CREATE TABLE bttest_b(id int8);
+CREATE TABLE bttest_multi(id int8, data int8);
 
 INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000);
 INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
+INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
 
 CREATE INDEX bttest_a_idx ON bttest_a USING btree (id);
 CREATE INDEX bttest_b_idx ON bttest_b USING btree (id);
+CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi
+USING btree (id) INCLUDE (data);
 
 CREATE ROLE bttest_role;
 
@@ -57,8 +61,23 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
     AND pid = pg_backend_pid();
 COMMIT;
 
+-- normal check outside of xact for index with included columns
+SELECT bt_index_check('bttest_multi_idx');
+-- more expansive test for index with included columns
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+
+-- repeat same checks with index made by insertions
+TRUNCATE bttest_multi;
+INSERT INTO bttest_multi SELECT i, i%2  FROM generate_series(1, 100000) as i;
+SELECT bt_index_check('bttest_multi_idx');
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+SELECT bt_index_parent_check('bttest_multi_idx', true);
+
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
+DROP TABLE bttest_multi;
 DROP OWNED BY bttest_role; -- permissions
 DROP ROLE bttest_role;
index 52aa633056b75dd929ac758493ebbca026e2e0c0..be0206d58ed1658a903374577320d6a874febefe 100644 (file)
@@ -617,7 +617,7 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
                                /* Internal page -- downlink gets leftmost on next level */
                                itemid = PageGetItemId(state->target, P_FIRSTDATAKEY(opaque));
                                itup = (IndexTuple) PageGetItem(state->target, itemid);
-                               nextleveldown.leftmost = ItemPointerGetBlockNumber(&(itup->t_tid));
+                               nextleveldown.leftmost = ItemPointerGetBlockNumberNoCheck(&(itup->t_tid));
                                nextleveldown.level = opaque->btpo.level - 1;
                        }
                        else
@@ -722,6 +722,39 @@ bt_target_page_check(BtreeCheckState *state)
        elog(DEBUG2, "verifying %u items on %s block %u", max,
                 P_ISLEAF(topaque) ? "leaf" : "internal", state->targetblock);
 
+
+       /* Check the number of attributes in high key if any */
+       if (!P_RIGHTMOST(topaque))
+       {
+               if (!_bt_check_natts(state->rel, state->target, P_HIKEY))
+               {
+                       ItemId          itemid;
+                       IndexTuple      itup;
+                       char       *itid,
+                                          *htid;
+
+                       itemid = PageGetItemId(state->target, P_HIKEY);
+                       itup = (IndexTuple) PageGetItem(state->target, itemid);
+                       itid = psprintf("(%u,%u)", state->targetblock, P_HIKEY);
+                       htid = psprintf("(%u,%u)",
+                                                       ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+                                                       ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
+
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INDEX_CORRUPTED),
+                                        errmsg("wrong number of index tuple attributes for index \"%s\"",
+                                                       RelationGetRelationName(state->rel)),
+                                        errdetail_internal("Index tid=%s natts=%u points to %s tid=%s page lsn=%X/%X.",
+                                                                               itid,
+                                                                               BTreeTupGetNAtts(itup, state->rel),
+                                                                               P_ISLEAF(topaque) ? "heap" : "index",
+                                                                               htid,
+                                                                               (uint32) (state->targetlsn >> 32),
+                                                                               (uint32) state->targetlsn)));
+               }
+       }
+
+
        /*
         * Loop over page items, starting from first non-highkey item, not high
         * key (if any).  Also, immediately skip "negative infinity" real item (if
@@ -760,6 +793,30 @@ bt_target_page_check(BtreeCheckState *state)
                                                                                (uint32) state->targetlsn),
                                         errhint("This could be a torn page problem")));
 
+               /* Check the number of index tuple attributes */
+               if (!_bt_check_natts(state->rel, state->target, offset))
+               {
+                       char       *itid,
+                                          *htid;
+
+                       itid = psprintf("(%u,%u)", state->targetblock, offset);
+                       htid = psprintf("(%u,%u)",
+                                                       ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+                                                       ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
+
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INDEX_CORRUPTED),
+                                        errmsg("wrong number of index tuple attributes for index \"%s\"",
+                                                       RelationGetRelationName(state->rel)),
+                                        errdetail_internal("Index tid=%s natts=%u points to %s tid=%s page lsn=%X/%X.",
+                                                                               itid,
+                                                                               BTreeTupGetNAtts(itup, state->rel),
+                                                                               P_ISLEAF(topaque) ? "heap" : "index",
+                                                                               htid,
+                                                                               (uint32) (state->targetlsn >> 32),
+                                                                               (uint32) state->targetlsn)));
+               }
+
                /*
                 * Don't try to generate scankey using "negative infinity" garbage
                 * data on internal pages
@@ -802,8 +859,8 @@ bt_target_page_check(BtreeCheckState *state)
 
                        itid = psprintf("(%u,%u)", state->targetblock, offset);
                        htid = psprintf("(%u,%u)",
-                                                       ItemPointerGetBlockNumber(&(itup->t_tid)),
-                                                       ItemPointerGetOffsetNumber(&(itup->t_tid)));
+                                                       ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+                                                       ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
 
                        ereport(ERROR,
                                        (errcode(ERRCODE_INDEX_CORRUPTED),
@@ -834,8 +891,8 @@ bt_target_page_check(BtreeCheckState *state)
 
                        itid = psprintf("(%u,%u)", state->targetblock, offset);
                        htid = psprintf("(%u,%u)",
-                                                       ItemPointerGetBlockNumber(&(itup->t_tid)),
-                                                       ItemPointerGetOffsetNumber(&(itup->t_tid)));
+                                                       ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+                                                       ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
                        nitid = psprintf("(%u,%u)", state->targetblock,
                                                         OffsetNumberNext(offset));
 
@@ -843,8 +900,8 @@ bt_target_page_check(BtreeCheckState *state)
                        itemid = PageGetItemId(state->target, OffsetNumberNext(offset));
                        itup = (IndexTuple) PageGetItem(state->target, itemid);
                        nhtid = psprintf("(%u,%u)",
-                                                        ItemPointerGetBlockNumber(&(itup->t_tid)),
-                                                        ItemPointerGetOffsetNumber(&(itup->t_tid)));
+                                                        ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+                                                        ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
 
                        ereport(ERROR,
                                        (errcode(ERRCODE_INDEX_CORRUPTED),
@@ -932,7 +989,7 @@ bt_target_page_check(BtreeCheckState *state)
                 */
                if (!P_ISLEAF(topaque) && state->readonly)
                {
-                       BlockNumber childblock = ItemPointerGetBlockNumber(&(itup->t_tid));
+                       BlockNumber childblock = ItemPointerGetBlockNumberNoCheck(&(itup->t_tid));
 
                        bt_downlink_check(state, childblock, skey);
                }
@@ -1326,6 +1383,11 @@ bt_tuple_present_callback(Relation index, HeapTuple htup, Datum *values,
         * or otherwise varied when or how compression was applied, our assumption
         * would break, leading to false positive reports of corruption.  For now,
         * we don't decompress/normalize toasted values as part of fingerprinting.
+        *
+        * In future, non-pivot index tuples might get use of
+        * BT_N_KEYS_OFFSET_MASK. Then binary representation of index tuple linked
+        * to particular heap tuple might vary and meeds to be normalized before
+        * bloom filter lookup.
         */
        itup = index_form_tuple(RelationGetDescr(index), values, isnull);
        itup->t_tid = htup->t_self;
@@ -1336,8 +1398,8 @@ bt_tuple_present_callback(Relation index, HeapTuple htup, Datum *values,
                ereport(ERROR,
                                (errcode(ERRCODE_DATA_CORRUPTED),
                                 errmsg("heap tuple (%u,%u) from table \"%s\" lacks matching index tuple within index \"%s\"",
-                                               ItemPointerGetBlockNumber(&(itup->t_tid)),
-                                               ItemPointerGetOffsetNumber(&(itup->t_tid)),
+                                               ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
+                                               ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)),
                                                RelationGetRelationName(state->heaprel),
                                                RelationGetRelationName(state->rel)),
                                 !state->readonly
@@ -1368,6 +1430,10 @@ offset_is_negative_infinity(BTPageOpaque opaque, OffsetNumber offset)
         * infinity item is either first or second line item, or there is none
         * within page.
         *
+        * "Negative infinity" tuple is a special corner case of pivot tuples,
+        * it has zero attributes while rest of pivot tuples have nkeyatts number
+        * of attributes.
+        *
         * Right-most pages don't have a high key, but could be said to
         * conceptually have a "positive infinity" high key.  Thus, there is a
         * symmetry between down link items in parent pages, and high keys in
@@ -1391,10 +1457,10 @@ static inline bool
 invariant_leq_offset(BtreeCheckState *state, ScanKey key,
                                         OffsetNumber upperbound)
 {
-       int16           natts = state->rel->rd_rel->relnatts;
+       int16           nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
        int32           cmp;
 
-       cmp = _bt_compare(state->rel, natts, key, state->target, upperbound);
+       cmp = _bt_compare(state->rel, nkeyatts, key, state->target, upperbound);
 
        return cmp <= 0;
 }
@@ -1410,10 +1476,10 @@ static inline bool
 invariant_geq_offset(BtreeCheckState *state, ScanKey key,
                                         OffsetNumber lowerbound)
 {
-       int16           natts = state->rel->rd_rel->relnatts;
+       int16           nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
        int32           cmp;
 
-       cmp = _bt_compare(state->rel, natts, key, state->target, lowerbound);
+       cmp = _bt_compare(state->rel, nkeyatts, key, state->target, lowerbound);
 
        return cmp >= 0;
 }
@@ -1433,10 +1499,10 @@ invariant_leq_nontarget_offset(BtreeCheckState *state,
                                                           Page nontarget, ScanKey key,
                                                           OffsetNumber upperbound)
 {
-       int16           natts = state->rel->rd_rel->relnatts;
+       int16           nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
        int32           cmp;
 
-       cmp = _bt_compare(state->rel, natts, key, nontarget, upperbound);
+       cmp = _bt_compare(state->rel, nkeyatts, key, nontarget, upperbound);
 
        return cmp <= 0;
 }
index bbe7183207e00b48fbff57217813fe1ed753d7a3..6b2b9e374263b5d9065ffe976fcc6d37ab8674f1 100644 (file)
@@ -120,6 +120,7 @@ blhandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = false;
        amroutine->ampredlocks = false;
        amroutine->amcanparallel = false;
+       amroutine->amcaninclude = false;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = blbuild;
index 8e5af5a62f7984f2035a762c68737df9ef575a3b..c6460688486544e006a6f31ebf6352a1274fce56 100644 (file)
@@ -100,7 +100,7 @@ static remoteConn *getConnectionByName(const char *name);
 static HTAB *createConnHash(void);
 static void createNewConnection(const char *name, remoteConn *rconn);
 static void deleteConnection(const char *name);
-static char **get_pkey_attnames(Relation rel, int16 *numatts);
+static char **get_pkey_attnames(Relation rel, int16 *indnkeyatts);
 static char **get_text_array_contents(ArrayType *array, int *numitems);
 static char *get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals);
 static char *get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals);
@@ -1493,7 +1493,7 @@ PG_FUNCTION_INFO_V1(dblink_get_pkey);
 Datum
 dblink_get_pkey(PG_FUNCTION_ARGS)
 {
-       int16           numatts;
+       int16           indnkeyatts;
        char      **results;
        FuncCallContext *funcctx;
        int32           call_cntr;
@@ -1519,7 +1519,7 @@ dblink_get_pkey(PG_FUNCTION_ARGS)
                rel = get_rel_from_relname(PG_GETARG_TEXT_PP(0), AccessShareLock, ACL_SELECT);
 
                /* get the array of attnums */
-               results = get_pkey_attnames(rel, &numatts);
+               results = get_pkey_attnames(rel, &indnkeyatts);
 
                relation_close(rel, AccessShareLock);
 
@@ -1539,9 +1539,9 @@ dblink_get_pkey(PG_FUNCTION_ARGS)
                attinmeta = TupleDescGetAttInMetadata(tupdesc);
                funcctx->attinmeta = attinmeta;
 
-               if ((results != NULL) && (numatts > 0))
+               if ((results != NULL) && (indnkeyatts > 0))
                {
-                       funcctx->max_calls = numatts;
+                       funcctx->max_calls = indnkeyatts;
 
                        /* got results, keep track of them */
                        funcctx->user_fctx = results;
@@ -2029,10 +2029,10 @@ dblink_fdw_validator(PG_FUNCTION_ARGS)
  * get_pkey_attnames
  *
  * Get the primary key attnames for the given relation.
- * Return NULL, and set numatts = 0, if no primary key exists.
+ * Return NULL, and set indnkeyatts = 0, if no primary key exists.
  */
 static char **
-get_pkey_attnames(Relation rel, int16 *numatts)
+get_pkey_attnames(Relation rel, int16 *indnkeyatts)
 {
        Relation        indexRelation;
        ScanKeyData skey;
@@ -2042,8 +2042,8 @@ get_pkey_attnames(Relation rel, int16 *numatts)
        char      **result = NULL;
        TupleDesc       tupdesc;
 
-       /* initialize numatts to 0 in case no primary key exists */
-       *numatts = 0;
+       /* initialize indnkeyatts to 0 in case no primary key exists */
+       *indnkeyatts = 0;
 
        tupdesc = rel->rd_att;
 
@@ -2064,12 +2064,12 @@ get_pkey_attnames(Relation rel, int16 *numatts)
                /* we're only interested if it is the primary key */
                if (index->indisprimary)
                {
-                       *numatts = index->indnatts;
-                       if (*numatts > 0)
+                       *indnkeyatts = index->indnkeyatts;
+                       if (*indnkeyatts > 0)
                        {
-                               result = (char **) palloc(*numatts * sizeof(char *));
+                               result = (char **) palloc(*indnkeyatts * sizeof(char *));
 
-                               for (i = 0; i < *numatts; i++)
+                               for (i = 0; i < *indnkeyatts; i++)
                                        result[i] = SPI_fname(tupdesc, index->indkey.values[i]);
                        }
                        break;
index dbcc6b08dba8c5b96787e7dee1296bd462e5665d..dfd49b937e87d2fc54ddab3e8b924121a0c05a3b 100644 (file)
@@ -54,6 +54,61 @@ SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}');
 -- too many pk fields, should fail
 SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
 ERROR:  invalid attribute number 4
+-- repeat the test for table with primary key index with included columns
+CREATE TABLE foo_1(f1 int, f2 text, f3 text[], primary key (f1,f2) include (f3));
+INSERT INTO foo_1 VALUES (0,'a','{"a0","b0","c0"}');
+INSERT INTO foo_1 VALUES (1,'b','{"a1","b1","c1"}');
+INSERT INTO foo_1 VALUES (2,'c','{"a2","b2","c2"}');
+INSERT INTO foo_1 VALUES (3,'d','{"a3","b3","c3"}');
+INSERT INTO foo_1 VALUES (4,'e','{"a4","b4","c4"}');
+INSERT INTO foo_1 VALUES (5,'f','{"a5","b5","c5"}');
+INSERT INTO foo_1 VALUES (6,'g','{"a6","b6","c6"}');
+INSERT INTO foo_1 VALUES (7,'h','{"a7","b7","c7"}');
+INSERT INTO foo_1 VALUES (8,'i','{"a8","b8","c8"}');
+INSERT INTO foo_1 VALUES (9,'j','{"a9","b9","c9"}');
+-- misc utilities
+-- list the primary key fields
+SELECT *
+FROM dblink_get_pkey('foo_1');
+ position | colname 
+----------+---------
+        1 | f1
+        2 | f2
+(2 rows)
+
+-- build an insert statement based on a local tuple,
+-- replacing the primary key values with new ones
+SELECT dblink_build_sql_insert('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}');
+                   dblink_build_sql_insert                   
+-------------------------------------------------------------
+ INSERT INTO foo_1(f1,f2,f3) VALUES('99','xyz','{a0,b0,c0}')
+(1 row)
+
+-- too many pk fields, should fail
+SELECT dblink_build_sql_insert('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}');
+ERROR:  invalid attribute number 4
+-- build an update statement based on a local tuple,
+-- replacing the primary key values with new ones
+SELECT dblink_build_sql_update('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}');
+                                 dblink_build_sql_update                                  
+------------------------------------------------------------------------------------------
+ UPDATE foo_1 SET f1 = '99', f2 = 'xyz', f3 = '{a0,b0,c0}' WHERE f1 = '99' AND f2 = 'xyz'
+(1 row)
+
+-- too many pk fields, should fail
+SELECT dblink_build_sql_update('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}');
+ERROR:  invalid attribute number 4
+-- build a delete statement based on a local tuple,
+SELECT dblink_build_sql_delete('foo_1','1 2',2,'{"0", "a"}');
+            dblink_build_sql_delete            
+-----------------------------------------------
+ DELETE FROM foo_1 WHERE f1 = '0' AND f2 = 'a'
+(1 row)
+
+-- too many pk fields, should fail
+SELECT dblink_build_sql_delete('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
+ERROR:  invalid attribute number 4
+DROP TABLE foo_1;
 -- retest using a quoted and schema qualified table
 CREATE SCHEMA "MySchema";
 CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
index b093fa6722f3344510e74ae5ae9c14805bb8d265..3e96b9857128a7429e39ac138f253cbb03931d7d 100644 (file)
@@ -38,6 +38,44 @@ SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}');
 -- too many pk fields, should fail
 SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
 
+-- repeat the test for table with primary key index with included columns
+CREATE TABLE foo_1(f1 int, f2 text, f3 text[], primary key (f1,f2) include (f3));
+INSERT INTO foo_1 VALUES (0,'a','{"a0","b0","c0"}');
+INSERT INTO foo_1 VALUES (1,'b','{"a1","b1","c1"}');
+INSERT INTO foo_1 VALUES (2,'c','{"a2","b2","c2"}');
+INSERT INTO foo_1 VALUES (3,'d','{"a3","b3","c3"}');
+INSERT INTO foo_1 VALUES (4,'e','{"a4","b4","c4"}');
+INSERT INTO foo_1 VALUES (5,'f','{"a5","b5","c5"}');
+INSERT INTO foo_1 VALUES (6,'g','{"a6","b6","c6"}');
+INSERT INTO foo_1 VALUES (7,'h','{"a7","b7","c7"}');
+INSERT INTO foo_1 VALUES (8,'i','{"a8","b8","c8"}');
+INSERT INTO foo_1 VALUES (9,'j','{"a9","b9","c9"}');
+
+-- misc utilities
+
+-- list the primary key fields
+SELECT *
+FROM dblink_get_pkey('foo_1');
+
+-- build an insert statement based on a local tuple,
+-- replacing the primary key values with new ones
+SELECT dblink_build_sql_insert('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}');
+-- too many pk fields, should fail
+SELECT dblink_build_sql_insert('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}');
+
+-- build an update statement based on a local tuple,
+-- replacing the primary key values with new ones
+SELECT dblink_build_sql_update('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}');
+-- too many pk fields, should fail
+SELECT dblink_build_sql_update('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}');
+
+-- build a delete statement based on a local tuple,
+SELECT dblink_build_sql_delete('foo_1','1 2',2,'{"0", "a"}');
+-- too many pk fields, should fail
+SELECT dblink_build_sql_delete('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
+
+DROP TABLE foo_1;
+
 -- retest using a quoted and schema qualified table
 CREATE SCHEMA "MySchema";
 CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
index 41186fdd8f795a346a9892584e6ff4ebfa504b61..43bdd92749d528a3e7871c28590f3c26d36f3a3d 100644 (file)
@@ -138,9 +138,9 @@ triggered_change_notification(PG_FUNCTION_ARGS)
                /* we're only interested if it is the primary key and valid */
                if (index->indisprimary && IndexIsValid(index))
                {
-                       int                     numatts = index->indnatts;
+                       int     indnkeyatts = index->indnkeyatts;
 
-                       if (numatts > 0)
+                       if (indnkeyatts > 0)
                        {
                                int                     i;
 
@@ -150,7 +150,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
                                appendStringInfoCharMacro(payload, ',');
                                appendStringInfoCharMacro(payload, operation);
 
-                               for (i = 0; i < numatts; i++)
+                               for (i = 0; i < indnkeyatts; i++)
                                {
                                        int                     colno = index->indkey.values[i];
                                        Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
index 10abf90189e6f12cd02848272057fc724b94f216..ca81fbbc84822ea5a1a68d9544f11ad74fe9e9ed 100644 (file)
@@ -433,6 +433,23 @@ returns bool
 
 </sect1>
 
+<sect1 id="btree-included-attributes">
+ <title>Included attributes in B-tree indexes</title>
+
+ <para>
+  As of <productname>PostgreSQL</productname> 11.0 there is an optional
+  INCLUDE clause, which allows to add non-key (included) attributes to index.
+  Those included attributes allow more queries to benefit from index-only scans.
+  We never use included attributes in ScanKeys for search.  That allows us to
+  include into B-tree any datatypes, even those which don't have suitable
+  operator classes.  Included columns only stored in regular tuples on leaf
+  pages.  All pivot tuples on non-leaf pages and highkey tuples are truncated
+  to contain only key attributes.  That helps to slightly reduce the size of
+  index.
+ </para>
+
+</sect1>
+
 <sect1 id="btree-implementation">
  <title>Implementation</title>
 
index e8efa13e8df9cf0d05d338f470521d7868a75611..c304262fdb652829ee306fccaac50d6d44c651b8 100644 (file)
@@ -3743,8 +3743,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry><structfield>indnatts</structfield></entry>
       <entry><type>int2</type></entry>
       <entry></entry>
-      <entry>The number of columns in the index (duplicates
-      <literal>pg_class.relnatts</literal>)</entry>
+      <entry>The total number of columns in the index (duplicates
+      <literal>pg_class.relnatts</literal>). This number includes both key and included attributes.</entry>
+     </row>
+
+      <row>
+      <entry><structfield>indnkeyatts</structfield></entry>
+      <entry><type>int2</type></entry>
+      <entry></entry>
+      <entry>The number of key columns in the index. "Key columns" are ordinary
+      index columns (as opposed to "included" columns).</entry>
      </row>
 
      <row>
index a7f6c8dc6ad3546c35b359f65fa56321a1771e12..24c3405f918ccf26483651781e341ebac4c9629f 100644 (file)
@@ -114,6 +114,8 @@ typedef struct IndexAmRoutine
     bool        amcanparallel;
     /* type of data stored in index, or InvalidOid if variable */
     Oid         amkeytype;
+    /* does AM support columns included with clause INCLUDE? */
+    bool        amcaninclude;
 
     /* interface functions */
     ambuild_function ambuild;
@@ -985,7 +987,8 @@ amparallelrescan (IndexScanDesc scan);
    using <firstterm>unique indexes</firstterm>, which are indexes that disallow
    multiple entries with identical keys.  An access method that supports this
    feature sets <structfield>amcanunique</structfield> true.
-   (At present, only b-tree supports it.)
+   (At present, only b-tree supports it.) Columns listed in the
+    <literal>INCLUDE</literal> clause are not used to enforce uniqueness.
   </para>
 
   <para>
index 0818196e7665c33df86a8f841ef21aff4b932795..14a1aa56cb5696ecf3013d53ed13d58200d73cf1 100644 (file)
@@ -638,7 +638,8 @@ CREATE INDEX test3_desc_index ON test3 (id DESC NULLS LAST);
    Indexes can also be used to enforce uniqueness of a column's value,
    or the uniqueness of the combined values of more than one column.
 <synopsis>
-CREATE UNIQUE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable> (<replaceable>column</replaceable> <optional>, ...</optional>);
+CREATE UNIQUE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable> (<replaceable>column</replaceable> <optional>, ...</optional>)
+[ INCLUDE (<replaceable>column</replaceable> <optional>, ...</optional>) ];
 </synopsis>
    Currently, only B-tree indexes can be declared unique.
   </para>
@@ -647,7 +648,9 @@ CREATE UNIQUE INDEX <replaceable>name</replaceable> ON <replaceable>table</repla
    When an index is declared unique, multiple table rows with equal
    indexed values are not allowed.  Null values are not considered
    equal.  A multicolumn unique index will only reject cases where all
-   indexed columns are equal in multiple rows.
+   indexed columns are equal in multiple rows.  Columns listed in the
+   <literal>INCLUDE</literal> clause aren't used to enforce constraints
+   (UNIQUE, PRIMARY KEY, etc).
   </para>
 
   <para>
index 6a6490cac3d2172623a8dd61cf6e2757c95b5023..91692325a50cfab84240536076a5d83befe3699e 100644 (file)
@@ -23,6 +23,7 @@ PostgreSQL documentation
 <synopsis>
 CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]
     ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
+    [ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] ) ]
     [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
     [ WHERE <replaceable class="parameter">predicate</replaceable> ]
@@ -143,6 +144,56 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>INCLUDE</literal></term>
+      <listitem>
+       <para>
+        The optional <literal>INCLUDE</literal> clause specifies a
+        list of columns which will be included as a non-key part in the index.
+        Columns listed in this clause cannot also be present as index key columns.
+        The <literal>INCLUDE</literal> columns exist solely to
+        allow more queries to benefit from <firstterm>index-only scans</firstterm>
+        by including the values of the specified columns in the index.  These values
+        would otherwise have to be obtained by reading the table's heap.
+       </para>
+
+       <para>
+        In <literal>UNIQUE</literal> indexes, uniqueness is only enforced
+        for key columns.  Columns listed in the <literal>INCLUDE</literal>
+        clause have no effect on uniqueness enforcement.  Other constraints
+        (<literal>PRIMARY KEY</literal> and <literal>EXCLUDE</literal>) work
+        the same way.
+       </para>
+
+       <para>
+        Columns listed in the <literal>INCLUDE</literal> clause don't need
+        appropriate operator classes; the clause can contain non-key index
+        columns whose data types don't have operator classes defined for
+        a given access method.
+       </para>
+
+       <para>
+        Expressions are not supported as included columns since they cannot be
+        used in index-only scans.
+       </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
+        are linked to the heap tuples, but are not included into pivot tuples
+        used for tree navigation.  Therefore, moving columns from the list of
+        key columns to the <literal>INCLUDE</literal> clause can slightly
+        reduce index size and improve the tree branching factor.
+       </para>
+
+       <para>
+        Indexes with columns listed in the <literal>INCLUDE</literal> clause
+        are also called <quote>covering indexes</quote>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">name</replaceable></term>
       <listitem>
@@ -729,13 +780,22 @@ Indexes:
   <title>Examples</title>
 
   <para>
-   To create a B-tree index on the column <literal>title</literal> in
+   To create a unique B-tree index on the column <literal>title</literal> in
    the table <literal>films</literal>:
 <programlisting>
 CREATE UNIQUE INDEX title_idx ON films (title);
 </programlisting>
   </para>
 
+  <para>
+   To create a unique B-tree index on the column <literal>title</literal>
+   and included columns <literal>director</literal> and <literal>rating</literal>
+   in the table <literal>films</literal>:
+<programlisting>
+CREATE UNIQUE INDEX title_idx ON films (title) INCLUDE (director, rating);
+</programlisting>
+  </para>
+
   <para>
    To create an index on the expression <literal>lower(title)</literal>,
    allowing efficient case-insensitive searches:
index be0effa5d91cb6c7d59d4ea6ff58d4523ae55f29..cb3867dbd52161e3e2a1222ca58f252488136edc 100644 (file)
@@ -73,8 +73,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
-  UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> <optional> INCLUDE (<replaceable class="parameter">column_name</replaceable> [, ...]) </optional> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> <optional> INCLUDE (<replaceable class="parameter">column_name</replaceable> [, ...]) </optional> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
   FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
@@ -769,7 +769,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
-    <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal> (table constraint)</term>
+    <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
 
     <listitem>
      <para>
@@ -798,12 +799,25 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       partitioned table, as well as those of all its descendant partitioned
       tables, must be included in the constraint definition.
      </para>
+
+     <para>
+      Adding a unique constraint will automatically create a unique btree
+      index on the column or group of columns used in the constraint.
+      The optional clause <literal>INCLUDE</literal> adds to that index
+      one or more columns on which the uniqueness is not enforced.
+      Note that although the constraint is not enforced on the included columns,
+      it still depends on them.  Consequently, some operations on these columns
+      (e.g. <literal>DROP COLUMN</literal>) can cause cascade constraint and
+      index deletion.  See paragraph about <literal>INCLUDE</literal> in
+      <xref linkend="sql-createindex"/> for more information.
+     </para>
     </listitem>
    </varlistentry>
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal> (table constraint)</term>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
       The <literal>PRIMARY KEY</literal> constraint specifies that a column or
@@ -833,6 +847,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       tables.
      </para>
 
+     <para>
+      Adding a <literal>PRIMARY KEY</literal> constraint will automatically
+      create a unique btree index on the column or group of columns used in the
+      constraint.  The optional <literal>INCLUDE</literal> clause allows a list
+      of columns to be specified which will be included in the non-key portion
+      of the index.  Although uniqueness is not enforced on the included columns,
+      the constraint still depends on them. Consequently, some operations on the
+      included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascade
+      constraint and index deletion. See paragraph about <literal>INCLUDE</literal>
+      in <xref linkend="sql-createindex"/> for more information.
+     </para>
     </listitem>
    </varlistentry>
 
index 6ed115f81cca22691214e5186193173f9259e371..e716f51503ca071dc63e575c7c34c891cc24fb0d 100644 (file)
@@ -97,6 +97,7 @@ brinhandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = false;
        amroutine->ampredlocks = false;
        amroutine->amcanparallel = false;
+       amroutine->amcaninclude = false;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = brinbuild;
index f7103e53bccd261f9ec6a4ff448b98a6b7a1e459..a9c0b620ec0f600c51df7999fd4d50d82108075c 100644 (file)
@@ -19,6 +19,7 @@
 #include "access/heapam.h"
 #include "access/itup.h"
 #include "access/tuptoaster.h"
+#include "utils/rel.h"
 
 
 /* ----------------------------------------------------------------
@@ -445,3 +446,33 @@ CopyIndexTuple(IndexTuple source)
        memcpy(result, source, size);
        return result;
 }
+
+/*
+ * Truncate tailing attributes from given index tuple leaving it with
+ * new_indnatts number of attributes.
+ */
+IndexTuple
+index_truncate_tuple(TupleDesc tupleDescriptor, IndexTuple olditup,
+                                        int new_indnatts)
+{
+       TupleDesc       itupdesc = CreateTupleDescCopyConstr(tupleDescriptor);
+       Datum           values[INDEX_MAX_KEYS];
+       bool            isnull[INDEX_MAX_KEYS];
+       IndexTuple      newitup;
+       int                     indnatts = tupleDescriptor->natts;
+
+       Assert(indnatts <= INDEX_MAX_KEYS);
+       Assert(new_indnatts > 0);
+       Assert(new_indnatts < indnatts);
+
+       index_deform_tuple(olditup, tupleDescriptor, values, isnull);
+
+       /* form new tuple that will contain only key attributes */
+       itupdesc->natts = new_indnatts;
+       newitup = index_form_tuple(itupdesc, values, isnull);
+       newitup->t_tid = olditup->t_tid;
+
+       FreeTupleDesc(itupdesc);
+       Assert(IndexTupleSize(newitup) <= IndexTupleSize(olditup));
+       return newitup;
+}
index 5632cc5a773f8523df3575a22bc5b7b045a2eeca..4367523dd98d7bc818edeacd1359814edf226068 100644 (file)
@@ -52,6 +52,7 @@ ginhandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = false;
        amroutine->ampredlocks = true;
        amroutine->amcanparallel = false;
+       amroutine->amcaninclude = false;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = ginbuild;
index 52c83b9cbf58317c4d4409a2c041c2c68a0b08bb..9007d65ad2a24d6d6938b18955b8665fd8b335b4 100644 (file)
@@ -74,6 +74,7 @@ gisthandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = true;
        amroutine->ampredlocks = true;
        amroutine->amcanparallel = false;
+       amroutine->amcaninclude = false;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = gistbuild;
index 4f2ea7955f96eefb738f3fc9ffc899ea5ed2bd68..0002df30c0d67fcb199fe3e574be8b725ad1c0f7 100644 (file)
@@ -70,6 +70,7 @@ hashhandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = false;
        amroutine->ampredlocks = true;
        amroutine->amcanparallel = false;
+       amroutine->amcaninclude = false;
        amroutine->amkeytype = INT4OID;
 
        amroutine->ambuild = hashbuild;
index 0bafb4fefcdbba07f4d8a0fc4483e0076377289c..201d1f5a1b48ddb97a04a06b57594ef6e343e340 100644 (file)
@@ -8023,7 +8023,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
        TupleDesc       desc = RelationGetDescr(relation);
        Oid                     replidindex;
        Relation        idx_rel;
-       TupleDesc       idx_desc;
        char            replident = relation->rd_rel->relreplident;
        HeapTuple       key_tuple = NULL;
        bool            nulls[MaxHeapAttributeNumber];
@@ -8066,7 +8065,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
        }
 
        idx_rel = RelationIdGetRelation(replidindex);
-       idx_desc = RelationGetDescr(idx_rel);
 
        /* deform tuple, so we have fast access to columns */
        heap_deform_tuple(tp, desc, values, nulls);
@@ -8078,7 +8076,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
         * Now set all columns contained in the index to NOT NULL, they cannot
         * currently be NULL.
         */
-       for (natt = 0; natt < idx_desc->natts; natt++)
+       for (natt = 0; natt < IndexRelationGetNumberOfKeyAttributes(idx_rel); natt++)
        {
                int                     attno = idx_rel->rd_index->indkey.values[natt];
 
index 214825114e0240bf634d8eb521bed44a7fbca8af..58b44117961188c864e2792269df41b2a014e00c 100644 (file)
@@ -158,7 +158,8 @@ IndexScanEnd(IndexScanDesc scan)
  *
  * Construct a string describing the contents of an index entry, in the
  * form "(key_name, ...)=(key_value, ...)".  This is currently used
- * for building unique-constraint and exclusion-constraint error messages.
+ * for building unique-constraint and exclusion-constraint error messages,
+ * so only key columns of the index are checked and printed.
  *
  * Note that if the user does not have permissions to view all of the
  * columns involved then a NULL is returned.  Returning a partial key seems
@@ -180,13 +181,15 @@ BuildIndexValueDescription(Relation indexRelation,
        StringInfoData buf;
        Form_pg_index idxrec;
        HeapTuple       ht_idx;
-       int                     natts = indexRelation->rd_rel->relnatts;
+       int                     indnkeyatts;
        int                     i;
        int                     keyno;
        Oid                     indexrelid = RelationGetRelid(indexRelation);
        Oid                     indrelid;
        AclResult       aclresult;
 
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation);
+
        /*
         * Check permissions- if the user does not have access to view all of the
         * key columns then return NULL to avoid leaking data.
@@ -224,7 +227,7 @@ BuildIndexValueDescription(Relation indexRelation,
                 * No table-level access, so step through the columns in the index and
                 * make sure the user has SELECT rights on all of them.
                 */
-               for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+               for (keyno = 0; keyno < idxrec->indnkeyatts; keyno++)
                {
                        AttrNumber      attnum = idxrec->indkey.values[keyno];
 
@@ -250,7 +253,7 @@ BuildIndexValueDescription(Relation indexRelation,
        appendStringInfo(&buf, "(%s)=(",
                                         pg_get_indexdef_columns(indexrelid, true));
 
-       for (i = 0; i < natts; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                char       *val;
 
@@ -368,7 +371,7 @@ systable_beginscan(Relation heapRelation,
                {
                        int                     j;
 
-                       for (j = 0; j < irel->rd_index->indnatts; j++)
+                       for (j = 0; j < IndexRelationGetNumberOfAttributes(irel); j++)
                        {
                                if (key[i].sk_attno == irel->rd_index->indkey.values[j])
                                {
@@ -376,7 +379,7 @@ systable_beginscan(Relation heapRelation,
                                        break;
                                }
                        }
-                       if (j == irel->rd_index->indnatts)
+                       if (j == IndexRelationGetNumberOfAttributes(irel))
                                elog(ERROR, "column is not in index");
                }
 
@@ -570,7 +573,7 @@ systable_beginscan_ordered(Relation heapRelation,
        {
                int                     j;
 
-               for (j = 0; j < indexRelation->rd_index->indnatts; j++)
+               for (j = 0; j < IndexRelationGetNumberOfAttributes(indexRelation); j++)
                {
                        if (key[i].sk_attno == indexRelation->rd_index->indkey.values[j])
                        {
@@ -578,7 +581,7 @@ systable_beginscan_ordered(Relation heapRelation,
                                break;
                        }
                }
-               if (j == indexRelation->rd_index->indnatts)
+               if (j == IndexRelationGetNumberOfAttributes(indexRelation))
                        elog(ERROR, "column is not in index");
        }
 
index 34f78b2f50a6ceb4edac9f1547aac9765aa8a855..aef455c122a3440089fb5b0465152a4eb9d803d0 100644 (file)
@@ -590,6 +590,23 @@ original search scankey is consulted as each index entry is sequentially
 scanned to decide whether to return the entry and whether the scan can
 stop (see _bt_checkkeys()).
 
+We use term "pivot" index tuples to distinguish tuples which don't point
+to heap tuples, but rather used for tree navigation.  Pivot tuples includes
+all tuples on non-leaf pages and high keys on leaf pages.  Note that pivot
+index tuples are only used to represent which part of the key space belongs
+on each page, and can have attribute values copied from non-pivot tuples
+that were deleted and killed by VACUUM some time ago.  In principle, we could
+truncate away attributes that are not needed for a page high key during a leaf
+page split, provided that the remaining attributes distinguish the last index
+tuple on the post-split left page as belonging on the left page, and the first
+index tuple on the post-split right page as belonging on the right page.  This
+optimization is sometimes called suffix truncation, and may appear in a future
+release. Since the high key is subsequently reused as the downlink in the
+parent page for the new right page, suffix truncation can increase index
+fan-out considerably by keeping pivot tuples short.  INCLUDE indexes similarly
+truncate away non-key attributes at the time of a leaf page split,
+increasing fan-out.
+
 Notes About Data Representation
 -------------------------------
 
index fd7360278dbd302c6ff41f4b37eb2094900600fc..9bfa0e9acecd60e643165ff9bb4a4a0b58564d1f 100644 (file)
@@ -82,7 +82,7 @@ static void _bt_checksplitloc(FindSplitData *state,
                                  int dataitemstoleft, Size firstoldonrightsz);
 static bool _bt_pgaddtup(Page page, Size itemsize, IndexTuple itup,
                         OffsetNumber itup_off);
-static bool _bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
+static bool _bt_isequal(Relation idxrel, Page page, OffsetNumber offnum,
                        int keysz, ScanKey scankey);
 static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
 
@@ -109,13 +109,16 @@ _bt_doinsert(Relation rel, IndexTuple itup,
                         IndexUniqueCheck checkUnique, Relation heapRel)
 {
        bool            is_unique = false;
-       int                     natts = rel->rd_rel->relnatts;
+       int                     indnkeyatts;
        ScanKey         itup_scankey;
        BTStack         stack = NULL;
        Buffer          buf;
        OffsetNumber offset;
        bool            fastpath;
 
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
+       Assert(indnkeyatts != 0);
+
        /* we need an insertion scan key to do our search, so build one */
        itup_scankey = _bt_mkscankey(rel, itup);
 
@@ -173,12 +176,12 @@ top:
                         * page.
                         */
                        if (P_ISLEAF(lpageop) && P_RIGHTMOST(lpageop) &&
-                                       !P_INCOMPLETE_SPLIT(lpageop) &&
-                                       !P_IGNORE(lpageop) &&
-                                       (PageGetFreeSpace(page) > itemsz) &&
-                                       PageGetMaxOffsetNumber(page) >= P_FIRSTDATAKEY(lpageop) &&
-                                       _bt_compare(rel, natts, itup_scankey, page,
-                                               P_FIRSTDATAKEY(lpageop)) > 0)
+                               !P_INCOMPLETE_SPLIT(lpageop) &&
+                               !P_IGNORE(lpageop) &&
+                               (PageGetFreeSpace(page) > itemsz) &&
+                               PageGetMaxOffsetNumber(page) >= P_FIRSTDATAKEY(lpageop) &&
+                               _bt_compare(rel, indnkeyatts, itup_scankey, page,
+                                                       P_FIRSTDATAKEY(lpageop)) > 0)
                        {
                                fastpath = true;
                        }
@@ -209,7 +212,7 @@ top:
        if (!fastpath)
        {
                /* find the first page containing this key */
-               stack = _bt_search(rel, natts, itup_scankey, false, &buf, BT_WRITE,
+               stack = _bt_search(rel, indnkeyatts, itup_scankey, false, &buf, BT_WRITE,
                                                   NULL);
 
                /* trade in our read lock for a write lock */
@@ -223,7 +226,7 @@ top:
                 * need to move right in the tree.  See Lehman and Yao for an
                 * excruciatingly precise description.
                 */
-               buf = _bt_moveright(rel, buf, natts, itup_scankey, false,
+               buf = _bt_moveright(rel, buf, indnkeyatts, itup_scankey, false,
                                                        true, stack, BT_WRITE, NULL);
        }
 
@@ -253,7 +256,7 @@ top:
                TransactionId xwait;
                uint32          speculativeToken;
 
-               offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
+               offset = _bt_binsrch(rel, buf, indnkeyatts, itup_scankey, false);
                xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
                                                                 checkUnique, &is_unique, &speculativeToken);
 
@@ -287,10 +290,12 @@ top:
                 * actual location of the insert is hard to predict because of the
                 * random search used to prevent O(N^2) performance when there are
                 * many duplicate entries, we can just use the "first valid" page.
+                * This reasoning also applies to INCLUDE indexes, whose extra
+                * attributes are not considered part of the key space.
                 */
                CheckForSerializableConflictIn(rel, NULL, buf);
                /* do the insertion */
-               _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup,
+               _bt_findinsertloc(rel, &buf, &offset, indnkeyatts, itup_scankey, itup,
                                                  stack, heapRel);
                _bt_insertonpg(rel, buf, InvalidBuffer, stack, itup, offset, false);
        }
@@ -333,8 +338,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
                                 IndexUniqueCheck checkUnique, bool *is_unique,
                                 uint32 *speculativeToken)
 {
-       TupleDesc       itupdesc = RelationGetDescr(rel);
-       int                     natts = rel->rd_rel->relnatts;
+       int                     indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
        SnapshotData SnapshotDirty;
        OffsetNumber maxoff;
        Page            page;
@@ -393,7 +397,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
                                 * in real comparison, but only for ordering/finding items on
                                 * pages. - vadim 03/24/97
                                 */
-                               if (!_bt_isequal(itupdesc, page, offset, natts, itup_scankey))
+                               if (!_bt_isequal(rel, page, offset, indnkeyatts, itup_scankey))
                                        break;          /* we're past all the equal tuples */
 
                                /* okay, we gotta fetch the heap tuple ... */
@@ -557,8 +561,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
                        /* If scankey == hikey we gotta check the next page too */
                        if (P_RIGHTMOST(opaque))
                                break;
-                       if (!_bt_isequal(itupdesc, page, P_HIKEY,
-                                                        natts, itup_scankey))
+                       if (!_bt_isequal(rel, page, P_HIKEY,
+                                                        indnkeyatts, itup_scankey))
                                break;
                        /* Advance to next non-dead page --- there must be one */
                        for (;;)
@@ -1087,6 +1091,9 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
        OffsetNumber maxoff;
        OffsetNumber i;
        bool            isleaf;
+       IndexTuple      lefthikey;
+       int                     indnatts = IndexRelationGetNumberOfAttributes(rel);
+       int                     indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
 
        /* Acquire a new page to split into */
        rbuf = _bt_getbuf(rel, P_NEW, BT_WRITE);
@@ -1186,7 +1193,23 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
                itemsz = ItemIdGetLength(itemid);
                item = (IndexTuple) PageGetItem(origpage, itemid);
        }
-       if (PageAddItem(leftpage, (Item) item, itemsz, leftoff,
+
+       /*
+        * We must truncate included attributes of the "high key" item, before
+        * insert it onto the leaf page.  It's the only point in insertion
+        * process, where we perform truncation.  All other functions work with
+        * this high key and do not change it.
+        */
+       if (indnatts != indnkeyatts && isleaf)
+       {
+               lefthikey = _bt_truncate_tuple(rel, item);
+               itemsz = IndexTupleSize(lefthikey);
+               itemsz = MAXALIGN(itemsz);
+       }
+       else
+               lefthikey = item;
+
+       if (PageAddItem(leftpage, (Item) lefthikey, itemsz, leftoff,
                                        false, false) == InvalidOffsetNumber)
        {
                memset(rightpage, 0, BufferGetPageSize(rbuf));
@@ -1375,6 +1398,7 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
                xl_btree_split xlrec;
                uint8           xlinfo;
                XLogRecPtr      recptr;
+               bool            loglhikey = false;
 
                xlrec.level = ropaque->btpo.level;
                xlrec.firstright = firstright;
@@ -1404,18 +1428,20 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
                        XLogRegisterBufData(0, (char *) newitem, MAXALIGN(newitemsz));
 
                /* Log left page */
-               if (!isleaf)
+               if (!isleaf || indnatts != indnkeyatts)
                {
                        /*
-                        * We must also log the left page's high key, because the right
-                        * page's leftmost key is suppressed on non-leaf levels.  Show it
-                        * as belonging to the left page buffer, so that it is not stored
-                        * if XLogInsert decides it needs a full-page image of the left
-                        * page.
+                        * We must also log the left page's high key.  There are two
+                        * reasons for that: right page's leftmost key is suppressed on
+                        * non-leaf levels and in covering indexes included columns are
+                        * truncated from high keys.  Show it as belonging to the left
+                        * page buffer, so that it is not stored if XLogInsert decides it
+                        * needs a full-page image of the left page.
                         */
                        itemid = PageGetItemId(origpage, P_HIKEY);
                        item = (IndexTuple) PageGetItem(origpage, itemid);
                        XLogRegisterBufData(0, (char *) item, MAXALIGN(IndexTupleSize(item)));
+                       loglhikey = true;
                }
 
                /*
@@ -1434,7 +1460,9 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
                                                        (char *) rightpage + ((PageHeader) rightpage)->pd_upper,
                                                        ((PageHeader) rightpage)->pd_special - ((PageHeader) rightpage)->pd_upper);
 
-               xlinfo = newitemonleft ? XLOG_BTREE_SPLIT_L : XLOG_BTREE_SPLIT_R;
+               xlinfo = newitemonleft ?
+                       (loglhikey ? XLOG_BTREE_SPLIT_L_HIGHKEY : XLOG_BTREE_SPLIT_L) :
+                       (loglhikey ? XLOG_BTREE_SPLIT_R_HIGHKEY : XLOG_BTREE_SPLIT_R);
                recptr = XLogInsert(RM_BTREE_ID, xlinfo);
 
                PageSetLSN(origpage, recptr);
@@ -1664,7 +1692,12 @@ _bt_checksplitloc(FindSplitData *state,
 
        /*
         * The first item on the right page becomes the high key of the left page;
-        * therefore it counts against left space as well as right space.
+        * therefore it counts against left space as well as right space. When
+        * index has included attribues, then those attributes of left page high
+        * key will be truncate leaving that page with slightly more free space.
+        * However, that shouldn't affect our ability to find valid split
+        * location, because anyway split location should exists even without high
+        * key truncation.
         */
        leftfree -= firstrightitemsz;
 
@@ -1787,18 +1820,18 @@ _bt_insert_parent(Relation rel,
                        stack = &fakestack;
                        stack->bts_blkno = BufferGetBlockNumber(pbuf);
                        stack->bts_offset = InvalidOffsetNumber;
-                       /* bts_btentry will be initialized below */
+                       stack->bts_btentry = InvalidBlockNumber;
                        stack->bts_parent = NULL;
                        _bt_relbuf(rel, pbuf);
                }
 
-               /* get high key from left page == lowest key on new right page */
+               /* get high key from left page == lower bound for new right page */
                ritem = (IndexTuple) PageGetItem(page,
                                                                                 PageGetItemId(page, P_HIKEY));
 
                /* form an index tuple that points at the new right page */
                new_item = CopyIndexTuple(ritem);
-               ItemPointerSet(&(new_item->t_tid), rbknum, P_HIKEY);
+               BTreeInnerTupleSetDownLink(new_item, rbknum);
 
                /*
                 * Find the parent buffer and get the parent page.
@@ -1807,7 +1840,7 @@ _bt_insert_parent(Relation rel,
                 * want to find parent pointing to where we are, right ?        - vadim
                 * 05/27/97
                 */
-               ItemPointerSet(&(stack->bts_btentry.t_tid), bknum, P_HIKEY);
+               stack->bts_btentry = bknum;
                pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
 
                /*
@@ -1962,7 +1995,8 @@ _bt_getstackbuf(Relation rel, BTStack stack, int access)
                        {
                                itemid = PageGetItemId(page, offnum);
                                item = (IndexTuple) PageGetItem(page, itemid);
-                               if (BTEntrySame(item, &stack->bts_btentry))
+
+                               if (BTreeInnerTupleGetDownLink(item) == stack->bts_btentry)
                                {
                                        /* Return accurate pointer to where link is now */
                                        stack->bts_blkno = blkno;
@@ -1977,7 +2011,8 @@ _bt_getstackbuf(Relation rel, BTStack stack, int access)
                        {
                                itemid = PageGetItemId(page, offnum);
                                item = (IndexTuple) PageGetItem(page, itemid);
-                               if (BTEntrySame(item, &stack->bts_btentry))
+
+                               if (BTreeInnerTupleGetDownLink(item) == stack->bts_btentry)
                                {
                                        /* Return accurate pointer to where link is now */
                                        stack->bts_blkno = blkno;
@@ -2067,7 +2102,8 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
        left_item_sz = sizeof(IndexTupleData);
        left_item = (IndexTuple) palloc(left_item_sz);
        left_item->t_info = left_item_sz;
-       ItemPointerSet(&(left_item->t_tid), lbkno, P_HIKEY);
+       BTreeInnerTupleSetDownLink(left_item, lbkno);
+       BTreeTupSetNAtts(left_item, 0);
 
        /*
         * Create downlink item for right page.  The key for it is obtained from
@@ -2077,7 +2113,7 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
        right_item_sz = ItemIdGetLength(itemid);
        item = (IndexTuple) PageGetItem(lpage, itemid);
        right_item = CopyIndexTuple(item);
-       ItemPointerSet(&(right_item->t_tid), rbkno, P_HIKEY);
+       BTreeInnerTupleSetDownLink(right_item, rbkno);
 
        /* NO EREPORT(ERROR) from here till newroot op is logged */
        START_CRIT_SECTION();
@@ -2208,6 +2244,7 @@ _bt_pgaddtup(Page page,
        {
                trunctuple = *itup;
                trunctuple.t_info = sizeof(IndexTupleData);
+               BTreeTupSetNAtts(&trunctuple, 0);
                itup = &trunctuple;
                itemsize = sizeof(IndexTupleData);
        }
@@ -2226,9 +2263,10 @@ _bt_pgaddtup(Page page,
  * Rule is simple: NOT_NULL not equal NULL, NULL not equal NULL too.
  */
 static bool
-_bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
+_bt_isequal(Relation idxrel, Page page, OffsetNumber offnum,
                        int keysz, ScanKey scankey)
 {
+       TupleDesc       itupdesc = RelationGetDescr(idxrel);
        IndexTuple      itup;
        int                     i;
 
@@ -2237,6 +2275,17 @@ _bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
 
        itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
 
+       /*
+        * Index tuple shouldn't be truncated.  Despite we technically could
+        * compare truncated tuple as well, this function should be only called
+        * for regular non-truncated leaf tuples and P_HIKEY tuple on
+        * rightmost leaf page.
+        */
+       Assert((P_RIGHTMOST((BTPageOpaque) PageGetSpecialPointer(page)) ||
+                               offnum != P_HIKEY)
+                  ?  BTreeTupGetNAtts(itup, idxrel) == itupdesc->natts
+                  : true);
+
        for (i = 1; i <= keysz; i++)
        {
                AttrNumber      attno;
index 019fe48cb6ebc0398f8de3a19547418972378ba5..ba6892591274b46af2c3f7d824a0b05f3636fb8f 100644 (file)
@@ -1143,7 +1143,7 @@ _bt_lock_branch_parent(Relation rel, BlockNumber child, BTStack stack,
         * Locate the downlink of "child" in the parent (updating the stack entry
         * if needed)
         */
-       ItemPointerSet(&(stack->bts_btentry.t_tid), child, P_HIKEY);
+       stack->bts_btentry = child;
        pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
        if (pbuf == InvalidBuffer)
                elog(ERROR, "failed to re-find parent key in index \"%s\" for deletion target page %u",
@@ -1414,8 +1414,9 @@ _bt_pagedel(Relation rel, Buffer buf)
                                /* we need an insertion scan key for the search, so build one */
                                itup_scankey = _bt_mkscankey(rel, targetkey);
                                /* find the leftmost leaf page containing this key */
-                               stack = _bt_search(rel, rel->rd_rel->relnatts, itup_scankey,
-                                                                  false, &lbuf, BT_READ, NULL);
+                               stack = _bt_search(rel,
+                                                                  IndexRelationGetNumberOfKeyAttributes(rel),
+                                                                  itup_scankey, false, &lbuf, BT_READ, NULL);
                                /* don't need a pin on the page */
                                _bt_relbuf(rel, lbuf);
 
@@ -1551,15 +1552,15 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
 #ifdef USE_ASSERT_CHECKING
        itemid = PageGetItemId(page, topoff);
        itup = (IndexTuple) PageGetItem(page, itemid);
-       Assert(ItemPointerGetBlockNumber(&(itup->t_tid)) == target);
+       Assert(BTreeInnerTupleGetDownLink(itup) == target);
 #endif
 
        nextoffset = OffsetNumberNext(topoff);
        itemid = PageGetItemId(page, nextoffset);
        itup = (IndexTuple) PageGetItem(page, itemid);
-       if (ItemPointerGetBlockNumber(&(itup->t_tid)) != rightsib)
+       if (BTreeInnerTupleGetDownLink(itup) != rightsib)
                elog(ERROR, "right sibling %u of block %u is not next child %u of block %u in index \"%s\"",
-                        rightsib, target, ItemPointerGetBlockNumber(&(itup->t_tid)),
+                        rightsib, target, BTreeInnerTupleGetDownLink(itup),
                         BufferGetBlockNumber(topparent), RelationGetRelationName(rel));
 
        /*
@@ -1582,7 +1583,7 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
 
        itemid = PageGetItemId(page, topoff);
        itup = (IndexTuple) PageGetItem(page, itemid);
-       ItemPointerSet(&(itup->t_tid), rightsib, P_HIKEY);
+       BTreeInnerTupleSetDownLink(itup, rightsib);
 
        nextoffset = OffsetNumberNext(topoff);
        PageIndexTupleDelete(page, nextoffset);
@@ -1601,7 +1602,7 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
        MemSet(&trunctuple, 0, sizeof(IndexTupleData));
        trunctuple.t_info = sizeof(IndexTupleData);
        if (target != leafblkno)
-               ItemPointerSet(&trunctuple.t_tid, target, P_HIKEY);
+               ItemPointerSetBlockNumber(&trunctuple.t_tid, target);
        else
                ItemPointerSetInvalid(&trunctuple.t_tid);
        if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
@@ -1713,7 +1714,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
         */
        if (ItemPointerIsValid(leafhikey))
        {
-               target = ItemPointerGetBlockNumber(leafhikey);
+               target = ItemPointerGetBlockNumberNoCheck(leafhikey);
                Assert(target != leafblkno);
 
                /* fetch the block number of the topmost parent's left sibling */
@@ -1829,7 +1830,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
 
                /* remember the next non-leaf child down in the branch. */
                itemid = PageGetItemId(page, P_FIRSTDATAKEY(opaque));
-               nextchild = ItemPointerGetBlockNumber(&((IndexTuple) PageGetItem(page, itemid))->t_tid);
+               nextchild = BTreeInnerTupleGetDownLink((IndexTuple) PageGetItem(page, itemid));
                if (nextchild == leafblkno)
                        nextchild = InvalidBlockNumber;
        }
@@ -1920,7 +1921,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
                if (nextchild == InvalidBlockNumber)
                        ItemPointerSetInvalid(leafhikey);
                else
-                       ItemPointerSet(leafhikey, nextchild, P_HIKEY);
+                       ItemPointerSetBlockNumber(leafhikey, nextchild);
        }
 
        /*
index 66a66f2dadb0445aae1b400ffcdbd04cdec46ad0..d97f5249deb4d7d0c483fdab8b9a5369c23d199d 100644 (file)
@@ -121,6 +121,7 @@ bthandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = true;
        amroutine->ampredlocks = true;
        amroutine->amcanparallel = true;
+       amroutine->amcaninclude = true;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = btbuild;
index 51dca64e139a84946022f2169b3750ce8fb85cb2..4c6fdcdd8aa79719e7c77f82f3bf73c1e4572278 100644 (file)
@@ -147,7 +147,7 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
                offnum = _bt_binsrch(rel, *bufP, keysz, scankey, nextkey);
                itemid = PageGetItemId(page, offnum);
                itup = (IndexTuple) PageGetItem(page, itemid);
-               blkno = ItemPointerGetBlockNumber(&(itup->t_tid));
+               blkno = BTreeInnerTupleGetDownLink(itup);
                par_blkno = BufferGetBlockNumber(*bufP);
 
                /*
@@ -163,7 +163,7 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
                new_stack = (BTStack) palloc(sizeof(BTStackData));
                new_stack->bts_blkno = par_blkno;
                new_stack->bts_offset = offnum;
-               memcpy(&new_stack->bts_btentry, itup, sizeof(IndexTupleData));
+               new_stack->bts_btentry = blkno;
                new_stack->bts_parent = stack_in;
 
                /* drop the read lock on the parent page, acquire one on the child */
@@ -436,6 +436,15 @@ _bt_compare(Relation rel,
        IndexTuple      itup;
        int                     i;
 
+       /*
+        * Check tuple has correct number of attributes.
+        */
+       if (unlikely(!_bt_check_natts(rel, page, offnum)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INTERNAL_ERROR),
+                                errmsg("tuple has wrong number of attributes in index \"%s\"",
+                                               RelationGetRelationName(rel))));
+
        /*
         * Force result ">" if target item is first data item on an internal page
         * --- see NOTE above.
@@ -1833,7 +1842,7 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
                        offnum = P_FIRSTDATAKEY(opaque);
 
                itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
-               blkno = ItemPointerGetBlockNumber(&(itup->t_tid));
+               blkno = BTreeInnerTupleGetDownLink(itup);
 
                buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ);
                page = BufferGetPage(buf);
@@ -1959,3 +1968,51 @@ _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
        so->numKilled = 0;                      /* just paranoia */
        so->markItemIndex = -1;         /* ditto */
 }
+
+/*
+ * Check if index tuple have appropriate number of attributes.
+ */
+bool
+_bt_check_natts(Relation index, Page page, OffsetNumber offnum)
+{
+       int16           natts = IndexRelationGetNumberOfAttributes(index);
+       int16           nkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+       ItemId          itemid;
+       IndexTuple      itup;
+       BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page);
+
+       /*
+        * Assert that mask allocated for number of keys in index tuple can fit
+        * maximum number of index keys.
+        */
+       StaticAssertStmt(BT_N_KEYS_OFFSET_MASK >= INDEX_MAX_KEYS,
+                                        "BT_N_KEYS_OFFSET_MASK can't fit INDEX_MAX_KEYS");
+
+       itemid = PageGetItemId(page, offnum);
+       itup = (IndexTuple) PageGetItem(page, itemid);
+
+       if (P_ISLEAF(opaque) && offnum >= P_FIRSTDATAKEY(opaque))
+       {
+               /*
+                * Regular leaf tuples have as every index attributes
+                */
+               return (BTreeTupGetNAtts(itup, index) == natts);
+       }
+       else if (!P_ISLEAF(opaque) && offnum == P_FIRSTDATAKEY(opaque))
+       {
+               /*
+                * Leftmost tuples on non-leaf pages have no attributes, or haven't
+                * INDEX_ALT_TID_MASK set in pg_upgraded indexes.
+                */
+               return (BTreeTupGetNAtts(itup, index) == 0 ||
+                               ((itup->t_info & INDEX_ALT_TID_MASK) == 0));
+       }
+       else
+       {
+               /*
+                * Pivot tuples stored in non-leaf pages and hikeys of leaf pages
+                * contain only key attributes
+                */
+               return (BTreeTupGetNAtts(itup, index) == nkeyatts);
+       }
+}
index 098e0ce1bea022111250d9aabc9546df5be9ee5e..feba5e1c8fb62fc4f835f7beae51ff1a48251181 100644 (file)
@@ -752,6 +752,7 @@ _bt_sortaddtup(Page page,
        {
                trunctuple = *itup;
                trunctuple.t_info = sizeof(IndexTupleData);
+               BTreeTupSetNAtts(&trunctuple, 0);
                itup = &trunctuple;
                itemsize = sizeof(IndexTupleData);
        }
@@ -802,6 +803,9 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
        OffsetNumber last_off;
        Size            pgspc;
        Size            itupsz;
+       BTPageOpaque pageop;
+       int                     indnatts = IndexRelationGetNumberOfAttributes(wstate->index);
+       int                     indnkeyatts = IndexRelationGetNumberOfKeyAttributes(wstate->index);
 
        /*
         * This is a handy place to check for cancel interrupts during the btree
@@ -856,6 +860,8 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
                ItemId          ii;
                ItemId          hii;
                IndexTuple      oitup;
+               IndexTuple      keytup;
+               BTPageOpaque opageop = (BTPageOpaque) PageGetSpecialPointer(opage);
 
                /* Create new page of same level */
                npage = _bt_blnewpage(state->btps_level);
@@ -883,6 +889,29 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
                ItemIdSetUnused(ii);    /* redundant */
                ((PageHeader) opage)->pd_lower -= sizeof(ItemIdData);
 
+               if (indnkeyatts != indnatts && P_ISLEAF(opageop))
+               {
+                       /*
+                        * We truncate included attributes of high key here.  Subsequent
+                        * insertions assume that hikey is already truncated, and so they
+                        * need not worry about it, when copying the high key into the
+                        * parent page as a downlink.
+                        *
+                        * The code above have just rearranged item pointers, but it
+                        * didn't save any space.  In order to save the space on page we
+                        * have to truly shift index tuples on the page.  But that's not
+                        * so bad for performance, because we operating pd_upper and don't
+                        * have to shift much of tuples memory.  Shift of ItemId's is
+                        * rather cheap, because they are small.
+                        */
+                       keytup = _bt_truncate_tuple(wstate->index, oitup);
+
+                       /* delete "wrong" high key, insert keytup as P_HIKEY. */
+                       PageIndexTupleDelete(opage, P_HIKEY);
+
+                       _bt_sortaddtup(opage, IndexTupleSize(keytup), keytup, P_HIKEY);
+               }
+
                /*
                 * Link the old page into its parent, using its minimum key. If we
                 * don't have a parent, we have to create one; this adds a new btree
@@ -892,15 +921,18 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
                        state->btps_next = _bt_pagestate(wstate, state->btps_level + 1);
 
                Assert(state->btps_minkey != NULL);
-               ItemPointerSet(&(state->btps_minkey->t_tid), oblkno, P_HIKEY);
+               BTreeInnerTupleSetDownLink(state->btps_minkey, oblkno);
                _bt_buildadd(wstate, state->btps_next, state->btps_minkey);
                pfree(state->btps_minkey);
 
                /*
                 * Save a copy of the minimum key for the new page.  We have to copy
                 * it off the old page, not the new one, in case we are not at leaf
-                * level.
+                * level.  Despite oitup is already initialized, it's important to get
+                * high key from the page, since we could have replaced it with
+                * truncated copy.  See comment above.
                 */
+               oitup = (IndexTuple) PageGetItem(opage, PageGetItemId(opage, P_HIKEY));
                state->btps_minkey = CopyIndexTuple(oitup);
 
                /*
@@ -927,6 +959,8 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
                last_off = P_FIRSTKEY;
        }
 
+       pageop = (BTPageOpaque) PageGetSpecialPointer(npage);
+
        /*
         * If the new item is the first for its page, stash a copy for later. Note
         * this will only happen for the first item on a level; on later pages,
@@ -936,7 +970,15 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
        if (last_off == P_HIKEY)
        {
                Assert(state->btps_minkey == NULL);
-               state->btps_minkey = CopyIndexTuple(itup);
+
+               /*
+                * Truncate included attributes of the tuple that we're going to
+                * insert into the parent page as a downlink
+                */
+               if (indnkeyatts != indnatts && P_ISLEAF(pageop))
+                       state->btps_minkey = _bt_truncate_tuple(wstate->index, itup);
+               else
+                       state->btps_minkey = CopyIndexTuple(itup);
        }
 
        /*
@@ -989,7 +1031,7 @@ _bt_uppershutdown(BTWriteState *wstate, BTPageState *state)
                else
                {
                        Assert(s->btps_minkey != NULL);
-                       ItemPointerSet(&(s->btps_minkey->t_tid), blkno, P_HIKEY);
+                       BTreeInnerTupleSetDownLink(s->btps_minkey, blkno);
                        _bt_buildadd(wstate, s->btps_next, s->btps_minkey);
                        pfree(s->btps_minkey);
                        s->btps_minkey = NULL;
@@ -1029,7 +1071,7 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
        bool            load1;
        TupleDesc       tupdes = RelationGetDescr(wstate->index);
        int                     i,
-                               keysz = RelationGetNumberOfAttributes(wstate->index);
+                               keysz = IndexRelationGetNumberOfKeyAttributes(wstate->index);
        ScanKey         indexScanKey = NULL;
        SortSupport sortKeys;
 
index 752667c8856a70ce4a129b725d59aae1531adf08..12b636253e7dea5e6a71fb809f7203645d221746 100644 (file)
@@ -63,17 +63,28 @@ _bt_mkscankey(Relation rel, IndexTuple itup)
 {
        ScanKey         skey;
        TupleDesc       itupdesc;
-       int                     natts;
+       int                     indnatts PG_USED_FOR_ASSERTS_ONLY;
+       int                     indnkeyatts;
        int16      *indoption;
        int                     i;
 
        itupdesc = RelationGetDescr(rel);
-       natts = RelationGetNumberOfAttributes(rel);
+       indnatts = IndexRelationGetNumberOfAttributes(rel);
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
        indoption = rel->rd_indoption;
 
-       skey = (ScanKey) palloc(natts * sizeof(ScanKeyData));
+       Assert(indnkeyatts != 0);
+       Assert(indnkeyatts <= indnatts);
+       Assert(BTreeTupGetNAtts(itup, rel) == indnatts ||
+                  BTreeTupGetNAtts(itup, rel) == indnkeyatts);
 
-       for (i = 0; i < natts; i++)
+       /*
+        * We'll execute search using ScanKey constructed on key columns. Non key
+        * (included) columns must be omitted.
+        */
+       skey = (ScanKey) palloc(indnkeyatts * sizeof(ScanKeyData));
+
+       for (i = 0; i < indnkeyatts; i++)
        {
                FmgrInfo   *procinfo;
                Datum           arg;
@@ -115,16 +126,16 @@ ScanKey
 _bt_mkscankey_nodata(Relation rel)
 {
        ScanKey         skey;
-       int                     natts;
+       int                     indnkeyatts;
        int16      *indoption;
        int                     i;
 
-       natts = RelationGetNumberOfAttributes(rel);
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
        indoption = rel->rd_indoption;
 
-       skey = (ScanKey) palloc(natts * sizeof(ScanKeyData));
+       skey = (ScanKey) palloc(indnkeyatts * sizeof(ScanKeyData));
 
-       for (i = 0; i < natts; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                FmgrInfo   *procinfo;
                int                     flags;
@@ -2069,3 +2080,30 @@ btproperty(Oid index_oid, int attno,
                        return false;           /* punt to generic code */
        }
 }
+
+/*
+ *     _bt_truncate_tuple() -- remove non-key (INCLUDE) attributes from index
+ *                                                     tuple.
+ *
+ *     Transforms an ordinal B-tree leaf index tuple into pivot tuple to be used
+ *     as hikey or non-leaf page tuple with downlink.  Note that t_tid offset
+ *     will be overritten in order to represent number of present tuple attributes.
+ */
+IndexTuple
+_bt_truncate_tuple(Relation idxrel, IndexTuple olditup)
+{
+       IndexTuple      newitup;
+       int                     nkeyattrs = IndexRelationGetNumberOfKeyAttributes(idxrel);
+
+       /*
+        * We're assuming to truncate only regular leaf index tuples which have
+        * both key and non-key attributes.
+        */
+       Assert(BTreeTupGetNAtts(olditup, idxrel) == IndexRelationGetNumberOfAttributes(idxrel));
+
+       newitup = index_truncate_tuple(RelationGetDescr(idxrel),
+                                                                  olditup, nkeyattrs);
+       BTreeTupSetNAtts(newitup, nkeyattrs);
+
+       return newitup;
+}
index b565bcb54017e0630f804d32f3f276f3945e8faf..0986ef07cf32a530098f5a6368476d12579cab7a 100644 (file)
@@ -202,7 +202,7 @@ btree_xlog_insert(bool isleaf, bool ismeta, XLogReaderState *record)
 }
 
 static void
-btree_xlog_split(bool onleft, XLogReaderState *record)
+btree_xlog_split(bool onleft, bool lhighkey, XLogReaderState *record)
 {
        XLogRecPtr      lsn = record->EndRecPtr;
        xl_btree_split *xlrec = (xl_btree_split *) XLogRecGetData(record);
@@ -248,11 +248,14 @@ btree_xlog_split(bool onleft, XLogReaderState *record)
 
        _bt_restore_page(rpage, datapos, datalen);
 
+       /* Non-leaf page should always have its high key logged. */
+       Assert(isleaf || lhighkey);
+
        /*
-        * On leaf level, the high key of the left page is equal to the first key
-        * on the right page.
+        * When the high key isn't present is the wal record, then we assume it to
+        * be equal to the first key on the right page.
         */
-       if (isleaf)
+       if (!lhighkey)
        {
                ItemId          hiItemId = PageGetItemId(rpage, P_FIRSTDATAKEY(ropaque));
 
@@ -296,13 +299,14 @@ btree_xlog_split(bool onleft, XLogReaderState *record)
                }
 
                /* Extract left hikey and its size (assuming 16-bit alignment) */
-               if (!isleaf)
+               if (lhighkey)
                {
                        left_hikey = (IndexTuple) datapos;
                        left_hikeysz = MAXALIGN(IndexTupleSize(left_hikey));
                        datapos += left_hikeysz;
                        datalen -= left_hikeysz;
                }
+
                Assert(datalen == 0);
 
                newlpage = PageGetTempPageCopySpecial(lpage);
@@ -616,7 +620,7 @@ btree_xlog_delete_get_latestRemovedXid(XLogReaderState *record)
                 * heap_fetch, since it uses ReadBuffer rather than XLogReadBuffer.
                 * Note that we are not looking at tuple data here, just headers.
                 */
-               hoffnum = ItemPointerGetOffsetNumber(&(itup->t_tid));
+               hoffnum = ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid));
                hitemid = PageGetItemId(hpage, hoffnum);
 
                /*
@@ -764,11 +768,11 @@ btree_xlog_mark_page_halfdead(uint8 info, XLogReaderState *record)
                nextoffset = OffsetNumberNext(poffset);
                itemid = PageGetItemId(page, nextoffset);
                itup = (IndexTuple) PageGetItem(page, itemid);
-               rightsib = ItemPointerGetBlockNumber(&itup->t_tid);
+               rightsib = BTreeInnerTupleGetDownLink(itup);
 
                itemid = PageGetItemId(page, poffset);
                itup = (IndexTuple) PageGetItem(page, itemid);
-               ItemPointerSet(&(itup->t_tid), rightsib, P_HIKEY);
+               BTreeInnerTupleSetDownLink(itup, rightsib);
                nextoffset = OffsetNumberNext(poffset);
                PageIndexTupleDelete(page, nextoffset);
 
@@ -798,7 +802,7 @@ btree_xlog_mark_page_halfdead(uint8 info, XLogReaderState *record)
        MemSet(&trunctuple, 0, sizeof(IndexTupleData));
        trunctuple.t_info = sizeof(IndexTupleData);
        if (xlrec->topparent != InvalidBlockNumber)
-               ItemPointerSet(&trunctuple.t_tid, xlrec->topparent, P_HIKEY);
+               ItemPointerSetBlockNumber(&trunctuple.t_tid, xlrec->topparent);
        else
                ItemPointerSetInvalid(&trunctuple.t_tid);
        if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
@@ -908,7 +912,7 @@ btree_xlog_unlink_page(uint8 info, XLogReaderState *record)
                MemSet(&trunctuple, 0, sizeof(IndexTupleData));
                trunctuple.t_info = sizeof(IndexTupleData);
                if (xlrec->topparent != InvalidBlockNumber)
-                       ItemPointerSet(&trunctuple.t_tid, xlrec->topparent, P_HIKEY);
+                       ItemPointerSetBlockNumber(&trunctuple.t_tid, xlrec->topparent);
                else
                        ItemPointerSetInvalid(&trunctuple.t_tid);
                if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
@@ -1004,10 +1008,16 @@ btree_redo(XLogReaderState *record)
                        btree_xlog_insert(false, true, record);
                        break;
                case XLOG_BTREE_SPLIT_L:
-                       btree_xlog_split(true, record);
+                       btree_xlog_split(true, false, record);
+                       break;
+               case XLOG_BTREE_SPLIT_L_HIGHKEY:
+                       btree_xlog_split(true, true, record);
                        break;
                case XLOG_BTREE_SPLIT_R:
-                       btree_xlog_split(false, record);
+                       btree_xlog_split(false, false, record);
+                       break;
+               case XLOG_BTREE_SPLIT_R_HIGHKEY:
+                       btree_xlog_split(false, true, record);
                        break;
                case XLOG_BTREE_VACUUM:
                        btree_xlog_vacuum(record);
index c8caf563686ed3a31eb008ab82aa797b2418bf2f..0b996ea13a83cae32e2f8145938b21dfdd4e41c1 100644 (file)
@@ -35,6 +35,8 @@ btree_desc(StringInfo buf, XLogReaderState *record)
                        }
                case XLOG_BTREE_SPLIT_L:
                case XLOG_BTREE_SPLIT_R:
+               case XLOG_BTREE_SPLIT_L_HIGHKEY:
+               case XLOG_BTREE_SPLIT_R_HIGHKEY:
                        {
                                xl_btree_split *xlrec = (xl_btree_split *) rec;
 
@@ -119,6 +121,12 @@ btree_identify(uint8 info)
                case XLOG_BTREE_SPLIT_R:
                        id = "SPLIT_R";
                        break;
+               case XLOG_BTREE_SPLIT_L_HIGHKEY:
+                       id = "SPLIT_L_HIGHKEY";
+                       break;
+               case XLOG_BTREE_SPLIT_R_HIGHKEY:
+                       id = "SPLIT_R_HIGHKEY";
+                       break;
                case XLOG_BTREE_VACUUM:
                        id = "VACUUM";
                        break;
index c4278b0160264506c54714fac5ac7e80a88d9bda..4a9b5da268d5941165ac3983cddcd6dd271f09d3 100644 (file)
@@ -50,6 +50,7 @@ spghandler(PG_FUNCTION_ARGS)
        amroutine->amclusterable = false;
        amroutine->ampredlocks = false;
        amroutine->amcanparallel = false;
+       amroutine->amcaninclude = false;
        amroutine->amkeytype = InvalidOid;
 
        amroutine->ambuild = spgbuild;
index 4ea3aa97cf73cb4d780d9ab0ef65e48770432703..1ec0e5c8a9ce16bec344dfc34d96eb41cfea2a9d 100644 (file)
@@ -302,6 +302,7 @@ Boot_DeclareIndexStmt:
                                        stmt->accessMethod = $8;
                                        stmt->tableSpace = NULL;
                                        stmt->indexParams = $10;
+                                       stmt->indexIncludingParams = NIL;
                                        stmt->options = NIL;
                                        stmt->whereClause = NULL;
                                        stmt->excludeOpNames = NIL;
@@ -350,6 +351,7 @@ Boot_DeclareUniqueIndexStmt:
                                        stmt->accessMethod = $9;
                                        stmt->tableSpace = NULL;
                                        stmt->indexParams = $11;
+                                       stmt->indexIncludingParams = NIL;
                                        stmt->options = NIL;
                                        stmt->whereClause = NULL;
                                        stmt->excludeOpNames = NIL;
index 1430894ad23a08eafae44b2a7a985719e3e7c2d8..644084d1c3ba9c80d255e2b86c47384b42f2d8e4 100644 (file)
@@ -616,7 +616,7 @@ boot_openrel(char *relname)
                 relname, (int) ATTRIBUTE_FIXED_PART_SIZE);
 
        boot_reldesc = heap_openrv(makeRangeVar(NULL, relname, -1), NoLock);
-       numattr = boot_reldesc->rd_rel->relnatts;
+       numattr = RelationGetNumberOfAttributes(boot_reldesc);
        for (i = 0; i < numattr; i++)
        {
                if (attrtypes[i] == NULL)
index a1def77944cd9fe2867f9a897976b74ee25a149a..faa12e061516a4a6f98822eafdc1acc2e6d92002 100644 (file)
@@ -2268,7 +2268,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
                                                          InvalidOid,   /* no parent constraint */
                                                          RelationGetRelid(rel),        /* relation */
                                                          attNos,       /* attrs in the constraint */
-                                                         keycount, /* # attrs in the constraint */
+                                                         keycount, /* # key attrs in the constraint */
+                                                         keycount, /* # total attrs in the constraint */
                                                          InvalidOid,   /* not a domain constraint */
                                                          InvalidOid,   /* no associated index */
                                                          InvalidOid,   /* Foreign key fields */
index b8e9f9f9c7aa9c94943a07921e032fd516bd0160..0966aec25f26b77ee0b4f90826df0c366be51762 100644 (file)
@@ -238,7 +238,7 @@ index_check_primary_key(Relation heapRel,
         * null, otherwise attempt to ALTER TABLE .. SET NOT NULL
         */
        cmds = NIL;
-       for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+       for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
        {
                AttrNumber      attnum = indexInfo->ii_KeyAttrNumbers[i];
                HeapTuple       atttuple;
@@ -447,32 +447,40 @@ ConstructTupleDescriptor(Relation heapRelation,
 
                /*
                 * Check the opclass and index AM to see if either provides a keytype
-                * (overriding the attribute type).  Opclass takes precedence.
+                * (overriding the attribute type).  Opclass (if exists) takes
+                * precedence.
                 */
-               tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(classObjectId[i]));
-               if (!HeapTupleIsValid(tuple))
-                       elog(ERROR, "cache lookup failed for opclass %u",
-                                classObjectId[i]);
-               opclassTup = (Form_pg_opclass) GETSTRUCT(tuple);
-               if (OidIsValid(opclassTup->opckeytype))
-                       keyType = opclassTup->opckeytype;
-               else
-                       keyType = amroutine->amkeytype;
+               keyType = amroutine->amkeytype;
 
                /*
-                * If keytype is specified as ANYELEMENT, and opcintype is ANYARRAY,
-                * then the attribute type must be an array (else it'd not have
-                * matched this opclass); use its element type.
+                * Code below is concerned to the opclasses which are not used with
+                * the included columns.
                 */
-               if (keyType == ANYELEMENTOID && opclassTup->opcintype == ANYARRAYOID)
+               if (i < indexInfo->ii_NumIndexKeyAttrs)
                {
-                       keyType = get_base_element_type(to->atttypid);
-                       if (!OidIsValid(keyType))
-                               elog(ERROR, "could not get element type of array type %u",
-                                        to->atttypid);
-               }
+                       tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(classObjectId[i]));
+                       if (!HeapTupleIsValid(tuple))
+                               elog(ERROR, "cache lookup failed for opclass %u",
+                                        classObjectId[i]);
+                       opclassTup = (Form_pg_opclass) GETSTRUCT(tuple);
+                       if (OidIsValid(opclassTup->opckeytype))
+                               keyType = opclassTup->opckeytype;
 
-               ReleaseSysCache(tuple);
+                       /*
+                        * If keytype is specified as ANYELEMENT, and opcintype is
+                        * ANYARRAY, then the attribute type must be an array (else it'd
+                        * not have matched this opclass); use its element type.
+                        */
+                       if (keyType == ANYELEMENTOID && opclassTup->opcintype == ANYARRAYOID)
+                       {
+                               keyType = get_base_element_type(to->atttypid);
+                               if (!OidIsValid(keyType))
+                                       elog(ERROR, "could not get element type of array type %u",
+                                                to->atttypid);
+                       }
+
+                       ReleaseSysCache(tuple);
+               }
 
                /*
                 * If a key type different from the heap value is specified, update
@@ -602,7 +610,7 @@ UpdateIndexRelation(Oid indexoid,
        for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
                indkey->values[i] = indexInfo->ii_KeyAttrNumbers[i];
        indcollation = buildoidvector(collationOids, indexInfo->ii_NumIndexAttrs);
-       indclass = buildoidvector(classOids, indexInfo->ii_NumIndexAttrs);
+       indclass = buildoidvector(classOids, indexInfo->ii_NumIndexKeyAttrs);
        indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexAttrs);
 
        /*
@@ -647,6 +655,7 @@ UpdateIndexRelation(Oid indexoid,
        values[Anum_pg_index_indexrelid - 1] = ObjectIdGetDatum(indexoid);
        values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
        values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
+       values[Anum_pg_index_indnkeyatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexKeyAttrs);
        values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
        values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
        values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(isexclusion);
@@ -1086,7 +1095,7 @@ index_create(Relation heapRelation,
                }
 
                /* Store dependency on operator classes */
-               for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+               for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
                {
                        referenced.classId = OperatorClassRelationId;
                        referenced.objectId = classObjectId[i];
@@ -1142,6 +1151,8 @@ index_create(Relation heapRelation,
        else
                Assert(indexRelation->rd_indexcxt != NULL);
 
+       indexRelation->rd_index->indnkeyatts = indexInfo->ii_NumIndexKeyAttrs;
+
        /*
         * If this is bootstrap (initdb) time, then we don't actually fill in the
         * index yet.  We'll be creating more indexes and classes later, so we
@@ -1287,6 +1298,7 @@ index_constraint_create(Relation heapRelation,
                                                                   parentConstraintId,
                                                                   RelationGetRelid(heapRelation),
                                                                   indexInfo->ii_KeyAttrNumbers,
+                                                                  indexInfo->ii_NumIndexKeyAttrs,
                                                                   indexInfo->ii_NumIndexAttrs,
                                                                   InvalidOid,  /* no domain */
                                                                   indexRelationId, /* index OID */
@@ -1732,15 +1744,19 @@ BuildIndexInfo(Relation index)
        IndexInfo  *ii = makeNode(IndexInfo);
        Form_pg_index indexStruct = index->rd_index;
        int                     i;
-       int                     numKeys;
+       int                     numAtts;
 
        /* check the number of keys, and copy attr numbers into the IndexInfo */
-       numKeys = indexStruct->indnatts;
-       if (numKeys < 1 || numKeys > INDEX_MAX_KEYS)
+       numAtts = indexStruct->indnatts;
+       if (numAtts < 1 || numAtts > INDEX_MAX_KEYS)
                elog(ERROR, "invalid indnatts %d for index %u",
-                        numKeys, RelationGetRelid(index));
-       ii->ii_NumIndexAttrs = numKeys;
-       for (i = 0; i < numKeys; i++)
+                        numAtts, RelationGetRelid(index));
+       ii->ii_NumIndexAttrs = numAtts;
+       ii->ii_NumIndexKeyAttrs = indexStruct->indnkeyatts;
+       Assert(ii->ii_NumIndexKeyAttrs != 0);
+       Assert(ii->ii_NumIndexKeyAttrs <= ii->ii_NumIndexAttrs);
+
+       for (i = 0; i < numAtts; i++)
                ii->ii_KeyAttrNumbers[i] = indexStruct->indkey.values[i];
 
        /* fetch any expressions needed for expressional indexes */
@@ -1911,9 +1927,11 @@ CompareIndexInfo(IndexInfo *info1, IndexInfo *info2,
 void
 BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
 {
-       int                     ncols = index->rd_rel->relnatts;
+       int                     indnkeyatts;
        int                     i;
 
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+
        /*
         * fetch info for checking unique indexes
         */
@@ -1922,16 +1940,16 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
        if (index->rd_rel->relam != BTREE_AM_OID)
                elog(ERROR, "unexpected non-btree speculative unique index");
 
-       ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * ncols);
-       ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * ncols);
-       ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+       ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
+       ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
+       ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts);
 
        /*
         * We have to look up the operator's strategy number.  This provides a
         * cross-check that the operator does match the index.
         */
        /* We need the func OIDs and strategy numbers too */
-       for (i = 0; i < ncols; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                ii->ii_UniqueStrats[i] = BTEqualStrategyNumber;
                ii->ii_UniqueOps[i] =
index a84b7da114af41fbfe31a89759c02a9201807b9b..5a361683da4cc8d04b710e6803ba57d5255f5b8f 100644 (file)
@@ -119,6 +119,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
                Assert(indexInfo->ii_Predicate == NIL);
                Assert(indexInfo->ii_ExclusionOps == NULL);
                Assert(relationDescs[i]->rd_index->indimmediate);
+               Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
 
                /*
                 * FormIndexDatum fills in its values and isnull parameters with the
index 153522782d45e67c5afce87f64c1966495fdd60f..485fd370803d11586ce0b141e351dd2668c74133 100644 (file)
@@ -57,6 +57,7 @@ CreateConstraintEntry(const char *constraintName,
                                          Oid relId,
                                          const int16 *constraintKey,
                                          int constraintNKeys,
+                                         int constraintNTotalKeys,
                                          Oid domainId,
                                          Oid indexRelId,
                                          Oid foreignRelId,
@@ -83,6 +84,7 @@ CreateConstraintEntry(const char *constraintName,
        bool            nulls[Natts_pg_constraint];
        Datum           values[Natts_pg_constraint];
        ArrayType  *conkeyArray;
+       ArrayType  *conincludingArray;
        ArrayType  *confkeyArray;
        ArrayType  *conpfeqopArray;
        ArrayType  *conppeqopArray;
@@ -113,6 +115,21 @@ CreateConstraintEntry(const char *constraintName,
        else
                conkeyArray = NULL;
 
+       if (constraintNTotalKeys > constraintNKeys)
+       {
+               Datum      *conincluding;
+               int                     j = 0;
+               int                     constraintNIncludedKeys = constraintNTotalKeys - constraintNKeys;
+
+               conincluding = (Datum *) palloc(constraintNIncludedKeys * sizeof(Datum));
+               for (i = constraintNKeys; i < constraintNTotalKeys; i++)
+                       conincluding[j++] = Int16GetDatum(constraintKey[i]);
+               conincludingArray = construct_array(conincluding, constraintNIncludedKeys,
+                                                                                       INT2OID, 2, true, 's');
+       }
+       else
+               conincludingArray = NULL;
+
        if (foreignNKeys > 0)
        {
                Datum      *fkdatums;
@@ -186,6 +203,11 @@ CreateConstraintEntry(const char *constraintName,
        else
                nulls[Anum_pg_constraint_conkey - 1] = true;
 
+       if (conincludingArray)
+               values[Anum_pg_constraint_conincluding - 1] = PointerGetDatum(conincludingArray);
+       else
+               nulls[Anum_pg_constraint_conincluding - 1] = true;
+
        if (confkeyArray)
                values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray);
        else
@@ -247,9 +269,9 @@ CreateConstraintEntry(const char *constraintName,
 
                relobject.classId = RelationRelationId;
                relobject.objectId = relId;
-               if (constraintNKeys > 0)
+               if (constraintNTotalKeys > 0)
                {
-                       for (i = 0; i < constraintNKeys; i++)
+                       for (i = 0; i < constraintNTotalKeys; i++)
                        {
                                relobject.objectSubId = constraintKey[i];
 
@@ -548,6 +570,7 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
                                                                  relationId,
                                                                  mapped_conkey,
                                                                  nelem,
+                                                                 nelem,
                                                                  InvalidOid,   /* not a domain constraint */
                                                                  constrForm->conindid, /* same index */
                                                                  constrForm->confrelid, /* same foreign rel */
index 9007dc6ebe54f04df5691e287914dffcdcdbeb01..9fb2e6b06e850854f456d95c095ce14f2442b52b 100644 (file)
@@ -303,6 +303,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 
        indexInfo = makeNode(IndexInfo);
        indexInfo->ii_NumIndexAttrs = 2;
+       indexInfo->ii_NumIndexKeyAttrs = 2;
        indexInfo->ii_KeyAttrNumbers[0] = 1;
        indexInfo->ii_KeyAttrNumbers[1] = 2;
        indexInfo->ii_Expressions = NIL;
index e224b91f53bf7e2e5951949b7752df1758074a9b..10f01bf5b70a259dcf890be7b9c6b5df046e41a4 100644 (file)
@@ -109,8 +109,10 @@ static void ReindexPartitionedIndex(Relation parentIdx);
  * indexes.  We acknowledge this when all operator classes, collations and
  * exclusion operators match.  Though we could further permit intra-opfamily
  * changes for btree and hash indexes, that adds subtle complexity with no
- * concrete benefit for core types.
-
+ * concrete benefit for core types. Note, that INCLUDE columns aren't
+ * checked by this function, for them it's enough that table rewrite is
+ * skipped.
+ *
  * When a comparison or exclusion operator has a polymorphic input type, the
  * actual input types must also match.  This defends against the possibility
  * that operators could vary behavior in response to get_fn_expr_argtype().
@@ -224,7 +226,7 @@ CheckIndexCompatible(Oid oldId,
        }
 
        /* Any change in operator class or collation breaks compatibility. */
-       old_natts = indexForm->indnatts;
+       old_natts = indexForm->indnkeyatts;
        Assert(old_natts == numberOfAttributes);
 
        d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indcollation, &isnull);
@@ -351,6 +353,7 @@ DefineIndex(Oid relationId,
        bits16          flags;
        bits16          constr_flags;
        int                     numberOfAttributes;
+       int                     numberOfKeyAttributes;
        TransactionId limitXmin;
        VirtualTransactionId *old_snapshots;
        ObjectAddress address;
@@ -361,10 +364,28 @@ DefineIndex(Oid relationId,
        Snapshot        snapshot;
        int                     i;
 
+       if (list_intersection(stmt->indexParams, stmt->indexIncludingParams) != NIL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("included columns must not intersect with key columns")));
+
+       /*
+        * count key attributes in index
+        */
+       numberOfKeyAttributes = list_length(stmt->indexParams);
+
        /*
-        * count attributes in index
+        * We append any INCLUDE columns onto the indexParams list so that we have
+        * one list with all columns.  Later we can determine which of these are
+        * key columns, and which are just part of the INCLUDE list by checking
+        * the list position.  A list item in a position less than
+        * ii_NumIndexKeyAttrs is part of the key columns, and anything equal to
+        * and over is part of the INCLUDE columns.
         */
+       stmt->indexParams = list_concat(stmt->indexParams,
+                                                                       stmt->indexIncludingParams);
        numberOfAttributes = list_length(stmt->indexParams);
+
        if (numberOfAttributes <= 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -568,6 +589,11 @@ DefineIndex(Oid relationId,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("access method \"%s\" does not support unique indexes",
                                                accessMethodName)));
+       if (list_length(stmt->indexIncludingParams) > 0 && !amRoutine->amcaninclude)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("access method \"%s\" does not support included columns",
+                                               accessMethodName)));
        if (numberOfAttributes > 1 && !amRoutine->amcanmulticol)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -605,6 +631,7 @@ DefineIndex(Oid relationId,
         */
        indexInfo = makeNode(IndexInfo);
        indexInfo->ii_NumIndexAttrs = numberOfAttributes;
+       indexInfo->ii_NumIndexKeyAttrs = numberOfKeyAttributes;
        indexInfo->ii_Expressions = NIL;        /* for now */
        indexInfo->ii_ExpressionsState = NIL;
        indexInfo->ii_Predicate = make_ands_implicit((Expr *) stmt->whereClause);
@@ -624,7 +651,7 @@ DefineIndex(Oid relationId,
 
        typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
        collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
-       classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
+       classObjectId = (Oid *) palloc(numberOfKeyAttributes * sizeof(Oid));
        coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
        ComputeIndexAttrs(indexInfo,
                                          typeObjectId, collationObjectId, classObjectId,
@@ -1348,16 +1375,15 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
        ListCell   *nextExclOp;
        ListCell   *lc;
        int                     attn;
+       int                     nkeycols = indexInfo->ii_NumIndexKeyAttrs;
 
        /* Allocate space for exclusion operator info, if needed */
        if (exclusionOpNames)
        {
-               int                     ncols = list_length(attList);
-
-               Assert(list_length(exclusionOpNames) == ncols);
-               indexInfo->ii_ExclusionOps = (Oid *) palloc(sizeof(Oid) * ncols);
-               indexInfo->ii_ExclusionProcs = (Oid *) palloc(sizeof(Oid) * ncols);
-               indexInfo->ii_ExclusionStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+               Assert(list_length(exclusionOpNames) == nkeycols);
+               indexInfo->ii_ExclusionOps = (Oid *) palloc(sizeof(Oid) * nkeycols);
+               indexInfo->ii_ExclusionProcs = (Oid *) palloc(sizeof(Oid) * nkeycols);
+               indexInfo->ii_ExclusionStrats = (uint16 *) palloc(sizeof(uint16) * nkeycols);
                nextExclOp = list_head(exclusionOpNames);
        }
        else
@@ -1410,6 +1436,11 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
                        Node       *expr = attribute->expr;
 
                        Assert(expr != NULL);
+
+                       if (attn >= nkeycols)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("expressions are not supported in included columns")));
                        atttype = exprType(expr);
                        attcollation = exprCollation(expr);
 
@@ -1487,6 +1518,16 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
 
                collationOidP[attn] = attcollation;
 
+               /*
+                * Skip opclass and ordering options for included columns.
+                */
+               if (attn >= nkeycols)
+               {
+                       colOptionP[attn] = 0;
+                       attn++;
+                       continue;
+               }
+
                /*
                 * Identify the opclass to use.
                 */
index 410d4e5a3808f7204dc7f6c0e3b36a704d3bb3b5..e1eb7c374b833402c20255db384529dc0b7cee34 100644 (file)
@@ -602,7 +602,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
                                                                                  RelationGetRelationName(tempRel));
        diffname = make_temptable_name_n(tempname, 2);
 
-       relnatts = matviewRel->rd_rel->relnatts;
+       relnatts = RelationGetNumberOfAttributes(matviewRel);
 
        /* Open SPI context. */
        if (SPI_connect() != SPI_OK_CONNECT)
@@ -680,7 +680,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
                if (is_usable_unique_index(indexRel))
                {
                        Form_pg_index indexStruct = indexRel->rd_index;
-                       int                     numatts = indexStruct->indnatts;
+                       int                     indnkeyatts = indexStruct->indnkeyatts;
                        oidvector  *indclass;
                        Datum           indclassDatum;
                        bool            isnull;
@@ -695,7 +695,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
                        indclass = (oidvector *) DatumGetPointer(indclassDatum);
 
                        /* Add quals for all columns from this index. */
-                       for (i = 0; i < numatts; i++)
+                       for (i = 0; i < indnkeyatts; i++)
                        {
                                int                     attnum = indexStruct->indkey.values[i];
                                Oid                     opclass = indclass->values[i];
index 801db12bee765fdde8433bb889f57a2d65183ac3..846811d1b8e2e3778921a2fd08aa1c49d668117a 100644 (file)
@@ -5942,7 +5942,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
                         * Loop over each attribute in the primary key and see if it
                         * matches the to-be-altered attribute
                         */
-                       for (i = 0; i < indexStruct->indnatts; i++)
+                       for (i = 0; i < indexStruct->indnkeyatts; i++)
                        {
                                if (indexStruct->indkey.values[i] == attnum)
                                        ereport(ERROR,
@@ -7641,6 +7641,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                                                          RelationGetRelid(rel),
                                                                          fkattnum,
                                                                          numfks,
+                                                                         numfks,
                                                                          InvalidOid,   /* not a domain constraint */
                                                                          indexOid,
                                                                          RelationGetRelid(pkrel),
@@ -8199,7 +8200,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
         * assume a primary key cannot have expressional elements)
         */
        *attnamelist = NIL;
-       for (i = 0; i < indexStruct->indnatts; i++)
+       for (i = 0; i < indexStruct->indnkeyatts; i++)
        {
                int                     pkattno = indexStruct->indkey.values[i];
 
@@ -8277,7 +8278,7 @@ transformFkeyCheckAttrs(Relation pkrel,
                 * partial index; forget it if there are any expressions, too. Invalid
                 * indexes are out as well.
                 */
-               if (indexStruct->indnatts == numattrs &&
+               if (indexStruct->indnkeyatts == numattrs &&
                        indexStruct->indisunique &&
                        IndexIsValid(indexStruct) &&
                        heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
@@ -12529,7 +12530,7 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
                                                RelationGetRelationName(indexRel))));
 
        /* Check index for nullable columns. */
-       for (key = 0; key < indexRel->rd_index->indnatts; key++)
+       for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)
        {
                int16           attno = indexRel->rd_index->indkey.values[key];
                Form_pg_attribute attr;
index a189356cada8c538017ee379eea4229877acb1c5..67f0b6c0ac96bc817b5653840206a6dfd5c7f5cc 100644 (file)
@@ -742,6 +742,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
                                                                                          RelationGetRelid(rel),
                                                                                          NULL, /* no conkey */
                                                                                          0,
+                                                                                         0,
                                                                                          InvalidOid,   /* no domain */
                                                                                          InvalidOid,   /* no index */
                                                                                          InvalidOid,   /* no foreign key */
index 2fdcb7f3fd3db7f900ea4ade6c0d284979dcfab4..04b8b907b55b639e2ea77738d9e37d1190007ba9 100644 (file)
@@ -3157,6 +3157,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
                                                          InvalidOid,   /* not a relation constraint */
                                                          NULL,
                                                          0,
+                                                         0,
                                                          domainOid,    /* domain constraint */
                                                          InvalidOid,   /* no associated index */
                                                          InvalidOid,   /* Foreign key fields */
index 62e51f1ef3b29c891f8a4d02e44bc1ac58cbabbb..903076ee3c4a45851bbc7f0251a6326e1b27aa5d 100644 (file)
@@ -648,7 +648,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
        Oid                *constr_procs;
        uint16     *constr_strats;
        Oid                *index_collations = index->rd_indcollation;
-       int                     index_natts = index->rd_index->indnatts;
+       int                     indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
        IndexScanDesc index_scan;
        HeapTuple       tup;
        ScanKeyData scankeys[INDEX_MAX_KEYS];
@@ -675,7 +675,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
         * If any of the input values are NULL, the constraint check is assumed to
         * pass (i.e., we assume the operators are strict).
         */
-       for (i = 0; i < index_natts; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                if (isnull[i])
                        return true;
@@ -687,7 +687,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
         */
        InitDirtySnapshot(DirtySnapshot);
 
-       for (i = 0; i < index_natts; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                ScanKeyEntryInitialize(&scankeys[i],
                                                           0,
@@ -719,8 +719,8 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 retry:
        conflict = false;
        found_self = false;
-       index_scan = index_beginscan(heap, index, &DirtySnapshot, index_natts, 0);
-       index_rescan(index_scan, scankeys, index_natts, NULL, 0);
+       index_scan = index_beginscan(heap, index, &DirtySnapshot, indnkeyatts, 0);
+       index_rescan(index_scan, scankeys, indnkeyatts, NULL, 0);
 
        while ((tup = index_getnext(index_scan,
                                                                ForwardScanDirection)) != NULL)
@@ -881,10 +881,10 @@ index_recheck_constraint(Relation index, Oid *constr_procs,
                                                 Datum *existing_values, bool *existing_isnull,
                                                 Datum *new_values)
 {
-       int                     index_natts = index->rd_index->indnatts;
+       int                     indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
        int                     i;
 
-       for (i = 0; i < index_natts; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                /* Assume the exclusion operators are strict */
                if (existing_isnull[i])
index 971f92a938ae3426e2d0a86aae51ff1cec1e2ccb..6c5a5401c32927f1e41f93e870f3e45b10c7f64a 100644 (file)
@@ -63,7 +63,7 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
        opclass = (oidvector *) DatumGetPointer(indclassDatum);
 
        /* Build scankey for every attribute in the index. */
-       for (attoff = 0; attoff < RelationGetNumberOfAttributes(idxrel); attoff++)
+       for (attoff = 0; attoff < IndexRelationGetNumberOfKeyAttributes(idxrel); attoff++)
        {
                Oid                     operator;
                Oid                     opfamily;
@@ -131,7 +131,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
        /* Start an index scan. */
        InitDirtySnapshot(snap);
        scan = index_beginscan(rel, idxrel, &snap,
-                                                  RelationGetNumberOfAttributes(idxrel),
+                                                  IndexRelationGetNumberOfKeyAttributes(idxrel),
                                                   0);
 
        /* Build scan key. */
@@ -140,7 +140,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
 retry:
        found = false;
 
-       index_rescan(scan, skey, RelationGetNumberOfAttributes(idxrel), NULL, 0);
+       index_rescan(scan, skey, IndexRelationGetNumberOfKeyAttributes(idxrel), NULL, 0);
 
        /* Try to find the tuple */
        if ((scantuple = index_getnext(scan, ForwardScanDirection)) != NULL)
index 01c9de88f4d98287da5168b1e83eed11ed340daa..d6012192a149127f742ce89b0080daff01bb1399 100644 (file)
@@ -1227,7 +1227,9 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
                Expr       *leftop;             /* expr on lhs of operator */
                Expr       *rightop;    /* expr on rhs ... */
                AttrNumber      varattno;       /* att number used in scan */
+               int                     indnkeyatts;
 
+               indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
                if (IsA(clause, OpExpr))
                {
                        /* indexkey op const or indexkey op expression */
@@ -1252,7 +1254,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
                                elog(ERROR, "indexqual doesn't have key on left side");
 
                        varattno = ((Var *) leftop)->varattno;
-                       if (varattno < 1 || varattno > index->rd_index->indnatts)
+                       if (varattno < 1 || varattno > indnkeyatts)
                                elog(ERROR, "bogus index qualification");
 
                        /*
@@ -1375,7 +1377,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
                                opnos_cell = lnext(opnos_cell);
 
                                if (index->rd_rel->relam != BTREE_AM_OID ||
-                                       varattno < 1 || varattno > index->rd_index->indnatts)
+                                       varattno < 1 || varattno > indnkeyatts)
                                        elog(ERROR, "bogus RowCompare index qualification");
                                opfamily = index->rd_opfamily[varattno - 1];
 
@@ -1499,7 +1501,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
                                elog(ERROR, "indexqual doesn't have key on left side");
 
                        varattno = ((Var *) leftop)->varattno;
-                       if (varattno < 1 || varattno > index->rd_index->indnatts)
+                       if (varattno < 1 || varattno > indnkeyatts)
                                elog(ERROR, "bogus index qualification");
 
                        /*
index 9287baaedc096f6d2ef67b4846dff803405ac7f6..d11a6a82f63e923ce2c93cb263cd55de1faa31f1 100644 (file)
@@ -2889,6 +2889,7 @@ _copyConstraint(const Constraint *from)
        COPY_STRING_FIELD(cooked_expr);
        COPY_SCALAR_FIELD(generated_when);
        COPY_NODE_FIELD(keys);
+       COPY_NODE_FIELD(including);
        COPY_NODE_FIELD(exclusions);
        COPY_NODE_FIELD(options);
        COPY_STRING_FIELD(indexname);
@@ -3464,6 +3465,7 @@ _copyIndexStmt(const IndexStmt *from)
        COPY_STRING_FIELD(accessMethod);
        COPY_STRING_FIELD(tableSpace);
        COPY_NODE_FIELD(indexParams);
+       COPY_NODE_FIELD(indexIncludingParams);
        COPY_NODE_FIELD(options);
        COPY_NODE_FIELD(whereClause);
        COPY_NODE_FIELD(excludeOpNames);
index d758515cfd10034748bd236f90871daf0b86a3d2..39946959afdcea44b38e067dd5e159aadb90ffdc 100644 (file)
@@ -1368,6 +1368,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
        COMPARE_STRING_FIELD(accessMethod);
        COMPARE_STRING_FIELD(tableSpace);
        COMPARE_NODE_FIELD(indexParams);
+       COMPARE_NODE_FIELD(indexIncludingParams);
        COMPARE_NODE_FIELD(options);
        COMPARE_NODE_FIELD(whereClause);
        COMPARE_NODE_FIELD(excludeOpNames);
@@ -2620,6 +2621,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
        COMPARE_STRING_FIELD(cooked_expr);
        COMPARE_SCALAR_FIELD(generated_when);
        COMPARE_NODE_FIELD(keys);
+       COMPARE_NODE_FIELD(including);
        COMPARE_NODE_FIELD(exclusions);
        COMPARE_NODE_FIELD(options);
        COMPARE_STRING_FIELD(indexname);
index 03a91c3352da708db557eae0c07b0704eecce36a..26c621c941ccb9319dae637f495b56d58aebe402 100644 (file)
@@ -2707,6 +2707,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
        WRITE_STRING_FIELD(accessMethod);
        WRITE_STRING_FIELD(tableSpace);
        WRITE_NODE_FIELD(indexParams);
+       WRITE_NODE_FIELD(indexIncludingParams);
        WRITE_NODE_FIELD(options);
        WRITE_NODE_FIELD(whereClause);
        WRITE_NODE_FIELD(excludeOpNames);
@@ -3535,6 +3536,7 @@ _outConstraint(StringInfo str, const Constraint *node)
                case CONSTR_PRIMARY:
                        appendStringInfoString(str, "PRIMARY_KEY");
                        WRITE_NODE_FIELD(keys);
+                       WRITE_NODE_FIELD(including);
                        WRITE_NODE_FIELD(options);
                        WRITE_STRING_FIELD(indexname);
                        WRITE_STRING_FIELD(indexspace);
@@ -3544,6 +3546,7 @@ _outConstraint(StringInfo str, const Constraint *node)
                case CONSTR_UNIQUE:
                        appendStringInfoString(str, "UNIQUE");
                        WRITE_NODE_FIELD(keys);
+                       WRITE_NODE_FIELD(including);
                        WRITE_NODE_FIELD(options);
                        WRITE_STRING_FIELD(indexname);
                        WRITE_STRING_FIELD(indexspace);
@@ -3553,6 +3556,7 @@ _outConstraint(StringInfo str, const Constraint *node)
                case CONSTR_EXCLUSION:
                        appendStringInfoString(str, "EXCLUSION");
                        WRITE_NODE_FIELD(exclusions);
+                       WRITE_NODE_FIELD(including);
                        WRITE_NODE_FIELD(options);
                        WRITE_STRING_FIELD(indexname);
                        WRITE_STRING_FIELD(indexspace);
index c29b79a0c3e5531375b9fae76a8ebef8d8387c84..87a38f9aaa0cd45e66199710abafd1b77d2c8e72 100644 (file)
@@ -565,10 +565,12 @@ of scanning the relation and the resulting ordering of the tuples.
 Sequential scan Paths have NIL pathkeys, indicating no known ordering.
 Index scans have Path.pathkeys that represent the chosen index's ordering,
 if any.  A single-key index would create a single-PathKey list, while a
-multi-column index generates a list with one element per index column.
-(Actually, since an index can be scanned either forward or backward, there
-are two possible sort orders and two possible PathKey lists it can
-generate.)
+multi-column index generates a list with one element per key index column.
+Non-key columns specified in the INCLUDE clause of covering indexes don't
+have corresponding PathKeys in the list, because the have no influence on
+index ordering.  (Actually, since an index can be scanned either forward or
+backward, there are two possible sort orders and two possible PathKey lists
+it can generate.)
 
 Note that a bitmap scan has NIL pathkeys since we can say nothing about
 the overall order of its result.  Also, an indexscan on an unordered type
index ec3f60d3115423f36544e3ac7a6720b1e642dd09..cc607dcdfaa55eed1249b5a80a693a650c615d44 100644 (file)
@@ -2162,7 +2162,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
        if (!index->rel->has_eclass_joins)
                return;
 
-       for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
+       for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
        {
                ec_member_matches_arg arg;
                List       *clauses;
index 6d1cc3b8a04c3fbf8a41fe9937370f3a93ebcff7..ec66cb9c3c5e04670e39b4ee30884379189e0a3f 100644 (file)
@@ -447,8 +447,10 @@ get_cheapest_parallel_safe_total_inner(List *paths)
  * If 'scandir' is BackwardScanDirection, build pathkeys representing a
  * backwards scan of the index.
  *
- * The result is canonical, meaning that redundant pathkeys are removed;
- * it may therefore have fewer entries than there are index columns.
+ * We iterate only key columns of covering indexes, since non-key columns
+ * don't influence index ordering.  The result is canonical, meaning that
+ * redundant pathkeys are removed; it may therefore have fewer entries than
+ * there are key columns in the index.
  *
  * Another reason for stopping early is that we may be able to tell that
  * an index column's sort order is uninteresting for this query.  However,
@@ -477,6 +479,13 @@ build_index_pathkeys(PlannerInfo *root,
                bool            nulls_first;
                PathKey    *cpathkey;
 
+               /*
+                * INCLUDE columns are stored in index unordered, so they don't
+                * support ordered index scan.
+                */
+               if (i >= index->nkeycolumns)
+                       break;
+
                /* We assume we don't need to make a copy of the tlist item */
                indexkey = indextle->expr;
 
index 52e4cca49aa25259f66607db58d010d15689ffed..90bb0c280416c825a8b6b402fe2305f1a6f247fc 100644 (file)
@@ -185,7 +185,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
                        Form_pg_index index;
                        IndexAmRoutine *amroutine;
                        IndexOptInfo *info;
-                       int                     ncolumns;
+                       int                     ncolumns,
+                                               nkeycolumns;
                        int                     i;
 
                        /*
@@ -238,19 +239,25 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
                                RelationGetForm(indexRelation)->reltablespace;
                        info->rel = rel;
                        info->ncolumns = ncolumns = index->indnatts;
+                       info->nkeycolumns = nkeycolumns = index->indnkeyatts;
+
                        info->indexkeys = (int *) palloc(sizeof(int) * ncolumns);
                        info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns);
-                       info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns);
-                       info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns);
+                       info->opfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
+                       info->opcintype = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
                        info->canreturn = (bool *) palloc(sizeof(bool) * ncolumns);
 
                        for (i = 0; i < ncolumns; i++)
                        {
                                info->indexkeys[i] = index->indkey.values[i];
                                info->indexcollations[i] = indexRelation->rd_indcollation[i];
+                               info->canreturn[i] = index_can_return(indexRelation, i + 1);
+                       }
+
+                       for (i = 0; i < nkeycolumns; i++)
+                       {
                                info->opfamily[i] = indexRelation->rd_opfamily[i];
                                info->opcintype[i] = indexRelation->rd_opcintype[i];
-                               info->canreturn[i] = index_can_return(indexRelation, i + 1);
                        }
 
                        info->relam = indexRelation->rd_rel->relam;
@@ -279,10 +286,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
                                Assert(amroutine->amcanorder);
 
                                info->sortopfamily = info->opfamily;
-                               info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns);
-                               info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns);
+                               info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns);
+                               info->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns);
 
-                               for (i = 0; i < ncolumns; i++)
+                               for (i = 0; i < nkeycolumns; i++)
                                {
                                        int16           opt = indexRelation->rd_indoption[i];
 
@@ -306,11 +313,11 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
                                 * of current or foreseeable amcanorder index types, it's not
                                 * worth expending more effort on now.
                                 */
-                               info->sortopfamily = (Oid *) palloc(sizeof(Oid) * ncolumns);
-                               info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns);
-                               info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns);
+                               info->sortopfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
+                               info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns);
+                               info->nulls_first = (bool *) palloc(sizeof(bool) * nkeycolumns);
 
-                               for (i = 0; i < ncolumns; i++)
+                               for (i = 0; i < nkeycolumns; i++)
                                {
                                        int16           opt = indexRelation->rd_indoption[i];
                                        Oid                     ltopr;
@@ -731,7 +738,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 
                /* Build BMS representation of plain (non expression) index attrs */
                indexedAttrs = NULL;
-               for (natt = 0; natt < idxForm->indnatts; natt++)
+               for (natt = 0; natt < idxForm->indnkeyatts; natt++)
                {
                        int                     attno = idxRel->rd_index->indkey.values[natt];
 
@@ -1798,7 +1805,7 @@ has_unique_index(RelOptInfo *rel, AttrNumber attno)
                 * just the specified attr is unique.
                 */
                if (index->unique &&
-                       index->ncolumns == 1 &&
+                       index->nkeycolumns == 1 &&
                        index->indexkeys[0] == attno &&
                        (index->indpred == NIL || index->predOK))
                        return true;
index 7eb9544efee2f4e78c73311c1dee28c358b41504..606021bc94f79e1a8e507b998c4775f2e58a9737 100644 (file)
@@ -1051,7 +1051,7 @@ transformOnConflictClause(ParseState *pstate,
                 * relation.  Have to be careful to use resnos that correspond to
                 * attnos of the underlying relation.
                 */
-               for (attno = 0; attno < targetrel->rd_rel->relnatts; attno++)
+               for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
                {
                        Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
                        char       *name;
@@ -2276,8 +2276,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
                                                                EXPR_KIND_UPDATE_SOURCE);
 
        /* Prepare to assign non-conflicting resnos to resjunk attributes */
-       if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
-               pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
+       if (pstate->p_next_resno <= RelationGetNumberOfAttributes(pstate->p_target_relation))
+               pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
        /* Prepare non-junk columns for assignment to target table */
        target_rte = pstate->p_target_rangetblentry;
index 177906e083dc99a613acff3def796d7dd91088a0..dd0c26c11b8bdc515cfdf6830366dcd4cb5c8844 100644 (file)
@@ -382,6 +382,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                                oper_argtypes RuleActionList RuleActionMulti
                                opt_column_list columnList opt_name_list
                                sort_clause opt_sort_clause sortby_list index_params
+                               opt_include opt_c_include index_including_params
                                name_list role_list from_clause from_list opt_array_bounds
                                qualified_name_list any_name any_name_list type_name_list
                                any_operator expr_list attrs
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
        HANDLER HAVING HEADER_P HOLD HOUR_P
 
-       IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
+       IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
        INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
        INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
        INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -3686,17 +3687,18 @@ ConstraintElem:
                                        n->initially_valid = !n->skip_validation;
                                        $$ = (Node *)n;
                                }
-                       | UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
+                       | UNIQUE '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
                                ConstraintAttributeSpec
                                {
                                        Constraint *n = makeNode(Constraint);
                                        n->contype = CONSTR_UNIQUE;
                                        n->location = @1;
                                        n->keys = $3;
-                                       n->options = $5;
+                                       n->including = $5;
+                                       n->options = $6;
                                        n->indexname = NULL;
-                                       n->indexspace = $6;
-                                       processCASbits($7, @7, "UNIQUE",
+                                       n->indexspace = $7;
+                                       processCASbits($8, @8, "UNIQUE",
                                                                   &n->deferrable, &n->initdeferred, NULL,
                                                                   NULL, yyscanner);
                                        $$ = (Node *)n;
@@ -3707,6 +3709,7 @@ ConstraintElem:
                                        n->contype = CONSTR_UNIQUE;
                                        n->location = @1;
                                        n->keys = NIL;
+                                       n->including = NIL;
                                        n->options = NIL;
                                        n->indexname = $2;
                                        n->indexspace = NULL;
@@ -3715,17 +3718,18 @@ ConstraintElem:
                                                                   NULL, yyscanner);
                                        $$ = (Node *)n;
                                }
-                       | PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
+                       | PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
                                ConstraintAttributeSpec
                                {
                                        Constraint *n = makeNode(Constraint);
                                        n->contype = CONSTR_PRIMARY;
                                        n->location = @1;
                                        n->keys = $4;
-                                       n->options = $6;
+                                       n->including = $6;
+                                       n->options = $7;
                                        n->indexname = NULL;
-                                       n->indexspace = $7;
-                                       processCASbits($8, @8, "PRIMARY KEY",
+                                       n->indexspace = $8;
+                                       processCASbits($9, @9, "PRIMARY KEY",
                                                                   &n->deferrable, &n->initdeferred, NULL,
                                                                   NULL, yyscanner);
                                        $$ = (Node *)n;
@@ -3736,6 +3740,7 @@ ConstraintElem:
                                        n->contype = CONSTR_PRIMARY;
                                        n->location = @1;
                                        n->keys = NIL;
+                                       n->including = NIL;
                                        n->options = NIL;
                                        n->indexname = $3;
                                        n->indexspace = NULL;
@@ -3745,7 +3750,7 @@ ConstraintElem:
                                        $$ = (Node *)n;
                                }
                        | EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
-                               opt_definition OptConsTableSpace ExclusionWhereClause
+                               opt_c_include opt_definition OptConsTableSpace  ExclusionWhereClause
                                ConstraintAttributeSpec
                                {
                                        Constraint *n = makeNode(Constraint);
@@ -3753,11 +3758,12 @@ ConstraintElem:
                                        n->location = @1;
                                        n->access_method        = $2;
                                        n->exclusions           = $4;
-                                       n->options                      = $6;
+                                       n->including            = $6;
+                                       n->options                      = $7;
                                        n->indexname            = NULL;
-                                       n->indexspace           = $7;
-                                       n->where_clause         = $8;
-                                       processCASbits($9, @9, "EXCLUDE",
+                                       n->indexspace           = $8;
+                                       n->where_clause         = $9;
+                                       processCASbits($10, @10, "EXCLUDE",
                                                                   &n->deferrable, &n->initdeferred, NULL,
                                                                   NULL, yyscanner);
                                        $$ = (Node *)n;
@@ -3803,6 +3809,10 @@ columnElem: ColId
                                }
                ;
 
+opt_c_include: INCLUDE '(' columnList ')'                      { $$ = $3; }
+                        |              /* EMPTY */                                             { $$ = NIL; }
+               ;
+
 key_match:  MATCH FULL
                        {
                                $$ = FKCONSTR_MATCH_FULL;
@@ -7373,7 +7383,7 @@ defacl_privilege_target:
 
 IndexStmt:     CREATE opt_unique INDEX opt_concurrently opt_index_name
                        ON relation_expr access_method_clause '(' index_params ')'
-                       opt_reloptions OptTableSpace where_clause
+                       opt_include opt_reloptions OptTableSpace where_clause
                                {
                                        IndexStmt *n = makeNode(IndexStmt);
                                        n->unique = $2;
@@ -7383,9 +7393,10 @@ IndexStmt:       CREATE opt_unique INDEX opt_concurrently opt_index_name
                                        n->relationId = InvalidOid;
                                        n->accessMethod = $8;
                                        n->indexParams = $10;
-                                       n->options = $12;
-                                       n->tableSpace = $13;
-                                       n->whereClause = $14;
+                                       n->indexIncludingParams = $12;
+                                       n->options = $13;
+                                       n->tableSpace = $14;
+                                       n->whereClause = $15;
                                        n->excludeOpNames = NIL;
                                        n->idxcomment = NULL;
                                        n->indexOid = InvalidOid;
@@ -7400,7 +7411,7 @@ IndexStmt:        CREATE opt_unique INDEX opt_concurrently opt_index_name
                                }
                        | CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
                        ON relation_expr access_method_clause '(' index_params ')'
-                       opt_reloptions OptTableSpace where_clause
+                       opt_include opt_reloptions OptTableSpace where_clause
                                {
                                        IndexStmt *n = makeNode(IndexStmt);
                                        n->unique = $2;
@@ -7410,9 +7421,10 @@ IndexStmt:       CREATE opt_unique INDEX opt_concurrently opt_index_name
                                        n->relationId = InvalidOid;
                                        n->accessMethod = $11;
                                        n->indexParams = $13;
-                                       n->options = $15;
-                                       n->tableSpace = $16;
-                                       n->whereClause = $17;
+                                       n->indexIncludingParams = $15;
+                                       n->options = $16;
+                                       n->tableSpace = $17;
+                                       n->whereClause = $18;
                                        n->excludeOpNames = NIL;
                                        n->idxcomment = NULL;
                                        n->indexOid = InvalidOid;
@@ -7491,6 +7503,14 @@ index_elem:      ColId opt_collate opt_class opt_asc_desc opt_nulls_order
                                }
                ;
 
+opt_include:           INCLUDE '(' index_including_params ')'                  { $$ = $3; }
+                        |              /* EMPTY */                                             { $$ = NIL; }
+               ;
+
+index_including_params:        index_elem                                              { $$ = list_make1($1); }
+                       | index_including_params ',' index_elem         { $$ = lappend($1, $3); }
+               ;
+
 opt_collate: COLLATE any_name                                          { $$ = $2; }
                        | /*EMPTY*/                                                             { $$ = NIL; }
                ;
@@ -15206,6 +15226,7 @@ unreserved_keyword:
                        | IMMUTABLE
                        | IMPLICIT_P
                        | IMPORT_P
+                       | INCLUDE
                        | INCLUDING
                        | INCREMENT
                        | INDEX
index f7e11f969c0dfc55b4b20b24dc30459c36298ef5..8b912eeea31b0680a773281166430efb72792e7f 100644 (file)
@@ -3089,7 +3089,7 @@ attnameAttNum(Relation rd, const char *attname, bool sysColOK)
 {
        int                     i;
 
-       for (i = 0; i < rd->rd_rel->relnatts; i++)
+       for (i = 0; i < RelationGetNumberOfAttributes(rd); i++)
        {
                Form_pg_attribute att = TupleDescAttr(rd->rd_att, i);
 
index ea209cdab6dd9158e88481d3a719fb9e103332c5..4932e58022bb1857d55a2de739743d99ffd574d4 100644 (file)
@@ -978,7 +978,8 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
                /*
                 * Generate default column list for INSERT.
                 */
-               int                     numcol = pstate->p_target_relation->rd_rel->relnatts;
+               int                     numcol = RelationGetNumberOfAttributes(pstate->p_target_relation);
+
                int                     i;
 
                for (i = 0; i < numcol; i++)
index 513a5dda262398f98493bdd6bd3203b3344330dc..bbbb1a8c1fe86c4b54fdc227ead5fa5f55d30c01 100644 (file)
@@ -1468,9 +1468,10 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
 
        /* Build the list of IndexElem */
        index->indexParams = NIL;
+       index->indexIncludingParams = NIL;
 
        indexpr_item = list_head(indexprs);
-       for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+       for (keyno = 0; keyno < idxrec->indnkeyatts; keyno++)
        {
                IndexElem  *iparam;
                AttrNumber      attnum = idxrec->indkey.values[keyno];
@@ -1559,6 +1560,40 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
                index->indexParams = lappend(index->indexParams, iparam);
        }
 
+       /* Handle included columns separately */
+       for (keyno = idxrec->indnkeyatts; keyno < idxrec->indnatts; keyno++)
+       {
+               IndexElem  *iparam;
+               AttrNumber      attnum = idxrec->indkey.values[keyno];
+               Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx),
+                                                                                          keyno);
+
+               iparam = makeNode(IndexElem);
+
+               if (AttributeNumberIsValid(attnum))
+               {
+                       /* Simple index column */
+                       char       *attname;
+
+                       attname = get_attname(indrelid, attnum, false);
+                       keycoltype = get_atttype(indrelid, attnum);
+
+                       iparam->name = attname;
+                       iparam->expr = NULL;
+               }
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("expressions are not supported in included columns")));
+
+               /* Copy the original index column name */
+               iparam->indexcolname = pstrdup(NameStr(attr->attname));
+
+               /* Add the collation name, if non-default */
+               iparam->collation = get_collation(indcollation->values[keyno], keycoltype);
+
+               index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
+       }
        /* Copy reloptions if any */
        datum = SysCacheGetAttr(RELOID, ht_idxrel,
                                                        Anum_pg_class_reloptions, &isnull);
@@ -1829,6 +1864,7 @@ transformIndexConstraints(CreateStmtContext *cxt)
                        IndexStmt  *priorindex = lfirst(k);
 
                        if (equal(index->indexParams, priorindex->indexParams) &&
+                               equal(index->indexIncludingParams, priorindex->indexIncludingParams) &&
                                equal(index->whereClause, priorindex->whereClause) &&
                                equal(index->excludeOpNames, priorindex->excludeOpNames) &&
                                strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
@@ -1900,6 +1936,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
        index->tableSpace = constraint->indexspace;
        index->whereClause = constraint->where_clause;
        index->indexParams = NIL;
+       index->indexIncludingParams = NIL;
        index->excludeOpNames = NIL;
        index->idxcomment = NULL;
        index->indexOid = InvalidOid;
@@ -2049,24 +2086,29 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                                                                                                        heap_rel->rd_rel->relhasoids);
                        attname = pstrdup(NameStr(attform->attname));
 
-                       /*
-                        * Insist on default opclass and sort options.  While the index
-                        * would still work as a constraint with non-default settings, it
-                        * might not provide exactly the same uniqueness semantics as
-                        * you'd get from a normally-created constraint; and there's also
-                        * the dump/reload problem mentioned above.
-                        */
-                       defopclass = GetDefaultOpClass(attform->atttypid,
-                                                                                  index_rel->rd_rel->relam);
-                       if (indclass->values[i] != defopclass ||
-                               index_rel->rd_indoption[i] != 0)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                                                errmsg("index \"%s\" does not have default sorting behavior", index_name),
-                                                errdetail("Cannot create a primary key or unique constraint using such an index."),
-                                                parser_errposition(cxt->pstate, constraint->location)));
+                       if (i < index_form->indnkeyatts)
+                       {
+                               /*
+                                * Insist on default opclass and sort options.  While the
+                                * index would still work as a constraint with non-default
+                                * settings, it might not provide exactly the same uniqueness
+                                * semantics as you'd get from a normally-created constraint;
+                                * and there's also the dump/reload problem mentioned above.
+                                */
+                               defopclass = GetDefaultOpClass(attform->atttypid,
+                                                                                          index_rel->rd_rel->relam);
+                               if (indclass->values[i] != defopclass ||
+                                       index_rel->rd_indoption[i] != 0)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                                        errmsg("index \"%s\" does not have default sorting behavior", index_name),
+                                                        errdetail("Cannot create a primary key or unique constraint using such an index."),
+                                                        parser_errposition(cxt->pstate, constraint->location)));
 
-                       constraint->keys = lappend(constraint->keys, makeString(attname));
+                               constraint->keys = lappend(constraint->keys, makeString(attname));
+                       }
+                       else
+                               constraint->including = lappend(constraint->including, makeString(attname));
                }
 
                /* Close the index relation but keep the lock */
@@ -2095,8 +2137,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                        index->indexParams = lappend(index->indexParams, elem);
                        index->excludeOpNames = lappend(index->excludeOpNames, opname);
                }
-
-               return index;
        }
 
        /*
@@ -2107,7 +2147,136 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
         * it to DefineIndex to mark the columns NOT NULL, it's more efficient to
         * get it right the first time.)
         */
-       foreach(lc, constraint->keys)
+       else
+       {
+               foreach(lc, constraint->keys)
+               {
+                       char       *key = strVal(lfirst(lc));
+                       bool            found = false;
+                       ColumnDef  *column = NULL;
+                       ListCell   *columns;
+                       IndexElem  *iparam;
+
+                       /* Make sure referenced column exist. */
+                       foreach(columns, cxt->columns)
+                       {
+                               column = castNode(ColumnDef, lfirst(columns));
+                               if (strcmp(column->colname, key) == 0)
+                               {
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       if (found)
+                       {
+                               /* found column in the new table; force it to be NOT NULL */
+                               if (constraint->contype == CONSTR_PRIMARY)
+                                       column->is_not_null = true;
+                       }
+                       else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
+                       {
+                               /*
+                                * column will be a system column in the new table, so accept
+                                * it. System columns can't ever be null, so no need to worry
+                                * about PRIMARY/NOT NULL constraint.
+                                */
+                               found = true;
+                       }
+                       else if (cxt->inhRelations)
+                       {
+                               /* try inherited tables */
+                               ListCell   *inher;
+
+                               foreach(inher, cxt->inhRelations)
+                               {
+                                       RangeVar   *inh = castNode(RangeVar, lfirst(inher));
+                                       Relation        rel;
+                                       int                     count;
+
+                                       rel = heap_openrv(inh, AccessShareLock);
+                                       /* check user requested inheritance from valid relkind */
+                                       if (rel->rd_rel->relkind != RELKIND_RELATION &&
+                                               rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+                                               rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                                                errmsg("inherited relation \"%s\" is not a table or foreign table",
+                                                                               inh->relname)));
+                                       for (count = 0; count < rel->rd_att->natts; count++)
+                                       {
+                                               Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
+                                                                                                                                 count);
+                                               char       *inhname = NameStr(inhattr->attname);
+
+                                               if (inhattr->attisdropped)
+                                                       continue;
+                                               if (strcmp(key, inhname) == 0)
+                                               {
+                                                       found = true;
+
+                                                       /*
+                                                        * We currently have no easy way to force an
+                                                        * inherited column to be NOT NULL at creation, if
+                                                        * its parent wasn't so already. We leave it to
+                                                        * DefineIndex to fix things up in this case.
+                                                        */
+                                                       break;
+                                               }
+                                       }
+                                       heap_close(rel, NoLock);
+                                       if (found)
+                                               break;
+                               }
+                       }
+
+                       /*
+                        * In the ALTER TABLE case, don't complain about index keys not
+                        * created in the command; they may well exist already.
+                        * DefineIndex will complain about them if not, and will also take
+                        * care of marking them NOT NULL.
+                        */
+                       if (!found && !cxt->isalter)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                                errmsg("column \"%s\" named in key does not exist", key),
+                                                parser_errposition(cxt->pstate, constraint->location)));
+
+                       /* Check for PRIMARY KEY(foo, foo) */
+                       foreach(columns, index->indexParams)
+                       {
+                               iparam = (IndexElem *) lfirst(columns);
+                               if (iparam->name && strcmp(key, iparam->name) == 0)
+                               {
+                                       if (index->primary)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_DUPLICATE_COLUMN),
+                                                                errmsg("column \"%s\" appears twice in primary key constraint",
+                                                                               key),
+                                                                parser_errposition(cxt->pstate, constraint->location)));
+                                       else
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_DUPLICATE_COLUMN),
+                                                                errmsg("column \"%s\" appears twice in unique constraint",
+                                                                               key),
+                                                                parser_errposition(cxt->pstate, constraint->location)));
+                               }
+                       }
+
+                       /* OK, add it to the index definition */
+                       iparam = makeNode(IndexElem);
+                       iparam->name = pstrdup(key);
+                       iparam->expr = NULL;
+                       iparam->indexcolname = NULL;
+                       iparam->collation = NIL;
+                       iparam->opclass = NIL;
+                       iparam->ordering = SORTBY_DEFAULT;
+                       iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+                       index->indexParams = lappend(index->indexParams, iparam);
+               }
+       }
+
+       /* Add included columns to index definition */
+       foreach(lc, constraint->including)
        {
                char       *key = strVal(lfirst(lc));
                bool            found = false;
@@ -2124,65 +2293,63 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                                break;
                        }
                }
-               if (found)
-               {
-                       /* found column in the new table; force it to be NOT NULL */
-                       if (constraint->contype == CONSTR_PRIMARY)
-                               column->is_not_null = true;
-               }
-               else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
-               {
-                       /*
-                        * column will be a system column in the new table, so accept it.
-                        * System columns can't ever be null, so no need to worry about
-                        * PRIMARY/NOT NULL constraint.
-                        */
-                       found = true;
-               }
-               else if (cxt->inhRelations)
-               {
-                       /* try inherited tables */
-                       ListCell   *inher;
 
-                       foreach(inher, cxt->inhRelations)
+               if (!found)
+               {
+                       if (SystemAttributeByName(key, cxt->hasoids) != NULL)
                        {
-                               RangeVar   *inh = lfirst_node(RangeVar, inher);
-                               Relation        rel;
-                               int                     count;
-
-                               rel = heap_openrv(inh, AccessShareLock);
-                               /* check user requested inheritance from valid relkind */
-                               if (rel->rd_rel->relkind != RELKIND_RELATION &&
-                                       rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
-                                       rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                                                        errmsg("inherited relation \"%s\" is not a table or foreign table",
-                                                                       inh->relname)));
-                               for (count = 0; count < rel->rd_att->natts; count++)
-                               {
-                                       Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
-                                                                                                                         count);
-                                       char       *inhname = NameStr(inhattr->attname);
+                               /*
+                                * column will be a system column in the new table, so accept
+                                * it. System columns can't ever be null, so no need to worry
+                                * about PRIMARY/NOT NULL constraint.
+                                */
+                               found = true;
+                       }
+                       else if (cxt->inhRelations)
+                       {
+                               /* try inherited tables */
+                               ListCell   *inher;
 
-                                       if (inhattr->attisdropped)
-                                               continue;
-                                       if (strcmp(key, inhname) == 0)
+                               foreach(inher, cxt->inhRelations)
+                               {
+                                       RangeVar   *inh = lfirst_node(RangeVar, inher);
+                                       Relation        rel;
+                                       int                     count;
+
+                                       rel = heap_openrv(inh, AccessShareLock);
+                                       /* check user requested inheritance from valid relkind */
+                                       if (rel->rd_rel->relkind != RELKIND_RELATION &&
+                                               rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+                                               rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                                                errmsg("inherited relation \"%s\" is not a table or foreign table",
+                                                                               inh->relname)));
+                                       for (count = 0; count < rel->rd_att->natts; count++)
                                        {
-                                               found = true;
-
-                                               /*
-                                                * We currently have no easy way to force an inherited
-                                                * column to be NOT NULL at creation, if its parent
-                                                * wasn't so already. We leave it to DefineIndex to
-                                                * fix things up in this case.
-                                                */
-                                               break;
+                                               Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
+                                                                                                                                 count);
+                                               char       *inhname = NameStr(inhattr->attname);
+
+                                               if (inhattr->attisdropped)
+                                                       continue;
+                                               if (strcmp(key, inhname) == 0)
+                                               {
+                                                       found = true;
+
+                                                       /*
+                                                        * We currently have no easy way to force an
+                                                        * inherited column to be NOT NULL at creation, if
+                                                        * its parent wasn't so already. We leave it to
+                                                        * DefineIndex to fix things up in this case.
+                                                        */
+                                                       break;
+                                               }
                                        }
+                                       heap_close(rel, NoLock);
+                                       if (found)
+                                               break;
                                }
-                               heap_close(rel, NoLock);
-                               if (found)
-                                       break;
                        }
                }
 
@@ -2198,27 +2365,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                                         errmsg("column \"%s\" named in key does not exist", key),
                                         parser_errposition(cxt->pstate, constraint->location)));
 
-               /* Check for PRIMARY KEY(foo, foo) */
-               foreach(columns, index->indexParams)
-               {
-                       iparam = (IndexElem *) lfirst(columns);
-                       if (iparam->name && strcmp(key, iparam->name) == 0)
-                       {
-                               if (index->primary)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DUPLICATE_COLUMN),
-                                                        errmsg("column \"%s\" appears twice in primary key constraint",
-                                                                       key),
-                                                        parser_errposition(cxt->pstate, constraint->location)));
-                               else
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_DUPLICATE_COLUMN),
-                                                        errmsg("column \"%s\" appears twice in unique constraint",
-                                                                       key),
-                                                        parser_errposition(cxt->pstate, constraint->location)));
-                       }
-               }
-
                /* OK, add it to the index definition */
                iparam = makeNode(IndexElem);
                iparam->name = pstrdup(key);
@@ -2226,9 +2372,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                iparam->indexcolname = NULL;
                iparam->collation = NIL;
                iparam->opclass = NIL;
-               iparam->ordering = SORTBY_DEFAULT;
-               iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
-               index->indexParams = lappend(index->indexParams, iparam);
+               index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
        }
 
        return index;
index f8fc7f83f9b14ff87ac330f723d356e266e45acd..b75a224ee8cca35572f5c63f1a2ea96bc63c6ac5 100644 (file)
@@ -1296,6 +1296,21 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
                Oid                     keycoltype;
                Oid                     keycolcollation;
 
+               /*
+                * attrsOnly flag is used for building unique-constraint and
+                * exclusion-constraint error messages. Included attrs are meaningless
+                * there, so do not include them in the message.
+                */
+               if (attrsOnly && keyno >= idxrec->indnkeyatts)
+                       break;
+
+               /* Report the INCLUDED attributes, if any. */
+               if ((!attrsOnly) && keyno == idxrec->indnkeyatts)
+               {
+                       appendStringInfoString(&buf, ") INCLUDE (");
+                       sep = "";
+               }
+
                if (!colno)
                        appendStringInfoString(&buf, sep);
                sep = ", ";
@@ -1347,6 +1362,9 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
                                appendStringInfo(&buf, " COLLATE %s",
                                                                 generate_collation_name((indcoll)));
 
+                       if (keyno >= idxrec->indnkeyatts)
+                               continue;
+
                        /* Add the operator class name, if not default */
                        get_opclass_name(indclass->values[keyno], keycoltype, &buf);
 
@@ -2047,6 +2065,19 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
                                appendStringInfoChar(&buf, ')');
 
+                               /* Fetch and build including column list */
+                               isnull = true;
+                               val = SysCacheGetAttr(CONSTROID, tup,
+                                                                         Anum_pg_constraint_conincluding, &isnull);
+                               if (!isnull)
+                               {
+                                       appendStringInfoString(&buf, " INCLUDE (");
+
+                                       decompile_column_index_array(val, conForm->conrelid, &buf);
+
+                                       appendStringInfoChar(&buf, ')');
+                               }
+
                                indexId = get_constraint_index(constraintId);
 
                                /* XXX why do we only print these bits if fullCommand? */
index f998d859c1c49775879bf8f7dd52506e07b7227c..fe606d72798fa2e16151031e4c52b7abe2c72ea3 100644 (file)
@@ -4902,7 +4902,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
                                                 * should match has_unique_index().
                                                 */
                                                if (index->unique &&
-                                                       index->ncolumns == 1 &&
+                                                       index->nkeycolumns == 1 &&
                                                        (index->indpred == NIL || index->predOK))
                                                        vardata->isunique = true;
 
@@ -7053,7 +7053,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
         * NullTest invalidates that theory, even though it sets eqQualHere.
         */
        if (index->unique &&
-               indexcol == index->ncolumns - 1 &&
+               indexcol == index->nkeycolumns - 1 &&
                eqQualHere &&
                !found_saop &&
                !found_is_null_op)
index 40a2c1df049847a9338e6d2e5d12095a95b45002..e81c4691ec2b5eb4f6ff323c29202356008d40e6 100644 (file)
@@ -538,7 +538,7 @@ RelationBuildTupleDesc(Relation relation)
        /*
         * add attribute data to relation->rd_att
         */
-       need = relation->rd_rel->relnatts;
+       need = RelationGetNumberOfAttributes(relation);
 
        while (HeapTupleIsValid(pg_attribute_tuple = systable_getnext(pg_attribute_scan)))
        {
@@ -548,7 +548,7 @@ RelationBuildTupleDesc(Relation relation)
                attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple);
 
                attnum = attp->attnum;
-               if (attnum <= 0 || attnum > relation->rd_rel->relnatts)
+               if (attnum <= 0 || attnum > RelationGetNumberOfAttributes(relation))
                        elog(ERROR, "invalid attribute number %d for %s",
                                 attp->attnum, RelationGetRelationName(relation));
 
@@ -567,7 +567,7 @@ RelationBuildTupleDesc(Relation relation)
                        if (attrdef == NULL)
                                attrdef = (AttrDefault *)
                                        MemoryContextAllocZero(CacheMemoryContext,
-                                                                                  relation->rd_rel->relnatts *
+                                                                                  RelationGetNumberOfAttributes(relation) *
                                                                                   sizeof(AttrDefault));
                        attrdef[ndef].adnum = attnum;
                        attrdef[ndef].adbin = NULL;
@@ -650,7 +650,7 @@ RelationBuildTupleDesc(Relation relation)
        {
                int                     i;
 
-               for (i = 0; i < relation->rd_rel->relnatts; i++)
+               for (i = 0; i < RelationGetNumberOfAttributes(relation); i++)
                        Assert(TupleDescAttr(relation->rd_att, i)->attcacheoff == -1);
        }
 #endif
@@ -660,7 +660,7 @@ RelationBuildTupleDesc(Relation relation)
         * attribute: it must be zero.  This eliminates the need for special cases
         * for attnum=1 that used to exist in fastgetattr() and index_getattr().
         */
-       if (relation->rd_rel->relnatts > 0)
+       if (RelationGetNumberOfAttributes(relation) > 0)
                TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
 
        /*
@@ -673,7 +673,7 @@ RelationBuildTupleDesc(Relation relation)
 
                if (ndef > 0)                   /* DEFAULTs */
                {
-                       if (ndef < relation->rd_rel->relnatts)
+                       if (ndef < RelationGetNumberOfAttributes(relation))
                                constr->defval = (AttrDefault *)
                                        repalloc(attrdef, ndef * sizeof(AttrDefault));
                        else
@@ -1557,7 +1557,8 @@ RelationInitIndexAccessInfo(Relation relation)
        int2vector *indoption;
        MemoryContext indexcxt;
        MemoryContext oldcontext;
-       int                     natts;
+       int                     indnatts;
+       int                     indnkeyatts;
        uint16          amsupport;
 
        /*
@@ -1587,10 +1588,11 @@ RelationInitIndexAccessInfo(Relation relation)
        relation->rd_amhandler = aform->amhandler;
        ReleaseSysCache(tuple);
 
-       natts = relation->rd_rel->relnatts;
-       if (natts != relation->rd_index->indnatts)
+       indnatts = RelationGetNumberOfAttributes(relation);
+       if (indnatts != IndexRelationGetNumberOfAttributes(relation))
                elog(ERROR, "relnatts disagrees with indnatts for index %u",
                         RelationGetRelid(relation));
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(relation);
 
        /*
         * Make the private context to hold index access info.  The reason we need
@@ -1610,17 +1612,18 @@ RelationInitIndexAccessInfo(Relation relation)
        InitIndexAmRoutine(relation);
 
        /*
-        * Allocate arrays to hold data
+        * Allocate arrays to hold data. Opclasses are not used for included
+        * columns, so allocate them for indnkeyatts only.
         */
        relation->rd_opfamily = (Oid *)
-               MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
+               MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
        relation->rd_opcintype = (Oid *)
-               MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
+               MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
 
        amsupport = relation->rd_amroutine->amsupport;
        if (amsupport > 0)
        {
-               int                     nsupport = natts * amsupport;
+               int                     nsupport = indnatts * amsupport;
 
                relation->rd_support = (RegProcedure *)
                        MemoryContextAllocZero(indexcxt, nsupport * sizeof(RegProcedure));
@@ -1634,10 +1637,10 @@ RelationInitIndexAccessInfo(Relation relation)
        }
 
        relation->rd_indcollation = (Oid *)
-               MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
+               MemoryContextAllocZero(indexcxt, indnatts * sizeof(Oid));
 
        relation->rd_indoption = (int16 *)
-               MemoryContextAllocZero(indexcxt, natts * sizeof(int16));
+               MemoryContextAllocZero(indexcxt, indnatts * sizeof(int16));
 
        /*
         * indcollation cannot be referenced directly through the C struct,
@@ -1650,7 +1653,7 @@ RelationInitIndexAccessInfo(Relation relation)
                                                           &isnull);
        Assert(!isnull);
        indcoll = (oidvector *) DatumGetPointer(indcollDatum);
-       memcpy(relation->rd_indcollation, indcoll->values, natts * sizeof(Oid));
+       memcpy(relation->rd_indcollation, indcoll->values, indnatts * sizeof(Oid));
 
        /*
         * indclass cannot be referenced directly through the C struct, because it
@@ -1671,7 +1674,7 @@ RelationInitIndexAccessInfo(Relation relation)
         */
        IndexSupportInitialize(indclass, relation->rd_support,
                                                   relation->rd_opfamily, relation->rd_opcintype,
-                                                  amsupport, natts);
+                                                  amsupport, indnkeyatts);
 
        /*
         * Similarly extract indoption and copy it to the cache entry
@@ -1682,7 +1685,7 @@ RelationInitIndexAccessInfo(Relation relation)
                                                                 &isnull);
        Assert(!isnull);
        indoption = (int2vector *) DatumGetPointer(indoptionDatum);
-       memcpy(relation->rd_indoption, indoption->values, natts * sizeof(int16));
+       memcpy(relation->rd_indoption, indoption->values, indnatts * sizeof(int16));
 
        /*
         * expressions, predicate, exclusion caches will be filled later
@@ -5064,20 +5067,28 @@ restart:
                {
                        int                     attrnum = indexInfo->ii_KeyAttrNumbers[i];
 
+                       /*
+                        * Since we have covering indexes with non-key columns, we must
+                        * handle them accurately here. non-key columns must be added into
+                        * indexattrs, since they are in index, and HOT-update shouldn't
+                        * miss them. Obviously, non-key columns couldn't be referenced by
+                        * foreign key or identity key. Hence we do not include them into
+                        * uindexattrs, pkindexattrs and idindexattrs bitmaps.
+                        */
                        if (attrnum != 0)
                        {
                                indexattrs = bms_add_member(indexattrs,
                                                                                        attrnum - FirstLowInvalidHeapAttributeNumber);
 
-                               if (isKey)
+                               if (isKey && i < indexInfo->ii_NumIndexKeyAttrs)
                                        uindexattrs = bms_add_member(uindexattrs,
                                                                                                 attrnum - FirstLowInvalidHeapAttributeNumber);
 
-                               if (isPK)
+                               if (isPK && i < indexInfo->ii_NumIndexKeyAttrs)
                                        pkindexattrs = bms_add_member(pkindexattrs,
                                                                                                  attrnum - FirstLowInvalidHeapAttributeNumber);
 
-                               if (isIDKey)
+                               if (isIDKey && i < indexInfo->ii_NumIndexKeyAttrs)
                                        idindexattrs = bms_add_member(idindexattrs,
                                                                                                  attrnum - FirstLowInvalidHeapAttributeNumber);
                        }
@@ -5195,7 +5206,7 @@ RelationGetExclusionInfo(Relation indexRelation,
                                                 Oid **procs,
                                                 uint16 **strategies)
 {
-       int                     ncols = indexRelation->rd_rel->relnatts;
+       int                     indnkeyatts;
        Oid                *ops;
        Oid                *funcs;
        uint16     *strats;
@@ -5207,17 +5218,19 @@ RelationGetExclusionInfo(Relation indexRelation,
        MemoryContext oldcxt;
        int                     i;
 
+       indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation);
+
        /* Allocate result space in caller context */
-       *operators = ops = (Oid *) palloc(sizeof(Oid) * ncols);
-       *procs = funcs = (Oid *) palloc(sizeof(Oid) * ncols);
-       *strategies = strats = (uint16 *) palloc(sizeof(uint16) * ncols);
+       *operators = ops = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
+       *procs = funcs = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
+       *strategies = strats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts);
 
        /* Quick exit if we have the data cached already */
        if (indexRelation->rd_exclstrats != NULL)
        {
-               memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * ncols);
-               memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * ncols);
-               memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * ncols);
+               memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * indnkeyatts);
+               memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * indnkeyatts);
+               memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * indnkeyatts);
                return;
        }
 
@@ -5266,12 +5279,12 @@ RelationGetExclusionInfo(Relation indexRelation,
                arr = DatumGetArrayTypeP(val);  /* ensure not toasted */
                nelem = ARR_DIMS(arr)[0];
                if (ARR_NDIM(arr) != 1 ||
-                       nelem != ncols ||
+                       nelem != indnkeyatts ||
                        ARR_HASNULL(arr) ||
                        ARR_ELEMTYPE(arr) != OIDOID)
                        elog(ERROR, "conexclop is not a 1-D Oid array");
 
-               memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * ncols);
+               memcpy(ops, ARR_DATA_PTR(arr), sizeof(Oid) * indnkeyatts);
        }
 
        systable_endscan(conscan);
@@ -5282,7 +5295,7 @@ RelationGetExclusionInfo(Relation indexRelation,
                         RelationGetRelationName(indexRelation));
 
        /* We need the func OIDs and strategy numbers too */
-       for (i = 0; i < ncols; i++)
+       for (i = 0; i < indnkeyatts; i++)
        {
                funcs[i] = get_opcode(ops[i]);
                strats[i] = get_op_opfamily_strategy(ops[i],
@@ -5295,12 +5308,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 
        /* Save a copy of the results in the relcache entry. */
        oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
-       indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * ncols);
-       indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * ncols);
-       indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * ncols);
-       memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * ncols);
-       memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * ncols);
-       memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * ncols);
+       indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
+       indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
+       indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts);
+       memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * indnkeyatts);
+       memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * indnkeyatts);
+       memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * indnkeyatts);
        MemoryContextSwitchTo(oldcxt);
 }
 
index e433faad86af1cb428bcb7e401c64bf9e1c1810c..a0c0d6f701a289c93e6ae66fe439a137c65b6ad9 100644 (file)
@@ -900,7 +900,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
                         workMem, randomAccess ? 't' : 'f');
 #endif
 
-       state->nKeys = RelationGetNumberOfAttributes(indexRel);
+       state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel);
 
        TRACE_POSTGRESQL_SORT_START(CLUSTER_SORT,
                                                                false,  /* no unique check */
@@ -995,7 +995,7 @@ tuplesort_begin_index_btree(Relation heapRel,
                         workMem, randomAccess ? 't' : 'f');
 #endif
 
-       state->nKeys = RelationGetNumberOfAttributes(indexRel);
+       state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel);
 
        TRACE_POSTGRESQL_SORT_START(INDEX_SORT,
                                                                enforceUnique,
@@ -1015,7 +1015,6 @@ tuplesort_begin_index_btree(Relation heapRel,
        state->enforceUnique = enforceUnique;
 
        indexScanKey = _bt_mkscankey_nodata(indexRel);
-       state->nKeys = RelationGetNumberOfAttributes(indexRel);
 
        /* Prepare SortSupport data for each column */
        state->sortKeys = (SortSupport) palloc0(state->nKeys *
index 69016a6c4d31a1f3bcefbf863797ea9d76045df8..d4c1b3261ee894cfa24915e2449a2b32eb8b664f 100644 (file)
@@ -6726,7 +6726,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                i_indexname,
                                i_parentidx,
                                i_indexdef,
-                               i_indnkeys,
+                               i_indnnkeyatts,
+                               i_indnatts,
                                i_indkey,
                                i_indisclustered,
                                i_indisreplident,
@@ -6783,6 +6784,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                                          "t.relname AS indexname, "
                                                          "inh.inhparent AS parentidx, "
                                                          "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                                                         "i.indnkeyatts AS indnkeyatts, "
+                                                         "i.indnatts AS indnatts, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
                                                          "i.indisreplident, t.relpages, "
@@ -6819,6 +6822,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                                          "t.relname AS indexname, "
                                                          "0 AS parentidx, "
                                                          "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                                                         "i.indnatts AS indnkeyatts, "
+                                                         "i.indnatts AS indnatts, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
                                                          "i.indisreplident, t.relpages, "
@@ -6851,6 +6856,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                                          "t.relname AS indexname, "
                                                          "0 AS parentidx, "
                                                          "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                                                         "i.indnatts AS indnkeyatts, "
+                                                         "i.indnatts AS indnatts, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
                                                          "false AS indisreplident, t.relpages, "
@@ -6879,6 +6886,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                                                          "t.relname AS indexname, "
                                                          "0 AS parentidx, "
                                                          "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                                                         "i.indnatts AS indnkeyatts, "
+                                                         "i.indnatts AS indnatts, "
                                                          "t.relnatts AS indnkeys, "
                                                          "i.indkey, i.indisclustered, "
                                                          "false AS indisreplident, t.relpages, "
@@ -6943,7 +6952,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                i_indexname = PQfnumber(res, "indexname");
                i_parentidx = PQfnumber(res, "parentidx");
                i_indexdef = PQfnumber(res, "indexdef");
-               i_indnkeys = PQfnumber(res, "indnkeys");
+               i_indnnkeyatts = PQfnumber(res, "indnkeyatts");
+               i_indnatts = PQfnumber(res, "indnatts");
                i_indkey = PQfnumber(res, "indkey");
                i_indisclustered = PQfnumber(res, "indisclustered");
                i_indisreplident = PQfnumber(res, "indisreplident");
@@ -6976,12 +6986,13 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                        indxinfo[j].dobj.namespace = tbinfo->dobj.namespace;
                        indxinfo[j].indextable = tbinfo;
                        indxinfo[j].indexdef = pg_strdup(PQgetvalue(res, j, i_indexdef));
-                       indxinfo[j].indnkeys = atoi(PQgetvalue(res, j, i_indnkeys));
+                       indxinfo[j].indnkeyattrs = atoi(PQgetvalue(res, j, i_indnnkeyatts));
+                       indxinfo[j].indnattrs = atoi(PQgetvalue(res, j, i_indnatts));
                        indxinfo[j].tablespace = pg_strdup(PQgetvalue(res, j, i_tablespace));
                        indxinfo[j].indreloptions = pg_strdup(PQgetvalue(res, j, i_indreloptions));
-                       indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnkeys * sizeof(Oid));
+                       indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid));
                        parseOidArray(PQgetvalue(res, j, i_indkey),
-                                                 indxinfo[j].indkeys, indxinfo[j].indnkeys);
+                                                 indxinfo[j].indkeys, indxinfo[j].indnattrs);
                        indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
                        indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
                        indxinfo[j].parentidx = atooid(PQgetvalue(res, j, i_parentidx));
@@ -16342,7 +16353,7 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
                {
                        appendPQExpBuffer(q, "%s (",
                                                          coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
-                       for (k = 0; k < indxinfo->indnkeys; k++)
+                       for (k = 0; k < indxinfo->indnkeyattrs; k++)
                        {
                                int                     indkey = (int) indxinfo->indkeys[k];
                                const char *attname;
@@ -16356,6 +16367,23 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
                                                                  fmtId(attname));
                        }
 
+                       if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
+                               appendPQExpBuffer(q, ") INCLUDE (");
+
+                       for (k = indxinfo->indnkeyattrs; k < indxinfo->indnattrs; k++)
+                       {
+                               int                     indkey = (int) indxinfo->indkeys[k];
+                               const char *attname;
+
+                               if (indkey == InvalidAttrNumber)
+                                       break;
+                               attname = getAttrName(indkey, tbinfo);
+
+                               appendPQExpBuffer(q, "%s%s",
+                                                                 (k == indxinfo->indnkeyattrs) ? "" : ", ",
+                                                                 fmtId(attname));
+                       }
+
                        appendPQExpBufferChar(q, ')');
 
                        if (nonemptyReloptions(indxinfo->indreloptions))
index c2314758dea374fcbf08e866d12054e13c66a920..e96c662b1e9017099fe0ff43989ef90e32919860 100644 (file)
@@ -360,8 +360,10 @@ typedef struct _indxInfo
        char       *indexdef;
        char       *tablespace;         /* tablespace in which index is stored */
        char       *indreloptions;      /* options specified by WITH (...) */
-       int                     indnkeys;
-       Oid                *indkeys;
+       int                     indnkeyattrs;   /* number of index key attributes */
+       int                     indnattrs;              /* total number of index attributes */
+       Oid                *indkeys;            /* In spite of the name 'indkeys' this field
+                                                                * contains both key and nonkey attributes */
        bool            indisclustered;
        bool            indisreplident;
        Oid                     parentidx;              /* if partitioned, parent index OID */
index 8d7bc246e672ca68eded50add632bd0978f909d3..d16fa6823b6b837fa07ff999c1b388dafa028e3f 100644 (file)
@@ -191,6 +191,8 @@ typedef struct IndexAmRoutine
        bool            ampredlocks;
        /* does AM support parallel scan? */
        bool            amcanparallel;
+       /* does AM support columns included with clause INCLUDE? */
+       bool            amcaninclude;
        /* type of data stored in index, or InvalidOid if variable */
        Oid                     amkeytype;
 
index f94bcf9e296e8dd52b54523856c09956cfe79fb1..d6c306e9695a9f71327ad86cc577494d407de5f5 100644 (file)
@@ -280,7 +280,7 @@ typedef HashMetaPageData *HashMetaPage;
                                  sizeof(ItemIdData) - \
                                  MAXALIGN(sizeof(HashPageOpaqueData)))
 
-#define INDEX_MOVED_BY_SPLIT_MASK      0x2000
+#define INDEX_MOVED_BY_SPLIT_MASK      INDEX_AM_RESERVED_BIT
 
 #define HASH_MIN_FILLFACTOR                    10
 #define HASH_DEFAULT_FILLFACTOR                75
index 9be3442c66daa3c9a8cd76691011f2eb61a4e91d..04526a8e59fb198b2716b75386aa0eed0b52c324 100644 (file)
@@ -41,7 +41,7 @@ typedef struct IndexTupleData
         *
         * 15th (high) bit: has nulls
         * 14th bit: has var-width attributes
-        * 13th bit: unused
+        * 13th bit: AM-defined meaning
         * 12-0 bit: size of tuple
         * ---------------
         */
@@ -63,7 +63,8 @@ typedef IndexAttributeBitMapData * IndexAttributeBitMap;
  * t_info manipulation macros
  */
 #define INDEX_SIZE_MASK 0x1FFF
-/* bit 0x2000 is reserved for index-AM specific usage */
+#define INDEX_AM_RESERVED_BIT 0x2000   /* reserved for index-AM specific
+                                                                                * usage */
 #define INDEX_VAR_MASK 0x4000
 #define INDEX_NULL_MASK 0x8000
 
@@ -146,5 +147,7 @@ extern Datum nocache_index_getattr(IndexTuple tup, int attnum,
 extern void index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
                                   Datum *values, bool *isnull);
 extern IndexTuple CopyIndexTuple(IndexTuple source);
+extern IndexTuple index_truncate_tuple(TupleDesc tupleDescriptor,
+                                        IndexTuple olditup, int new_indnatts);
 
 #endif                                                 /* ITUP_H */
index f532f3ffff3ca8d4bcca4f5dc60f5694a530e232..36619b220f10bc85119c41c922d7b7a6d35cab9f 100644 (file)
@@ -139,31 +139,6 @@ typedef struct BTMetaPageData
 #define BTREE_DEFAULT_FILLFACTOR       90
 #define BTREE_NONLEAF_FILLFACTOR       70
 
-/*
- *     Test whether two btree entries are "the same".
- *
- *     Old comments:
- *     In addition, we must guarantee that all tuples in the index are unique,
- *     in order to satisfy some assumptions in Lehman and Yao.  The way that we
- *     do this is by generating a new OID for every insertion that we do in the
- *     tree.  This adds eight bytes to the size of btree index tuples.  Note
- *     that we do not use the OID as part of a composite key; the OID only
- *     serves as a unique identifier for a given index tuple (logical position
- *     within a page).
- *
- *     New comments:
- *     actually, we must guarantee that all tuples in A LEVEL
- *     are unique, not in ALL INDEX. So, we can use the t_tid
- *     as unique identifier for a given index tuple (logical position
- *     within a level). - vadim 04/09/97
- */
-#define BTTidSame(i1, i2)      \
-       ((ItemPointerGetBlockNumber(&(i1)) == ItemPointerGetBlockNumber(&(i2))) && \
-        (ItemPointerGetOffsetNumber(&(i1)) == ItemPointerGetOffsetNumber(&(i2))))
-#define BTEntrySame(i1, i2) \
-       BTTidSame((i1)->t_tid, (i2)->t_tid)
-
-
 /*
  *     In general, the btree code tries to localize its knowledge about
  *     page layout to a couple of routines.  However, we need a special
@@ -212,6 +187,68 @@ typedef struct BTMetaPageData
 #define P_FIRSTDATAKEY(opaque) (P_RIGHTMOST(opaque) ? P_HIKEY : P_FIRSTKEY)
 
 
+/*
+ * B-tree index with INCLUDE clause has non-key (included) attributes, which
+ * are used solely in index-only scans.  Those non-key attributes are present
+ * in leaf index tuples which point to corresponding heap tuples.  However,
+ * tree also contains "pivot" tuples.  Pivot tuples are used for navigation
+ * during tree traversal.  Pivot tuples include tuples on non-leaf pages and
+ * high key tuples.  Such, tuples don't need to included attributes, because
+ * they have no use during tree traversal.  This is why we truncate them in
+ * order to save some space.  Therefore, B-tree index with INCLUDE clause
+ * contain tuples with variable number of attributes.
+ *
+ * In order to keep on-disk compatibility with upcoming suffix truncation of
+ * pivot tuples, we store number of attributes present inside tuple itself.
+ * Thankfully, offset number is always unused in pivot tuple.  So, we use free
+ * bit of index tuple flags as sign that offset have alternative meaning: it
+ * stores number of keys present in index tuple (12 bit is far enough for that).
+ * And we have 4 bits reserved for future usage.
+ *
+ * Right now INDEX_ALT_TID_MASK is set only on truncation of non-key
+ * attributes of included indexes.  But potentially every pivot index tuple
+ * might have INDEX_ALT_TID_MASK set.  Then this tuple should have number of
+ * attributes correctly set in BT_N_KEYS_OFFSET_MASK, and in future it might
+ * use some bits of BT_RESERVED_OFFSET_MASK.
+ *
+ * Non-pivot tuples might also use bit of BT_RESERVED_OFFSET_MASK.  Despite
+ * they store heap tuple offset, higher bits of offset are always free.
+ */
+#define INDEX_ALT_TID_MASK             INDEX_AM_RESERVED_BIT   /* flag indicating t_tid
+                                                                                                                * offset has an
+                                                                                                                * alternative meaning */
+#define BT_RESERVED_OFFSET_MASK        0xF000  /* mask of bits in t_tid offset
+                                                                                * reserved for future usage */
+#define BT_N_KEYS_OFFSET_MASK  0x0FFF  /* mask of bits in t_tid offset
+                                                                                * holding number of attributes
+                                                                                * actually present in index tuple */
+
+/* Acess to downlink block number */
+#define BTreeInnerTupleGetDownLink(itup) \
+       ItemPointerGetBlockNumberNoCheck(&((itup)->t_tid))
+
+#define BTreeInnerTupleSetDownLink(itup, blkno) \
+       ItemPointerSetBlockNumber(&((itup)->t_tid), (blkno))
+
+/* Set number of attributes to B-tree index tuple overriding t_tid offset */
+#define BTreeTupSetNAtts(itup, n) \
+       do { \
+               (itup)->t_info |= INDEX_ALT_TID_MASK; \
+               ItemPointerSetOffsetNumber(&(itup)->t_tid, n); \
+       } while(0)
+
+/* Get number of attributes in B-tree index tuple */
+#define BTreeTupGetNAtts(itup, index)  \
+       ( \
+               (itup)->t_info & INDEX_ALT_TID_MASK ? \
+               ( \
+                       AssertMacro((ItemPointerGetOffsetNumberNoCheck(&(itup)->t_tid) & BT_RESERVED_OFFSET_MASK) == 0), \
+                       ItemPointerGetOffsetNumberNoCheck(&(itup)->t_tid) & BT_N_KEYS_OFFSET_MASK \
+               ) \
+               : \
+               IndexRelationGetNumberOfAttributes(index) \
+       )
+
 /*
  *     Operator strategy numbers for B-tree have been moved to access/stratnum.h,
  *     because many places need to use them in ScanKeyInit() calls.
@@ -265,7 +302,7 @@ typedef struct BTStackData
 {
        BlockNumber bts_blkno;
        OffsetNumber bts_offset;
-       IndexTupleData bts_btentry;
+       BlockNumber bts_btentry;
        struct BTStackData *bts_parent;
 } BTStackData;
 
@@ -524,6 +561,7 @@ extern bool _bt_first(IndexScanDesc scan, ScanDirection dir);
 extern bool _bt_next(IndexScanDesc scan, ScanDirection dir);
 extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
                                 Snapshot snapshot);
+extern bool _bt_check_natts(Relation index, Page page, OffsetNumber offnum);
 
 /*
  * prototypes for functions in nbtutils.c
@@ -552,6 +590,7 @@ extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
                   IndexAMProperty prop, const char *propname,
                   bool *res, bool *isnull);
+extern IndexTuple _bt_truncate_tuple(Relation idxrel, IndexTuple olditup);
 
 /*
  * prototypes for functions in nbtvalidate.c
index a8ccdcec426dde3c48282cadbaef2f97ffde7f53..c55b618ff7d2c8261a6d848fcedcb6aef26931ea 100644 (file)
@@ -28,7 +28,8 @@
 #define XLOG_BTREE_INSERT_META 0x20    /* same, plus update metapage */
 #define XLOG_BTREE_SPLIT_L             0x30    /* add index tuple with split */
 #define XLOG_BTREE_SPLIT_R             0x40    /* as above, new item on right */
-/* 0x50 and 0x60 are unused */
+#define XLOG_BTREE_SPLIT_L_HIGHKEY 0x50 /* as above, include truncated highkey */
+#define XLOG_BTREE_SPLIT_R_HIGHKEY 0x60 /* as above, include truncated highkey */
 #define XLOG_BTREE_DELETE              0x70    /* delete leaf index tuples for a page */
 #define XLOG_BTREE_UNLINK_PAGE 0x80    /* delete a half-dead page */
 #define XLOG_BTREE_UNLINK_PAGE_META 0x90       /* same, and update metapage */
@@ -82,10 +83,11 @@ typedef struct xl_btree_insert
  * Note: the four XLOG_BTREE_SPLIT xl_info codes all use this data record.
  * The _L and _R variants indicate whether the inserted tuple went into the
  * left or right split page (and thus, whether newitemoff and the new item
- * are stored or not).  The _ROOT variants indicate that we are splitting
- * the root page, and thus that a newroot record rather than an insert or
- * split record should follow.  Note that a split record never carries a
- * metapage update --- we'll do that in the parent-level update.
+ * are stored or not).  The _HIGHKEY variants indicate that we've logged
+ * explicitly left page high key value, otherwise redo should use right page
+ * leftmost key as a left page high key.  _HIGHKEY is specified for internal
+ * pages where right page leftmost key is suppressed, and for leaf pages
+ * of covering indexes where high key have non-key attributes truncated.
  *
  * Backup Blk 0: original page / new left page
  *
index 5641c60593b6f01b2f7e704ca09630957755b9d4..dd69816f9e5d020e4d8a0426bafa66085ce8177b 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201804072
+#define CATALOG_VERSION_NO     201804073
 
 #endif
index 773713b49ddec0929e79dc7f2492c09dbba69ca2..a0fb5f8243209fc3ed9340f1a4d07ec481cd82b3 100644 (file)
@@ -104,6 +104,12 @@ CATALOG(pg_constraint,2606)
         */
        int16           conkey[1];
 
+       /*
+        * Columns of conrelid that the constraint does not apply to, but included
+        * into the same index with key columns.
+        */
+       int16           conincluding[1];
+
        /*
         * If a foreign key, the referenced columns of confrelid
         */
@@ -156,7 +162,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
  *             compiler constants for pg_constraint
  * ----------------
  */
-#define Natts_pg_constraint                                    25
+#define Natts_pg_constraint                                    26
 #define Anum_pg_constraint_conname                     1
 #define Anum_pg_constraint_connamespace                2
 #define Anum_pg_constraint_contype                     3
@@ -175,13 +181,14 @@ typedef FormData_pg_constraint *Form_pg_constraint;
 #define Anum_pg_constraint_coninhcount         16
 #define Anum_pg_constraint_connoinherit                17
 #define Anum_pg_constraint_conkey                      18
-#define Anum_pg_constraint_confkey                     19
-#define Anum_pg_constraint_conpfeqop           20
-#define Anum_pg_constraint_conppeqop           21
-#define Anum_pg_constraint_conffeqop           22
-#define Anum_pg_constraint_conexclop           23
-#define Anum_pg_constraint_conbin                      24
-#define Anum_pg_constraint_consrc                      25
+#define Anum_pg_constraint_conincluding                19
+#define Anum_pg_constraint_confkey                     20
+#define Anum_pg_constraint_conpfeqop           21
+#define Anum_pg_constraint_conppeqop           22
+#define Anum_pg_constraint_conffeqop           23
+#define Anum_pg_constraint_conexclop           24
+#define Anum_pg_constraint_conbin                      25
+#define Anum_pg_constraint_consrc                      26
 
 /* ----------------
  *             initial contents of pg_constraint
index 0170e08c450622197c748dda7ce9ee24bf451b20..5f64409f3d5893c37867a3ab020050c027dd8f6c 100644 (file)
@@ -50,6 +50,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
                                          Oid relId,
                                          const int16 *constraintKey,
                                          int constraintNKeys,
+                                         int constraintNTotalKeys,
                                          Oid domainId,
                                          Oid indexRelId,
                                          Oid foreignRelId,
index 057a9f7fe4acf3f3e3691b2181071aa435c394eb..6ae03dbcbbc70b985f284c786e27397aecc9c7cb 100644 (file)
@@ -32,7 +32,8 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
 {
        Oid                     indexrelid;             /* OID of the index */
        Oid                     indrelid;               /* OID of the relation it indexes */
-       int16           indnatts;               /* number of columns in index */
+       int16           indnatts;               /* total number of columns in index */
+       int16           indnkeyatts;    /* number of key columns in index */
        bool            indisunique;    /* is this a unique index? */
        bool            indisprimary;   /* is this index for primary key? */
        bool            indisexclusion; /* is this index for exclusion constraint? */
@@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index;
  *             compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index                                 19
+#define Natts_pg_index                                 20
 #define Anum_pg_index_indexrelid               1
 #define Anum_pg_index_indrelid                 2
 #define Anum_pg_index_indnatts                 3
-#define Anum_pg_index_indisunique              4
-#define Anum_pg_index_indisprimary             5
-#define Anum_pg_index_indisexclusion   6
-#define Anum_pg_index_indimmediate             7
-#define Anum_pg_index_indisclustered   8
-#define Anum_pg_index_indisvalid               9
-#define Anum_pg_index_indcheckxmin             10
-#define Anum_pg_index_indisready               11
-#define Anum_pg_index_indislive                        12
-#define Anum_pg_index_indisreplident   13
-#define Anum_pg_index_indkey                   14
-#define Anum_pg_index_indcollation             15
-#define Anum_pg_index_indclass                 16
-#define Anum_pg_index_indoption                        17
-#define Anum_pg_index_indexprs                 18
-#define Anum_pg_index_indpred                  19
+#define Anum_pg_index_indnkeyatts              4
+#define Anum_pg_index_indisunique              5
+#define Anum_pg_index_indisprimary             6
+#define Anum_pg_index_indisexclusion   7
+#define Anum_pg_index_indimmediate             8
+#define Anum_pg_index_indisclustered   9
+#define Anum_pg_index_indisvalid               10
+#define Anum_pg_index_indcheckxmin             11
+#define Anum_pg_index_indisready               12
+#define Anum_pg_index_indislive                        13
+#define Anum_pg_index_indisreplident   14
+#define Anum_pg_index_indkey                   15
+#define Anum_pg_index_indcollation             16
+#define Anum_pg_index_indclass                 17
+#define Anum_pg_index_indoption                        18
+#define Anum_pg_index_indexprs                 19
+#define Anum_pg_index_indpred                  20
 
 /*
  * Index AMs that support ordered scans must support these two indoption
index 538e679cdf3637e579de0ec099bb98d8c9bfce5e..4ad5131aa9704309e4767c9b671d9f10ded18783 100644 (file)
@@ -118,9 +118,11 @@ typedef struct ExprState
  *             entries for a particular index.  Used for both index_build and
  *             retail creation of index entries.
  *
- *             NumIndexAttrs           number of columns in this index
+ *             NumIndexAttrs           total number of columns in this index
+ *             NumIndexKeyAttrs        number of key columns in index
  *             KeyAttrNumbers          underlying-rel attribute numbers used as keys
- *                                                     (zeroes indicate expressions)
+ *                                                     (zeroes indicate expressions). It also contains
+ *                                                     info about included columns.
  *             Expressions                     expr trees for expression entries, or NIL if none
  *             ExpressionsState        exec state for expressions, or NIL if none
  *             Predicate                       partial-index predicate, or NIL if none
@@ -146,7 +148,8 @@ typedef struct ExprState
 typedef struct IndexInfo
 {
        NodeTag         type;
-       int                     ii_NumIndexAttrs;
+       int                     ii_NumIndexAttrs;       /* total number of columns in index */
+       int                     ii_NumIndexKeyAttrs;    /* number of key columns in index */
        AttrNumber      ii_KeyAttrNumbers[INDEX_MAX_KEYS];
        List       *ii_Expressions; /* list of Expr */
        List       *ii_ExpressionsState;        /* list of ExprState */
index 06abb70e947e7f7dd272670db2cb0a8646d7f67a..c8405386cf91559541837b17a7815bc384aa5fc0 100644 (file)
@@ -2147,7 +2147,10 @@ typedef struct Constraint
        char            generated_when;
 
        /* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
-       List       *keys;                       /* String nodes naming referenced column(s) */
+       List       *keys;                       /* String nodes naming referenced key
+                                                                * column(s) */
+       List       *including;          /* String nodes naming referenced nonkey
+                                                                * column(s) */
 
        /* Fields used for EXCLUSION constraints: */
        List       *exclusions;         /* list of (IndexElem, operator name) pairs */
@@ -2760,6 +2763,8 @@ typedef struct IndexStmt
        char       *accessMethod;       /* name of access method (eg. btree) */
        char       *tableSpace;         /* tablespace, or NULL for default */
        List       *indexParams;        /* columns to index: a list of IndexElem */
+       List       *indexIncludingParams;       /* additional columns to index: a list
+                                                                                * of IndexElem */
        List       *options;            /* WITH clause options: a list of DefElem */
        Node       *whereClause;        /* qualification (partial-index predicate) */
        List       *excludeOpNames; /* exclusion operator names, or NIL if none */
index acb8814924f3ec29d728a8c03e26fefd881ddc7e..73a41c5475a39263d3b7f2bc83263e4ef7bcc67c 100644 (file)
@@ -707,11 +707,12 @@ typedef struct RelOptInfo
  * IndexOptInfo
  *             Per-index information for planning/optimization
  *
- *             indexkeys[], indexcollations[], opfamily[], and opcintype[]
- *             each have ncolumns entries.
+ *             indexkeys[], indexcollations[] each have ncolumns entries.
+ *             opfamily[], and opcintype[]     each have nkeycolumns entries. They do
+ *             not contain any information about included attributes.
  *
- *             sortopfamily[], reverse_sort[], and nulls_first[] likewise have
- *             ncolumns entries, if the index is ordered; but if it is unordered,
+ *             sortopfamily[], reverse_sort[], and nulls_first[] have
+ *             nkeycolumns entries, if the index is ordered; but if it is unordered,
  *             those pointers are NULL.
  *
  *             Zeroes in the indexkeys[] array indicate index columns that are
@@ -748,7 +749,9 @@ typedef struct IndexOptInfo
 
        /* index descriptor information */
        int                     ncolumns;               /* number of columns in index */
-       int                *indexkeys;          /* column numbers of index's keys, or 0 */
+       int                     nkeycolumns;    /* number of key columns in index */
+       int                *indexkeys;          /* column numbers of index's attributes both
+                                                                * key and included columns, or 0 */
        Oid                *indexcollations;    /* OIDs of collations of index columns */
        Oid                *opfamily;           /* OIDs of operator families for columns */
        Oid                *opcintype;          /* OIDs of opclass declared input data types */
index 4dff55a8e9970f5393150f2306c5dddbc82f8703..81f758afbf0192f97e8ea880e0228eb45af85329 100644 (file)
@@ -196,6 +196,7 @@ PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
+PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
index 9826c67fc418a448b95f39e3e3182b9a9b1cfe2c..ffffde01da9c9f493b945f146cf277c1e84e8cc4 100644 (file)
@@ -438,10 +438,24 @@ typedef struct ViewOptions
 
 /*
  * RelationGetNumberOfAttributes
- *             Returns the number of attributes in a relation.
+ *             Returns the total number of attributes in a relation.
  */
 #define RelationGetNumberOfAttributes(relation) ((relation)->rd_rel->relnatts)
 
+/*
+ * IndexRelationGetNumberOfAttributes
+ *             Returns the number of attributes in an index.
+ */
+#define IndexRelationGetNumberOfAttributes(relation) \
+               ((relation)->rd_index->indnatts)
+
+/*
+ * IndexRelationGetNumberOfKeyAttributes
+ *             Returns the number of key attributes in an index.
+ */
+#define IndexRelationGetNumberOfKeyAttributes(relation) \
+               ((relation)->rd_index->indnkeyatts)
+
 /*
  * RelationGetDescr
  *             Returns tuple descriptor for a relation.
index f1e5bde357cb8a4c51782a4274c8a18a380c5af8..8a8ec944473864d62d7b714770f121c91aa7fad4 100644 (file)
@@ -3,7 +3,7 @@
 
 setup
 {
-  CREATE TABLE ints (key int primary key, val text);
+  CREATE TABLE ints (key int, val text, PRIMARY KEY (key) INCLUDE (val));
 }
 
 teardown
index cd7e3f42feb9b644644e523fe48d6c08eae1f55f..f5b4f601b58c88838fe8421928389d47f2eac1a6 100644 (file)
@@ -7,7 +7,7 @@
 setup
 {
   CREATE TABLE upsert (key text not null, payload text);
-  CREATE UNIQUE INDEX ON upsert(lower(key));
+  CREATE UNIQUE INDEX ON upsert(lower(key)) INCLUDE (payload);
 }
 
 teardown
index 1630282d0f44ab2a455c7f11acc82a4205cadd91..3fb424af0ed74b6b5eaf09238f9e37809220d580 100644 (file)
@@ -8,7 +8,7 @@
 setup
 {
     DROP TABLE IF EXISTS lcku_table;
-    CREATE TABLE lcku_table (id INTEGER PRIMARY KEY, value TEXT);
+    CREATE TABLE lcku_table (id INTEGER, value TEXT, PRIMARY KEY (id) INCLUDE (value));
     INSERT INTO lcku_table VALUES (1, 'one');
     INSERT INTO lcku_table VALUES (3, 'two');
 }
index 7042b9399cf4832232abaee6b30b8e218e18f150..2ffe87d152b1731b4efdd71038ec7dae86f488cd 100644 (file)
@@ -7,8 +7,9 @@
 setup
 {
   CREATE TABLE foo (
-       key             int PRIMARY KEY,
-       value   int
+       key             int,
+       value   int,
+       PRIMARY KEY (key) INCLUDE (value)
   );
 
   INSERT INTO foo VALUES (1, 1);
index 09757c5a749217e31962dd06b98c2288aafc5955..fe5b698669c40ad5abbcaa2036994e02e5b33c99 100644 (file)
@@ -2433,6 +2433,25 @@ DETAIL:  Key ((f1 || f2))=(ABCDEF) already exists.
 -- but this shouldn't:
 INSERT INTO func_index_heap VALUES('QWERTY');
 --
+-- Test unique index with included columns
+--
+CREATE TABLE covering_index_heap (f1 int, f2 int, f3 text);
+CREATE UNIQUE INDEX covering_index_index on covering_index_heap (f1,f2) INCLUDE(f3);
+INSERT INTO covering_index_heap VALUES(1,1,'AAA');
+INSERT INTO covering_index_heap VALUES(1,2,'AAA');
+-- this should fail because of unique index on f1,f2:
+INSERT INTO covering_index_heap VALUES(1,2,'BBB');
+ERROR:  duplicate key value violates unique constraint "covering_index_index"
+DETAIL:  Key (f1, f2)=(1, 2) already exists.
+-- and this shouldn't:
+INSERT INTO covering_index_heap VALUES(1,4,'AAA');
+-- Try to build index on table that already contains data
+CREATE UNIQUE INDEX covering_pkey on covering_index_heap (f1,f2) INCLUDE(f3);
+-- Try to use existing covering index as primary key
+ALTER TABLE covering_index_heap ADD CONSTRAINT covering_pkey PRIMARY KEY USING INDEX
+covering_pkey;
+DROP TABLE covering_index_heap;
+--
 -- Also try building functional, expressional, and partial indexes on
 -- tables that already contain data.
 --
diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out
new file mode 100644 (file)
index 0000000..1d253ee
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * 1.test CREATE INDEX
+ */
+-- Regular index with included columns
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_idx ON tbl using btree (c1, c2) INCLUDE (c3,c4);
+-- must fail because of intersection of key and included columns
+CREATE INDEX tbl_idx ON tbl using btree (c1, c2) INCLUDE (c1,c3);
+ERROR:  included columns must not intersect with key columns
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+                             pg_get_indexdef                              
+--------------------------------------------------------------------------
+ CREATE INDEX tbl_idx ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+DROP TABLE tbl;
+-- Unique index and unique constraint
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree (c1, c2) INCLUDE (c3, c4);
+ALTER TABLE tbl add UNIQUE USING INDEX tbl_idx_unique;
+ALTER TABLE tbl add UNIQUE (c1, c2) INCLUDE (c3, c4);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+                                       pg_get_indexdef                                       
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+ CREATE UNIQUE INDEX tbl_idx_unique ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(2 rows)
+
+DROP TABLE tbl;
+-- Unique index and unique constraint. Both must fail.
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree (c1, c2) INCLUDE (c3, c4);
+ERROR:  could not create unique index "tbl_idx_unique"
+DETAIL:  Key (c1, c2)=(1, 2) is duplicated.
+ALTER TABLE tbl add UNIQUE (c1, c2) INCLUDE (c3, c4);
+ERROR:  could not create unique index "tbl_c1_c2_c3_c4_key"
+DETAIL:  Key (c1, c2)=(1, 2) is duplicated.
+DROP TABLE tbl;
+-- PK constraint
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ALTER TABLE tbl add PRIMARY KEY (c1, c2) INCLUDE (c3, c4);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+                                 pg_get_indexdef                                  
+----------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_pkey ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+DROP TABLE tbl;
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree (c1, c2) INCLUDE (c3, c4);
+ALTER TABLE tbl add PRIMARY KEY USING INDEX tbl_idx_unique;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+                                    pg_get_indexdef                                     
+----------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_idx_unique ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+DROP TABLE tbl;
+-- PK constraint. Must fail.
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ALTER TABLE tbl add PRIMARY KEY (c1, c2) INCLUDE (c3, c4);
+ERROR:  could not create unique index "tbl_pkey"
+DETAIL:  Key (c1, c2)=(1, 2) is duplicated.
+DROP TABLE tbl;
+/*
+ * 2. Test CREATE TABLE with constraint
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               CONSTRAINT covering UNIQUE(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey  | indclass  
+------------+----------+-------------+-------------+--------------+---------+-----------
+ covering   |        4 |           2 | t           | f            | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+       pg_get_constraintdef       | conname  | conkey | conincluding 
+----------------------------------+----------+--------+--------------
+ UNIQUE (c1, c2) INCLUDE (c3, c4) | covering | {1,2}  | {3,4}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  duplicate key value violates unique constraint "covering"
+DETAIL:  Key (c1, c2)=(1, 2) already exists.
+DROP TABLE tbl;
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               CONSTRAINT covering PRIMARY KEY(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey  | indclass  
+------------+----------+-------------+-------------+--------------+---------+-----------
+ covering   |        4 |           2 | t           | t            | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+         pg_get_constraintdef          | conname  | conkey | conincluding 
+---------------------------------------+----------+--------+--------------
+ PRIMARY KEY (c1, c2) INCLUDE (c3, c4) | covering | {1,2}  | {3,4}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  duplicate key value violates unique constraint "covering"
+DETAIL:  Key (c1, c2)=(1, 2) already exists.
+INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  null value in column "c2" violates not-null constraint
+DETAIL:  Failing row contains (1, null, 3, (4,4),(4,4)).
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               UNIQUE(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+     indexrelid      | indnatts | indnkeyatts | indisunique | indisprimary | indkey  | indclass  
+---------------------+----------+-------------+-------------+--------------+---------+-----------
+ tbl_c1_c2_c3_c4_key |        4 |           2 | t           | f            | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+       pg_get_constraintdef       |       conname       | conkey | conincluding 
+----------------------------------+---------------------+--------+--------------
+ UNIQUE (c1, c2) INCLUDE (c3, c4) | tbl_c1_c2_c3_c4_key | {1,2}  | {3,4}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  duplicate key value violates unique constraint "tbl_c1_c2_c3_c4_key"
+DETAIL:  Key (c1, c2)=(1, 2) already exists.
+DROP TABLE tbl;
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               PRIMARY KEY(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+ indexrelid | indnatts | indnkeyatts | indisunique | indisprimary | indkey  | indclass  
+------------+----------+-------------+-------------+--------------+---------+-----------
+ tbl_pkey   |        4 |           2 | t           | t            | 1 2 3 4 | 1978 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+         pg_get_constraintdef          | conname  | conkey | conincluding 
+---------------------------------------+----------+--------+--------------
+ PRIMARY KEY (c1, c2) INCLUDE (c3, c4) | tbl_pkey | {1,2}  | {3,4}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  duplicate key value violates unique constraint "tbl_pkey"
+DETAIL:  Key (c1, c2)=(1, 2) already exists.
+INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  null value in column "c2" violates not-null constraint
+DETAIL:  Failing row contains (1, null, 3, (4,4),(4,4)).
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               EXCLUDE USING btree (c1 WITH =) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+    indexrelid     | indnatts | indnkeyatts | indisunique | indisprimary | indkey | indclass 
+-------------------+----------+-------------+-------------+--------------+--------+----------
+ tbl_c1_c3_c4_excl |        3 |           1 | f           | f            | 1 3 4  | 1978
+(1 row)
+
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+               pg_get_constraintdef               |      conname      | conkey | conincluding 
+--------------------------------------------------+-------------------+--------+--------------
+ EXCLUDE USING btree (c1 WITH =) INCLUDE (c3, c4) | tbl_c1_c3_c4_excl | {1}    | {3,4}
+(1 row)
+
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ERROR:  conflicting key value violates exclusion constraint "tbl_c1_c3_c4_excl"
+DETAIL:  Key (c1)=(1) conflicts with existing key (c1)=(1).
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+/*
+ * 3.0 Test ALTER TABLE DROP COLUMN.
+ * Any column deletion leads to index deletion.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 int);
+CREATE UNIQUE INDEX tbl_idx ON tbl using btree(c1, c2, c3, c4);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+                                indexdef                                
+------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_idx ON public.tbl USING btree (c1, c2, c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+DROP TABLE tbl;
+/*
+ * 3.1 Test ALTER TABLE DROP COLUMN.
+ * Included column deletion leads to the index deletion,
+ * AS well AS key columns deletion. It's explained in documentation.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box);
+CREATE UNIQUE INDEX tbl_idx ON tbl using btree(c1, c2) INCLUDE(c3,c4);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+                                    indexdef                                     
+---------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_idx ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+DROP TABLE tbl;
+/*
+ * 3.2 Test ALTER TABLE DROP COLUMN.
+ * Included column deletion leads to the index deletion.
+ * AS well AS key columns deletion. It's explained in documentation.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+                                          indexdef                                           
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+ALTER TABLE tbl DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+DROP TABLE tbl;
+/*
+ * 4. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,1000) AS x;
+CREATE UNIQUE INDEX CONCURRENTLY on tbl (c1, c2) INCLUDE (c3, c4);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+                                          indexdef                                           
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_idx ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(2 rows)
+
+DROP TABLE tbl;
+/*
+ * 5. REINDEX
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+                                          indexdef                                           
+---------------------------------------------------------------------------------------------
+ CREATE UNIQUE INDEX tbl_c1_c2_c3_c4_key ON public.tbl USING btree (c1, c2) INCLUDE (c3, c4)
+(1 row)
+
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+REINDEX INDEX tbl_c1_c2_c3_c4_key;
+ERROR:  relation "tbl_c1_c2_c3_c4_key" does not exist
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+ALTER TABLE tbl DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ indexdef 
+----------
+(0 rows)
+
+DROP TABLE tbl;
+/*
+ * 7. Check various AMs. All but btree 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 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);
+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;
+/*
+ * 8. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree(c1, c2) INCLUDE (c3,c4);
+UPDATE tbl SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl SET c1 = 1 WHERE c1 = 3;
+-- should fail
+UPDATE tbl SET c2 = 2 WHERE c1 = 1;
+ERROR:  duplicate key value violates unique constraint "tbl_idx_unique"
+DETAIL:  Key (c1, c2)=(1, 2) already exists.
+UPDATE tbl SET c3 = 1;
+DELETE FROM tbl WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl;
+/*
+ * 9. Alter column type.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ALTER TABLE tbl ALTER c1 TYPE bigint;
+ALTER TABLE tbl ALTER c3 TYPE bigint;
+\d tbl
+                Table "public.tbl"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ c1     | bigint  |           |          | 
+ c2     | integer |           |          | 
+ c3     | bigint  |           |          | 
+ c4     | box     |           |          | 
+Indexes:
+    "tbl_c1_c2_c3_c4_key" UNIQUE CONSTRAINT, btree (c1, c2) INCLUDE (c3, c4)
+
+DROP TABLE tbl;
index 00c324dd4449c47771b221afdc8c1ac4531f9d53..0d3a27ed4107b53f4194bd991ef8c94d2fbb2e4c 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
+test: create_index create_view index_including
 
 # ----------
 # Another group of parallel tests
index 39c3fa9c8508a2fc8af70e89b7483f1bbcc2e8bc..20027c131c2af6bb96850e7c8a8cf145bd6f050b 100644 (file)
@@ -65,6 +65,7 @@ test: create_misc
 test: create_operator
 test: create_procedure
 test: create_index
+test: index_including
 test: create_view
 test: create_aggregate
 test: create_function_3
index c9671a4e13e5669f0fab99e56732f41a106f7fd1..f7731265a08c258f6eb3db8d85084dd6603529b1 100644 (file)
@@ -741,6 +741,26 @@ INSERT INTO func_index_heap VALUES('ABCD', 'EF');
 -- but this shouldn't:
 INSERT INTO func_index_heap VALUES('QWERTY');
 
+--
+-- Test unique index with included columns
+--
+CREATE TABLE covering_index_heap (f1 int, f2 int, f3 text);
+CREATE UNIQUE INDEX covering_index_index on covering_index_heap (f1,f2) INCLUDE(f3);
+
+INSERT INTO covering_index_heap VALUES(1,1,'AAA');
+INSERT INTO covering_index_heap VALUES(1,2,'AAA');
+-- this should fail because of unique index on f1,f2:
+INSERT INTO covering_index_heap VALUES(1,2,'BBB');
+-- and this shouldn't:
+INSERT INTO covering_index_heap VALUES(1,4,'AAA');
+-- Try to build index on table that already contains data
+CREATE UNIQUE INDEX covering_pkey on covering_index_heap (f1,f2) INCLUDE(f3);
+-- Try to use existing covering index as primary key
+ALTER TABLE covering_index_heap ADD CONSTRAINT covering_pkey PRIMARY KEY USING INDEX
+covering_pkey;
+DROP TABLE covering_index_heap;
+
+
 --
 -- Also try building functional, expressional, and partial indexes on
 -- tables that already contain data.
diff --git a/src/test/regress/sql/index_including.sql b/src/test/regress/sql/index_including.sql
new file mode 100644 (file)
index 0000000..caedc98
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * 1.test CREATE INDEX
+ */
+
+-- Regular index with included columns
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE INDEX tbl_idx ON tbl using btree (c1, c2) INCLUDE (c3,c4);
+-- must fail because of intersection of key and included columns
+CREATE INDEX tbl_idx ON tbl using btree (c1, c2) INCLUDE (c1,c3);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+DROP TABLE tbl;
+
+-- Unique index and unique constraint
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree (c1, c2) INCLUDE (c3, c4);
+ALTER TABLE tbl add UNIQUE USING INDEX tbl_idx_unique;
+ALTER TABLE tbl add UNIQUE (c1, c2) INCLUDE (c3, c4);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+DROP TABLE tbl;
+
+-- Unique index and unique constraint. Both must fail.
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree (c1, c2) INCLUDE (c3, c4);
+ALTER TABLE tbl add UNIQUE (c1, c2) INCLUDE (c3, c4);
+DROP TABLE tbl;
+
+-- PK constraint
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ALTER TABLE tbl add PRIMARY KEY (c1, c2) INCLUDE (c3, c4);
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+DROP TABLE tbl;
+
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree (c1, c2) INCLUDE (c3, c4);
+ALTER TABLE tbl add PRIMARY KEY USING INDEX tbl_idx_unique;
+SELECT pg_get_indexdef(i.indexrelid)
+FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid
+WHERE i.indrelid = 'tbl'::regclass ORDER BY c.relname;
+DROP TABLE tbl;
+
+-- PK constraint. Must fail.
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ALTER TABLE tbl add PRIMARY KEY (c1, c2) INCLUDE (c3, c4);
+DROP TABLE tbl;
+
+
+/*
+ * 2. Test CREATE TABLE with constraint
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               CONSTRAINT covering UNIQUE(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               CONSTRAINT covering PRIMARY KEY(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               UNIQUE(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               PRIMARY KEY(c1,c2) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
+                               EXCLUDE USING btree (c1 WITH =) INCLUDE(c3,c4));
+SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey, conincluding FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+-- ensure that constraint works
+INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,10) AS x;
+DROP TABLE tbl;
+
+/*
+ * 3.0 Test ALTER TABLE DROP COLUMN.
+ * Any column deletion leads to index deletion.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 int);
+CREATE UNIQUE INDEX tbl_idx ON tbl using btree(c1, c2, c3, c4);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+DROP TABLE tbl;
+
+/*
+ * 3.1 Test ALTER TABLE DROP COLUMN.
+ * Included column deletion leads to the index deletion,
+ * AS well AS key columns deletion. It's explained in documentation.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box);
+CREATE UNIQUE INDEX tbl_idx ON tbl using btree(c1, c2) INCLUDE(c3,c4);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+DROP TABLE tbl;
+
+/*
+ * 3.2 Test ALTER TABLE DROP COLUMN.
+ * Included column deletion leads to the index deletion.
+ * AS well AS key columns deletion. It's explained in documentation.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ALTER TABLE tbl DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+DROP TABLE tbl;
+
+
+/*
+ * 4. CREATE INDEX CONCURRENTLY
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,1000) AS x;
+CREATE UNIQUE INDEX CONCURRENTLY on tbl (c1, c2) INCLUDE (c3, c4);
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+DROP TABLE tbl;
+
+
+/*
+ * 5. REINDEX
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ALTER TABLE tbl DROP COLUMN c3;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+REINDEX INDEX tbl_c1_c2_c3_c4_key;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+ALTER TABLE tbl DROP COLUMN c1;
+SELECT indexdef FROM pg_indexes WHERE tablename = 'tbl' ORDER BY indexname;
+DROP TABLE tbl;
+
+/*
+ * 7. Check various AMs. All but btree 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 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 btree(c1, c2) INCLUDE (c3, c4);
+DROP TABLE tbl;
+
+/*
+ * 8. Update, delete values in indexed table.
+ */
+CREATE TABLE tbl (c1 int, c2 int, c3 int, c4 box);
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+CREATE UNIQUE INDEX tbl_idx_unique ON tbl using btree(c1, c2) INCLUDE (c3,c4);
+UPDATE tbl SET c1 = 100 WHERE c1 = 2;
+UPDATE tbl SET c1 = 1 WHERE c1 = 3;
+-- should fail
+UPDATE tbl SET c2 = 2 WHERE c1 = 1;
+UPDATE tbl SET c3 = 1;
+DELETE FROM tbl WHERE c1 = 5 OR c3 = 12;
+DROP TABLE tbl;
+
+/*
+ * 9. Alter column type.
+ */
+CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box, UNIQUE(c1, c2) INCLUDE(c3,c4));
+INSERT INTO tbl SELECT x, 2*x, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
+ALTER TABLE tbl ALTER c1 TYPE bigint;
+ALTER TABLE tbl ALTER c3 TYPE bigint;
+\d tbl
+DROP TABLE tbl;
+
index e0104cd8d057d2ac642e55914a748c3f6907d3ff..4050e82bc9f04e30097a563f04e0e2621246a62b 100644 (file)
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 17;
 
 # Initialize publisher node
 my $node_publisher = get_new_node('publisher');
@@ -31,6 +31,8 @@ $node_publisher->safe_psql('postgres',
        "CREATE TABLE tab_mixed (a int primary key, b text)");
 $node_publisher->safe_psql('postgres',
        "INSERT INTO tab_mixed (a, b) VALUES (1, 'foo')");
+$node_publisher->safe_psql('postgres',
+       "CREATE TABLE tab_include (a int, b text, CONSTRAINT covering PRIMARY KEY(a) INCLUDE(b))");
 
 # Setup structure on subscriber
 $node_subscriber->safe_psql('postgres', "CREATE TABLE tab_notrep (a int)");
@@ -44,13 +46,17 @@ $node_subscriber->safe_psql('postgres',
 $node_subscriber->safe_psql('postgres',
        "CREATE TABLE tab_mixed (c text, b text, a int primary key)");
 
+# replication of the table with included index
+$node_subscriber->safe_psql('postgres',
+       "CREATE TABLE tab_include (a int, b text, CONSTRAINT covering PRIMARY KEY(a) INCLUDE(b))");
+
 # Setup logical replication
 my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
 $node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub");
 $node_publisher->safe_psql('postgres',
        "CREATE PUBLICATION tap_pub_ins_only WITH (publish = insert)");
 $node_publisher->safe_psql('postgres',
-"ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_full2, tab_mixed"
+"ALTER PUBLICATION tap_pub ADD TABLE tab_rep, tab_full, tab_full2, tab_mixed, tab_include"
 );
 $node_publisher->safe_psql('postgres',
        "ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
@@ -89,6 +95,11 @@ $node_publisher->safe_psql('postgres', "UPDATE tab_rep SET a = -a");
 $node_publisher->safe_psql('postgres',
        "INSERT INTO tab_mixed VALUES (2, 'bar')");
 
+$node_publisher->safe_psql('postgres',
+       "INSERT INTO tab_include SELECT generate_series(1,50)");
+$node_publisher->safe_psql('postgres', "DELETE FROM tab_include WHERE a > 20");
+$node_publisher->safe_psql('postgres', "UPDATE tab_include SET a = -a");
+
 $node_publisher->wait_for_catchup($appname);
 
 $result = $node_subscriber->safe_psql('postgres',
@@ -104,6 +115,10 @@ $result =
 is( $result, qq(|foo|1
 |bar|2), 'check replicated changes with different column order');
 
+$result = $node_subscriber->safe_psql('postgres',
+       "SELECT count(*), min(a), max(a) FROM tab_include");
+is($result, qq(20|-20|-1), 'check replicated changes with primary key index with included columns');
+
 # insert some duplicate rows
 $node_publisher->safe_psql('postgres',
        "INSERT INTO tab_full SELECT generate_series(1,10)");