Indexes with INCLUDE columns and their support in B-tree
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
This commit is contained in:
parent
01bb85169a
commit
8224de4f42
@ -1,10 +1,14 @@
|
|||||||
-- minimal test, basically just verifying that amcheck
|
-- minimal test, basically just verifying that amcheck
|
||||||
CREATE TABLE bttest_a(id int8);
|
CREATE TABLE bttest_a(id int8);
|
||||||
CREATE TABLE bttest_b(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_a SELECT * FROM generate_series(1, 100000);
|
||||||
INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
|
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_a_idx ON bttest_a USING btree (id);
|
||||||
CREATE INDEX bttest_b_idx ON bttest_b 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;
|
CREATE ROLE bttest_role;
|
||||||
-- verify permissions are checked (error due to function not callable)
|
-- verify permissions are checked (error due to function not callable)
|
||||||
SET ROLE bttest_role;
|
SET ROLE bttest_role;
|
||||||
@ -93,8 +97,50 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
|
|||||||
(0 rows)
|
(0 rows)
|
||||||
|
|
||||||
COMMIT;
|
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
|
-- cleanup
|
||||||
DROP TABLE bttest_a;
|
DROP TABLE bttest_a;
|
||||||
DROP TABLE bttest_b;
|
DROP TABLE bttest_b;
|
||||||
|
DROP TABLE bttest_multi;
|
||||||
DROP OWNED BY bttest_role; -- permissions
|
DROP OWNED BY bttest_role; -- permissions
|
||||||
DROP ROLE bttest_role;
|
DROP ROLE bttest_role;
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
-- minimal test, basically just verifying that amcheck
|
-- minimal test, basically just verifying that amcheck
|
||||||
CREATE TABLE bttest_a(id int8);
|
CREATE TABLE bttest_a(id int8);
|
||||||
CREATE TABLE bttest_b(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_a SELECT * FROM generate_series(1, 100000);
|
||||||
INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
|
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_a_idx ON bttest_a USING btree (id);
|
||||||
CREATE INDEX bttest_b_idx ON bttest_b 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;
|
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();
|
AND pid = pg_backend_pid();
|
||||||
COMMIT;
|
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
|
-- cleanup
|
||||||
DROP TABLE bttest_a;
|
DROP TABLE bttest_a;
|
||||||
DROP TABLE bttest_b;
|
DROP TABLE bttest_b;
|
||||||
|
DROP TABLE bttest_multi;
|
||||||
DROP OWNED BY bttest_role; -- permissions
|
DROP OWNED BY bttest_role; -- permissions
|
||||||
DROP ROLE bttest_role;
|
DROP ROLE bttest_role;
|
||||||
|
@ -617,7 +617,7 @@ bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level)
|
|||||||
/* Internal page -- downlink gets leftmost on next level */
|
/* Internal page -- downlink gets leftmost on next level */
|
||||||
itemid = PageGetItemId(state->target, P_FIRSTDATAKEY(opaque));
|
itemid = PageGetItemId(state->target, P_FIRSTDATAKEY(opaque));
|
||||||
itup = (IndexTuple) PageGetItem(state->target, itemid);
|
itup = (IndexTuple) PageGetItem(state->target, itemid);
|
||||||
nextleveldown.leftmost = ItemPointerGetBlockNumber(&(itup->t_tid));
|
nextleveldown.leftmost = ItemPointerGetBlockNumberNoCheck(&(itup->t_tid));
|
||||||
nextleveldown.level = opaque->btpo.level - 1;
|
nextleveldown.level = opaque->btpo.level - 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -722,6 +722,39 @@ bt_target_page_check(BtreeCheckState *state)
|
|||||||
elog(DEBUG2, "verifying %u items on %s block %u", max,
|
elog(DEBUG2, "verifying %u items on %s block %u", max,
|
||||||
P_ISLEAF(topaque) ? "leaf" : "internal", state->targetblock);
|
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
|
* Loop over page items, starting from first non-highkey item, not high
|
||||||
* key (if any). Also, immediately skip "negative infinity" real item (if
|
* key (if any). Also, immediately skip "negative infinity" real item (if
|
||||||
@ -760,6 +793,30 @@ bt_target_page_check(BtreeCheckState *state)
|
|||||||
(uint32) state->targetlsn),
|
(uint32) state->targetlsn),
|
||||||
errhint("This could be a torn page problem")));
|
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
|
* Don't try to generate scankey using "negative infinity" garbage
|
||||||
* data on internal pages
|
* data on internal pages
|
||||||
@ -802,8 +859,8 @@ bt_target_page_check(BtreeCheckState *state)
|
|||||||
|
|
||||||
itid = psprintf("(%u,%u)", state->targetblock, offset);
|
itid = psprintf("(%u,%u)", state->targetblock, offset);
|
||||||
htid = psprintf("(%u,%u)",
|
htid = psprintf("(%u,%u)",
|
||||||
ItemPointerGetBlockNumber(&(itup->t_tid)),
|
ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
|
||||||
ItemPointerGetOffsetNumber(&(itup->t_tid)));
|
ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
|
||||||
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INDEX_CORRUPTED),
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
||||||
@ -834,8 +891,8 @@ bt_target_page_check(BtreeCheckState *state)
|
|||||||
|
|
||||||
itid = psprintf("(%u,%u)", state->targetblock, offset);
|
itid = psprintf("(%u,%u)", state->targetblock, offset);
|
||||||
htid = psprintf("(%u,%u)",
|
htid = psprintf("(%u,%u)",
|
||||||
ItemPointerGetBlockNumber(&(itup->t_tid)),
|
ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
|
||||||
ItemPointerGetOffsetNumber(&(itup->t_tid)));
|
ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
|
||||||
nitid = psprintf("(%u,%u)", state->targetblock,
|
nitid = psprintf("(%u,%u)", state->targetblock,
|
||||||
OffsetNumberNext(offset));
|
OffsetNumberNext(offset));
|
||||||
|
|
||||||
@ -843,8 +900,8 @@ bt_target_page_check(BtreeCheckState *state)
|
|||||||
itemid = PageGetItemId(state->target, OffsetNumberNext(offset));
|
itemid = PageGetItemId(state->target, OffsetNumberNext(offset));
|
||||||
itup = (IndexTuple) PageGetItem(state->target, itemid);
|
itup = (IndexTuple) PageGetItem(state->target, itemid);
|
||||||
nhtid = psprintf("(%u,%u)",
|
nhtid = psprintf("(%u,%u)",
|
||||||
ItemPointerGetBlockNumber(&(itup->t_tid)),
|
ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
|
||||||
ItemPointerGetOffsetNumber(&(itup->t_tid)));
|
ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)));
|
||||||
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INDEX_CORRUPTED),
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
||||||
@ -932,7 +989,7 @@ bt_target_page_check(BtreeCheckState *state)
|
|||||||
*/
|
*/
|
||||||
if (!P_ISLEAF(topaque) && state->readonly)
|
if (!P_ISLEAF(topaque) && state->readonly)
|
||||||
{
|
{
|
||||||
BlockNumber childblock = ItemPointerGetBlockNumber(&(itup->t_tid));
|
BlockNumber childblock = ItemPointerGetBlockNumberNoCheck(&(itup->t_tid));
|
||||||
|
|
||||||
bt_downlink_check(state, childblock, skey);
|
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
|
* or otherwise varied when or how compression was applied, our assumption
|
||||||
* would break, leading to false positive reports of corruption. For now,
|
* would break, leading to false positive reports of corruption. For now,
|
||||||
* we don't decompress/normalize toasted values as part of fingerprinting.
|
* 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 = index_form_tuple(RelationGetDescr(index), values, isnull);
|
||||||
itup->t_tid = htup->t_self;
|
itup->t_tid = htup->t_self;
|
||||||
@ -1336,8 +1398,8 @@ bt_tuple_present_callback(Relation index, HeapTuple htup, Datum *values,
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg("heap tuple (%u,%u) from table \"%s\" lacks matching index tuple within index \"%s\"",
|
errmsg("heap tuple (%u,%u) from table \"%s\" lacks matching index tuple within index \"%s\"",
|
||||||
ItemPointerGetBlockNumber(&(itup->t_tid)),
|
ItemPointerGetBlockNumberNoCheck(&(itup->t_tid)),
|
||||||
ItemPointerGetOffsetNumber(&(itup->t_tid)),
|
ItemPointerGetOffsetNumberNoCheck(&(itup->t_tid)),
|
||||||
RelationGetRelationName(state->heaprel),
|
RelationGetRelationName(state->heaprel),
|
||||||
RelationGetRelationName(state->rel)),
|
RelationGetRelationName(state->rel)),
|
||||||
!state->readonly
|
!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
|
* infinity item is either first or second line item, or there is none
|
||||||
* within page.
|
* 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
|
* 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
|
* conceptually have a "positive infinity" high key. Thus, there is a
|
||||||
* symmetry between down link items in parent pages, and high keys in
|
* 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,
|
invariant_leq_offset(BtreeCheckState *state, ScanKey key,
|
||||||
OffsetNumber upperbound)
|
OffsetNumber upperbound)
|
||||||
{
|
{
|
||||||
int16 natts = state->rel->rd_rel->relnatts;
|
int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
|
||||||
int32 cmp;
|
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;
|
return cmp <= 0;
|
||||||
}
|
}
|
||||||
@ -1410,10 +1476,10 @@ static inline bool
|
|||||||
invariant_geq_offset(BtreeCheckState *state, ScanKey key,
|
invariant_geq_offset(BtreeCheckState *state, ScanKey key,
|
||||||
OffsetNumber lowerbound)
|
OffsetNumber lowerbound)
|
||||||
{
|
{
|
||||||
int16 natts = state->rel->rd_rel->relnatts;
|
int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
|
||||||
int32 cmp;
|
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;
|
return cmp >= 0;
|
||||||
}
|
}
|
||||||
@ -1433,10 +1499,10 @@ invariant_leq_nontarget_offset(BtreeCheckState *state,
|
|||||||
Page nontarget, ScanKey key,
|
Page nontarget, ScanKey key,
|
||||||
OffsetNumber upperbound)
|
OffsetNumber upperbound)
|
||||||
{
|
{
|
||||||
int16 natts = state->rel->rd_rel->relnatts;
|
int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(state->rel);
|
||||||
int32 cmp;
|
int32 cmp;
|
||||||
|
|
||||||
cmp = _bt_compare(state->rel, natts, key, nontarget, upperbound);
|
cmp = _bt_compare(state->rel, nkeyatts, key, nontarget, upperbound);
|
||||||
|
|
||||||
return cmp <= 0;
|
return cmp <= 0;
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ blhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = false;
|
amroutine->ampredlocks = false;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
amroutine->amcaninclude = false;
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
amroutine->ambuild = blbuild;
|
amroutine->ambuild = blbuild;
|
||||||
|
@ -100,7 +100,7 @@ static remoteConn *getConnectionByName(const char *name);
|
|||||||
static HTAB *createConnHash(void);
|
static HTAB *createConnHash(void);
|
||||||
static void createNewConnection(const char *name, remoteConn *rconn);
|
static void createNewConnection(const char *name, remoteConn *rconn);
|
||||||
static void deleteConnection(const char *name);
|
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_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_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);
|
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
|
Datum
|
||||||
dblink_get_pkey(PG_FUNCTION_ARGS)
|
dblink_get_pkey(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
int16 numatts;
|
int16 indnkeyatts;
|
||||||
char **results;
|
char **results;
|
||||||
FuncCallContext *funcctx;
|
FuncCallContext *funcctx;
|
||||||
int32 call_cntr;
|
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);
|
rel = get_rel_from_relname(PG_GETARG_TEXT_PP(0), AccessShareLock, ACL_SELECT);
|
||||||
|
|
||||||
/* get the array of attnums */
|
/* get the array of attnums */
|
||||||
results = get_pkey_attnames(rel, &numatts);
|
results = get_pkey_attnames(rel, &indnkeyatts);
|
||||||
|
|
||||||
relation_close(rel, AccessShareLock);
|
relation_close(rel, AccessShareLock);
|
||||||
|
|
||||||
@ -1539,9 +1539,9 @@ dblink_get_pkey(PG_FUNCTION_ARGS)
|
|||||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||||
funcctx->attinmeta = attinmeta;
|
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 */
|
/* got results, keep track of them */
|
||||||
funcctx->user_fctx = results;
|
funcctx->user_fctx = results;
|
||||||
@ -2029,10 +2029,10 @@ dblink_fdw_validator(PG_FUNCTION_ARGS)
|
|||||||
* get_pkey_attnames
|
* get_pkey_attnames
|
||||||
*
|
*
|
||||||
* Get the primary key attnames for the given relation.
|
* 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 **
|
static char **
|
||||||
get_pkey_attnames(Relation rel, int16 *numatts)
|
get_pkey_attnames(Relation rel, int16 *indnkeyatts)
|
||||||
{
|
{
|
||||||
Relation indexRelation;
|
Relation indexRelation;
|
||||||
ScanKeyData skey;
|
ScanKeyData skey;
|
||||||
@ -2042,8 +2042,8 @@ get_pkey_attnames(Relation rel, int16 *numatts)
|
|||||||
char **result = NULL;
|
char **result = NULL;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
|
|
||||||
/* initialize numatts to 0 in case no primary key exists */
|
/* initialize indnkeyatts to 0 in case no primary key exists */
|
||||||
*numatts = 0;
|
*indnkeyatts = 0;
|
||||||
|
|
||||||
tupdesc = rel->rd_att;
|
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 */
|
/* we're only interested if it is the primary key */
|
||||||
if (index->indisprimary)
|
if (index->indisprimary)
|
||||||
{
|
{
|
||||||
*numatts = index->indnatts;
|
*indnkeyatts = index->indnkeyatts;
|
||||||
if (*numatts > 0)
|
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]);
|
result[i] = SPI_fname(tupdesc, index->indkey.values[i]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -54,6 +54,61 @@ SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}');
|
|||||||
-- too many pk fields, should fail
|
-- too many pk fields, should fail
|
||||||
SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
|
SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
|
||||||
ERROR: invalid attribute number 4
|
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
|
-- retest using a quoted and schema qualified table
|
||||||
CREATE SCHEMA "MySchema";
|
CREATE SCHEMA "MySchema";
|
||||||
CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
|
CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
|
||||||
|
@ -38,6 +38,44 @@ SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}');
|
|||||||
-- too many pk fields, should fail
|
-- too many pk fields, should fail
|
||||||
SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
|
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
|
-- retest using a quoted and schema qualified table
|
||||||
CREATE SCHEMA "MySchema";
|
CREATE SCHEMA "MySchema";
|
||||||
CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
|
CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
|
||||||
|
@ -138,9 +138,9 @@ triggered_change_notification(PG_FUNCTION_ARGS)
|
|||||||
/* we're only interested if it is the primary key and valid */
|
/* we're only interested if it is the primary key and valid */
|
||||||
if (index->indisprimary && IndexIsValid(index))
|
if (index->indisprimary && IndexIsValid(index))
|
||||||
{
|
{
|
||||||
int numatts = index->indnatts;
|
int indnkeyatts = index->indnkeyatts;
|
||||||
|
|
||||||
if (numatts > 0)
|
if (indnkeyatts > 0)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
|
|||||||
appendStringInfoCharMacro(payload, ',');
|
appendStringInfoCharMacro(payload, ',');
|
||||||
appendStringInfoCharMacro(payload, operation);
|
appendStringInfoCharMacro(payload, operation);
|
||||||
|
|
||||||
for (i = 0; i < numatts; i++)
|
for (i = 0; i < indnkeyatts; i++)
|
||||||
{
|
{
|
||||||
int colno = index->indkey.values[i];
|
int colno = index->indkey.values[i];
|
||||||
Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
|
Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
|
||||||
|
@ -433,6 +433,23 @@ returns bool
|
|||||||
|
|
||||||
</sect1>
|
</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">
|
<sect1 id="btree-implementation">
|
||||||
<title>Implementation</title>
|
<title>Implementation</title>
|
||||||
|
|
||||||
|
@ -3743,8 +3743,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
|
|||||||
<entry><structfield>indnatts</structfield></entry>
|
<entry><structfield>indnatts</structfield></entry>
|
||||||
<entry><type>int2</type></entry>
|
<entry><type>int2</type></entry>
|
||||||
<entry></entry>
|
<entry></entry>
|
||||||
<entry>The number of columns in the index (duplicates
|
<entry>The total number of columns in the index (duplicates
|
||||||
<literal>pg_class.relnatts</literal>)</entry>
|
<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>
|
||||||
|
|
||||||
<row>
|
<row>
|
||||||
|
@ -114,6 +114,8 @@ typedef struct IndexAmRoutine
|
|||||||
bool amcanparallel;
|
bool amcanparallel;
|
||||||
/* type of data stored in index, or InvalidOid if variable */
|
/* type of data stored in index, or InvalidOid if variable */
|
||||||
Oid amkeytype;
|
Oid amkeytype;
|
||||||
|
/* does AM support columns included with clause INCLUDE? */
|
||||||
|
bool amcaninclude;
|
||||||
|
|
||||||
/* interface functions */
|
/* interface functions */
|
||||||
ambuild_function ambuild;
|
ambuild_function ambuild;
|
||||||
@ -985,7 +987,8 @@ amparallelrescan (IndexScanDesc scan);
|
|||||||
using <firstterm>unique indexes</firstterm>, which are indexes that disallow
|
using <firstterm>unique indexes</firstterm>, which are indexes that disallow
|
||||||
multiple entries with identical keys. An access method that supports this
|
multiple entries with identical keys. An access method that supports this
|
||||||
feature sets <structfield>amcanunique</structfield> true.
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -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,
|
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.
|
or the uniqueness of the combined values of more than one column.
|
||||||
<synopsis>
|
<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>
|
</synopsis>
|
||||||
Currently, only B-tree indexes can be declared unique.
|
Currently, only B-tree indexes can be declared unique.
|
||||||
</para>
|
</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
|
When an index is declared unique, multiple table rows with equal
|
||||||
indexed values are not allowed. Null values are not considered
|
indexed values are not allowed. Null values are not considered
|
||||||
equal. A multicolumn unique index will only reject cases where all
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -23,6 +23,7 @@ PostgreSQL documentation
|
|||||||
<synopsis>
|
<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> ]
|
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 } ] [, ...] )
|
( { <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> [, ... ] ) ]
|
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] ) ]
|
||||||
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
|
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
|
||||||
[ WHERE <replaceable class="parameter">predicate</replaceable> ]
|
[ WHERE <replaceable class="parameter">predicate</replaceable> ]
|
||||||
@ -143,6 +144,56 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><replaceable class="parameter">name</replaceable></term>
|
<term><replaceable class="parameter">name</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -729,13 +780,22 @@ Indexes:
|
|||||||
<title>Examples</title>
|
<title>Examples</title>
|
||||||
|
|
||||||
<para>
|
<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>:
|
the table <literal>films</literal>:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE UNIQUE INDEX title_idx ON films (title);
|
CREATE UNIQUE INDEX title_idx ON films (title);
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</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>
|
<para>
|
||||||
To create an index on the expression <literal>lower(title)</literal>,
|
To create an index on the expression <literal>lower(title)</literal>,
|
||||||
allowing efficient case-insensitive searches:
|
allowing efficient case-insensitive searches:
|
||||||
|
@ -73,8 +73,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
|
|||||||
|
|
||||||
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
|
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
|
||||||
{ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
|
{ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
|
||||||
UNIQUE ( <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> |
|
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> ) ] |
|
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> [, ... ] ) ]
|
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> ] }
|
[ 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>
|
<varlistentry>
|
||||||
<term><literal>UNIQUE</literal> (column constraint)</term>
|
<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>
|
<listitem>
|
||||||
<para>
|
<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
|
partitioned table, as well as those of all its descendant partitioned
|
||||||
tables, must be included in the constraint definition.
|
tables, must be included in the constraint definition.
|
||||||
</para>
|
</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>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><literal>PRIMARY KEY</literal> (column constraint)</term>
|
<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>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The <literal>PRIMARY KEY</literal> constraint specifies that a column or
|
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.
|
tables.
|
||||||
</para>
|
</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>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ brinhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = false;
|
amroutine->ampredlocks = false;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
amroutine->amcaninclude = false;
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
amroutine->ambuild = brinbuild;
|
amroutine->ambuild = brinbuild;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
#include "access/itup.h"
|
#include "access/itup.h"
|
||||||
#include "access/tuptoaster.h"
|
#include "access/tuptoaster.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -445,3 +446,33 @@ CopyIndexTuple(IndexTuple source)
|
|||||||
memcpy(result, source, size);
|
memcpy(result, source, size);
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
@ -52,6 +52,7 @@ ginhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = true;
|
amroutine->ampredlocks = true;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
amroutine->amcaninclude = false;
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
amroutine->ambuild = ginbuild;
|
amroutine->ambuild = ginbuild;
|
||||||
|
@ -74,6 +74,7 @@ gisthandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = true;
|
amroutine->amclusterable = true;
|
||||||
amroutine->ampredlocks = true;
|
amroutine->ampredlocks = true;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
amroutine->amcaninclude = false;
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
amroutine->ambuild = gistbuild;
|
amroutine->ambuild = gistbuild;
|
||||||
|
@ -70,6 +70,7 @@ hashhandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = true;
|
amroutine->ampredlocks = true;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
amroutine->amcaninclude = false;
|
||||||
amroutine->amkeytype = INT4OID;
|
amroutine->amkeytype = INT4OID;
|
||||||
|
|
||||||
amroutine->ambuild = hashbuild;
|
amroutine->ambuild = hashbuild;
|
||||||
|
@ -8023,7 +8023,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
|
|||||||
TupleDesc desc = RelationGetDescr(relation);
|
TupleDesc desc = RelationGetDescr(relation);
|
||||||
Oid replidindex;
|
Oid replidindex;
|
||||||
Relation idx_rel;
|
Relation idx_rel;
|
||||||
TupleDesc idx_desc;
|
|
||||||
char replident = relation->rd_rel->relreplident;
|
char replident = relation->rd_rel->relreplident;
|
||||||
HeapTuple key_tuple = NULL;
|
HeapTuple key_tuple = NULL;
|
||||||
bool nulls[MaxHeapAttributeNumber];
|
bool nulls[MaxHeapAttributeNumber];
|
||||||
@ -8066,7 +8065,6 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool *
|
|||||||
}
|
}
|
||||||
|
|
||||||
idx_rel = RelationIdGetRelation(replidindex);
|
idx_rel = RelationIdGetRelation(replidindex);
|
||||||
idx_desc = RelationGetDescr(idx_rel);
|
|
||||||
|
|
||||||
/* deform tuple, so we have fast access to columns */
|
/* deform tuple, so we have fast access to columns */
|
||||||
heap_deform_tuple(tp, desc, values, nulls);
|
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
|
* Now set all columns contained in the index to NOT NULL, they cannot
|
||||||
* currently be NULL.
|
* 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];
|
int attno = idx_rel->rd_index->indkey.values[natt];
|
||||||
|
|
||||||
|
@ -158,7 +158,8 @@ IndexScanEnd(IndexScanDesc scan)
|
|||||||
*
|
*
|
||||||
* Construct a string describing the contents of an index entry, in the
|
* Construct a string describing the contents of an index entry, in the
|
||||||
* form "(key_name, ...)=(key_value, ...)". This is currently used
|
* 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
|
* 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
|
* columns involved then a NULL is returned. Returning a partial key seems
|
||||||
@ -180,13 +181,15 @@ BuildIndexValueDescription(Relation indexRelation,
|
|||||||
StringInfoData buf;
|
StringInfoData buf;
|
||||||
Form_pg_index idxrec;
|
Form_pg_index idxrec;
|
||||||
HeapTuple ht_idx;
|
HeapTuple ht_idx;
|
||||||
int natts = indexRelation->rd_rel->relnatts;
|
int indnkeyatts;
|
||||||
int i;
|
int i;
|
||||||
int keyno;
|
int keyno;
|
||||||
Oid indexrelid = RelationGetRelid(indexRelation);
|
Oid indexrelid = RelationGetRelid(indexRelation);
|
||||||
Oid indrelid;
|
Oid indrelid;
|
||||||
AclResult aclresult;
|
AclResult aclresult;
|
||||||
|
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check permissions- if the user does not have access to view all of the
|
* Check permissions- if the user does not have access to view all of the
|
||||||
* key columns then return NULL to avoid leaking data.
|
* 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
|
* No table-level access, so step through the columns in the index and
|
||||||
* make sure the user has SELECT rights on all of them.
|
* 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];
|
AttrNumber attnum = idxrec->indkey.values[keyno];
|
||||||
|
|
||||||
@ -250,7 +253,7 @@ BuildIndexValueDescription(Relation indexRelation,
|
|||||||
appendStringInfo(&buf, "(%s)=(",
|
appendStringInfo(&buf, "(%s)=(",
|
||||||
pg_get_indexdef_columns(indexrelid, true));
|
pg_get_indexdef_columns(indexrelid, true));
|
||||||
|
|
||||||
for (i = 0; i < natts; i++)
|
for (i = 0; i < indnkeyatts; i++)
|
||||||
{
|
{
|
||||||
char *val;
|
char *val;
|
||||||
|
|
||||||
@ -368,7 +371,7 @@ systable_beginscan(Relation heapRelation,
|
|||||||
{
|
{
|
||||||
int j;
|
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])
|
if (key[i].sk_attno == irel->rd_index->indkey.values[j])
|
||||||
{
|
{
|
||||||
@ -376,7 +379,7 @@ systable_beginscan(Relation heapRelation,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (j == irel->rd_index->indnatts)
|
if (j == IndexRelationGetNumberOfAttributes(irel))
|
||||||
elog(ERROR, "column is not in index");
|
elog(ERROR, "column is not in index");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,7 +573,7 @@ systable_beginscan_ordered(Relation heapRelation,
|
|||||||
{
|
{
|
||||||
int j;
|
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])
|
if (key[i].sk_attno == indexRelation->rd_index->indkey.values[j])
|
||||||
{
|
{
|
||||||
@ -578,7 +581,7 @@ systable_beginscan_ordered(Relation heapRelation,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (j == indexRelation->rd_index->indnatts)
|
if (j == IndexRelationGetNumberOfAttributes(indexRelation))
|
||||||
elog(ERROR, "column is not in index");
|
elog(ERROR, "column is not in index");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
scanned to decide whether to return the entry and whether the scan can
|
||||||
stop (see _bt_checkkeys()).
|
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
|
Notes About Data Representation
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ static void _bt_checksplitloc(FindSplitData *state,
|
|||||||
int dataitemstoleft, Size firstoldonrightsz);
|
int dataitemstoleft, Size firstoldonrightsz);
|
||||||
static bool _bt_pgaddtup(Page page, Size itemsize, IndexTuple itup,
|
static bool _bt_pgaddtup(Page page, Size itemsize, IndexTuple itup,
|
||||||
OffsetNumber itup_off);
|
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);
|
int keysz, ScanKey scankey);
|
||||||
static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel);
|
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)
|
IndexUniqueCheck checkUnique, Relation heapRel)
|
||||||
{
|
{
|
||||||
bool is_unique = false;
|
bool is_unique = false;
|
||||||
int natts = rel->rd_rel->relnatts;
|
int indnkeyatts;
|
||||||
ScanKey itup_scankey;
|
ScanKey itup_scankey;
|
||||||
BTStack stack = NULL;
|
BTStack stack = NULL;
|
||||||
Buffer buf;
|
Buffer buf;
|
||||||
OffsetNumber offset;
|
OffsetNumber offset;
|
||||||
bool fastpath;
|
bool fastpath;
|
||||||
|
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
|
||||||
|
Assert(indnkeyatts != 0);
|
||||||
|
|
||||||
/* we need an insertion scan key to do our search, so build one */
|
/* we need an insertion scan key to do our search, so build one */
|
||||||
itup_scankey = _bt_mkscankey(rel, itup);
|
itup_scankey = _bt_mkscankey(rel, itup);
|
||||||
|
|
||||||
@ -173,12 +176,12 @@ top:
|
|||||||
* page.
|
* page.
|
||||||
*/
|
*/
|
||||||
if (P_ISLEAF(lpageop) && P_RIGHTMOST(lpageop) &&
|
if (P_ISLEAF(lpageop) && P_RIGHTMOST(lpageop) &&
|
||||||
!P_INCOMPLETE_SPLIT(lpageop) &&
|
!P_INCOMPLETE_SPLIT(lpageop) &&
|
||||||
!P_IGNORE(lpageop) &&
|
!P_IGNORE(lpageop) &&
|
||||||
(PageGetFreeSpace(page) > itemsz) &&
|
(PageGetFreeSpace(page) > itemsz) &&
|
||||||
PageGetMaxOffsetNumber(page) >= P_FIRSTDATAKEY(lpageop) &&
|
PageGetMaxOffsetNumber(page) >= P_FIRSTDATAKEY(lpageop) &&
|
||||||
_bt_compare(rel, natts, itup_scankey, page,
|
_bt_compare(rel, indnkeyatts, itup_scankey, page,
|
||||||
P_FIRSTDATAKEY(lpageop)) > 0)
|
P_FIRSTDATAKEY(lpageop)) > 0)
|
||||||
{
|
{
|
||||||
fastpath = true;
|
fastpath = true;
|
||||||
}
|
}
|
||||||
@ -209,7 +212,7 @@ top:
|
|||||||
if (!fastpath)
|
if (!fastpath)
|
||||||
{
|
{
|
||||||
/* find the first page containing this key */
|
/* 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);
|
NULL);
|
||||||
|
|
||||||
/* trade in our read lock for a write lock */
|
/* 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
|
* need to move right in the tree. See Lehman and Yao for an
|
||||||
* excruciatingly precise description.
|
* 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);
|
true, stack, BT_WRITE, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +256,7 @@ top:
|
|||||||
TransactionId xwait;
|
TransactionId xwait;
|
||||||
uint32 speculativeToken;
|
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,
|
xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
|
||||||
checkUnique, &is_unique, &speculativeToken);
|
checkUnique, &is_unique, &speculativeToken);
|
||||||
|
|
||||||
@ -287,10 +290,12 @@ top:
|
|||||||
* actual location of the insert is hard to predict because of the
|
* actual location of the insert is hard to predict because of the
|
||||||
* random search used to prevent O(N^2) performance when there are
|
* random search used to prevent O(N^2) performance when there are
|
||||||
* many duplicate entries, we can just use the "first valid" page.
|
* 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);
|
CheckForSerializableConflictIn(rel, NULL, buf);
|
||||||
/* do the insertion */
|
/* do the insertion */
|
||||||
_bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup,
|
_bt_findinsertloc(rel, &buf, &offset, indnkeyatts, itup_scankey, itup,
|
||||||
stack, heapRel);
|
stack, heapRel);
|
||||||
_bt_insertonpg(rel, buf, InvalidBuffer, stack, itup, offset, false);
|
_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,
|
IndexUniqueCheck checkUnique, bool *is_unique,
|
||||||
uint32 *speculativeToken)
|
uint32 *speculativeToken)
|
||||||
{
|
{
|
||||||
TupleDesc itupdesc = RelationGetDescr(rel);
|
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
|
||||||
int natts = rel->rd_rel->relnatts;
|
|
||||||
SnapshotData SnapshotDirty;
|
SnapshotData SnapshotDirty;
|
||||||
OffsetNumber maxoff;
|
OffsetNumber maxoff;
|
||||||
Page page;
|
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
|
* in real comparison, but only for ordering/finding items on
|
||||||
* pages. - vadim 03/24/97
|
* 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 */
|
break; /* we're past all the equal tuples */
|
||||||
|
|
||||||
/* okay, we gotta fetch the heap tuple ... */
|
/* 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 scankey == hikey we gotta check the next page too */
|
||||||
if (P_RIGHTMOST(opaque))
|
if (P_RIGHTMOST(opaque))
|
||||||
break;
|
break;
|
||||||
if (!_bt_isequal(itupdesc, page, P_HIKEY,
|
if (!_bt_isequal(rel, page, P_HIKEY,
|
||||||
natts, itup_scankey))
|
indnkeyatts, itup_scankey))
|
||||||
break;
|
break;
|
||||||
/* Advance to next non-dead page --- there must be one */
|
/* Advance to next non-dead page --- there must be one */
|
||||||
for (;;)
|
for (;;)
|
||||||
@ -1087,6 +1091,9 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
|
|||||||
OffsetNumber maxoff;
|
OffsetNumber maxoff;
|
||||||
OffsetNumber i;
|
OffsetNumber i;
|
||||||
bool isleaf;
|
bool isleaf;
|
||||||
|
IndexTuple lefthikey;
|
||||||
|
int indnatts = IndexRelationGetNumberOfAttributes(rel);
|
||||||
|
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
|
||||||
|
|
||||||
/* Acquire a new page to split into */
|
/* Acquire a new page to split into */
|
||||||
rbuf = _bt_getbuf(rel, P_NEW, BT_WRITE);
|
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);
|
itemsz = ItemIdGetLength(itemid);
|
||||||
item = (IndexTuple) PageGetItem(origpage, 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)
|
false, false) == InvalidOffsetNumber)
|
||||||
{
|
{
|
||||||
memset(rightpage, 0, BufferGetPageSize(rbuf));
|
memset(rightpage, 0, BufferGetPageSize(rbuf));
|
||||||
@ -1375,6 +1398,7 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
|
|||||||
xl_btree_split xlrec;
|
xl_btree_split xlrec;
|
||||||
uint8 xlinfo;
|
uint8 xlinfo;
|
||||||
XLogRecPtr recptr;
|
XLogRecPtr recptr;
|
||||||
|
bool loglhikey = false;
|
||||||
|
|
||||||
xlrec.level = ropaque->btpo.level;
|
xlrec.level = ropaque->btpo.level;
|
||||||
xlrec.firstright = firstright;
|
xlrec.firstright = firstright;
|
||||||
@ -1404,18 +1428,20 @@ _bt_split(Relation rel, Buffer buf, Buffer cbuf, OffsetNumber firstright,
|
|||||||
XLogRegisterBufData(0, (char *) newitem, MAXALIGN(newitemsz));
|
XLogRegisterBufData(0, (char *) newitem, MAXALIGN(newitemsz));
|
||||||
|
|
||||||
/* Log left page */
|
/* Log left page */
|
||||||
if (!isleaf)
|
if (!isleaf || indnatts != indnkeyatts)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* We must also log the left page's high key, because the right
|
* We must also log the left page's high key. There are two
|
||||||
* page's leftmost key is suppressed on non-leaf levels. Show it
|
* reasons for that: right page's leftmost key is suppressed on
|
||||||
* as belonging to the left page buffer, so that it is not stored
|
* non-leaf levels and in covering indexes included columns are
|
||||||
* if XLogInsert decides it needs a full-page image of the left
|
* truncated from high keys. Show it as belonging to the left
|
||||||
* page.
|
* 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);
|
itemid = PageGetItemId(origpage, P_HIKEY);
|
||||||
item = (IndexTuple) PageGetItem(origpage, itemid);
|
item = (IndexTuple) PageGetItem(origpage, itemid);
|
||||||
XLogRegisterBufData(0, (char *) item, MAXALIGN(IndexTupleSize(item)));
|
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,
|
(char *) rightpage + ((PageHeader) rightpage)->pd_upper,
|
||||||
((PageHeader) rightpage)->pd_special - ((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);
|
recptr = XLogInsert(RM_BTREE_ID, xlinfo);
|
||||||
|
|
||||||
PageSetLSN(origpage, recptr);
|
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;
|
* 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;
|
leftfree -= firstrightitemsz;
|
||||||
|
|
||||||
@ -1787,18 +1820,18 @@ _bt_insert_parent(Relation rel,
|
|||||||
stack = &fakestack;
|
stack = &fakestack;
|
||||||
stack->bts_blkno = BufferGetBlockNumber(pbuf);
|
stack->bts_blkno = BufferGetBlockNumber(pbuf);
|
||||||
stack->bts_offset = InvalidOffsetNumber;
|
stack->bts_offset = InvalidOffsetNumber;
|
||||||
/* bts_btentry will be initialized below */
|
stack->bts_btentry = InvalidBlockNumber;
|
||||||
stack->bts_parent = NULL;
|
stack->bts_parent = NULL;
|
||||||
_bt_relbuf(rel, pbuf);
|
_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,
|
ritem = (IndexTuple) PageGetItem(page,
|
||||||
PageGetItemId(page, P_HIKEY));
|
PageGetItemId(page, P_HIKEY));
|
||||||
|
|
||||||
/* form an index tuple that points at the new right page */
|
/* form an index tuple that points at the new right page */
|
||||||
new_item = CopyIndexTuple(ritem);
|
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.
|
* 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
|
* want to find parent pointing to where we are, right ? - vadim
|
||||||
* 05/27/97
|
* 05/27/97
|
||||||
*/
|
*/
|
||||||
ItemPointerSet(&(stack->bts_btentry.t_tid), bknum, P_HIKEY);
|
stack->bts_btentry = bknum;
|
||||||
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1962,7 +1995,8 @@ _bt_getstackbuf(Relation rel, BTStack stack, int access)
|
|||||||
{
|
{
|
||||||
itemid = PageGetItemId(page, offnum);
|
itemid = PageGetItemId(page, offnum);
|
||||||
item = (IndexTuple) PageGetItem(page, itemid);
|
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 */
|
/* Return accurate pointer to where link is now */
|
||||||
stack->bts_blkno = blkno;
|
stack->bts_blkno = blkno;
|
||||||
@ -1977,7 +2011,8 @@ _bt_getstackbuf(Relation rel, BTStack stack, int access)
|
|||||||
{
|
{
|
||||||
itemid = PageGetItemId(page, offnum);
|
itemid = PageGetItemId(page, offnum);
|
||||||
item = (IndexTuple) PageGetItem(page, itemid);
|
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 */
|
/* Return accurate pointer to where link is now */
|
||||||
stack->bts_blkno = blkno;
|
stack->bts_blkno = blkno;
|
||||||
@ -2067,7 +2102,8 @@ _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf)
|
|||||||
left_item_sz = sizeof(IndexTupleData);
|
left_item_sz = sizeof(IndexTupleData);
|
||||||
left_item = (IndexTuple) palloc(left_item_sz);
|
left_item = (IndexTuple) palloc(left_item_sz);
|
||||||
left_item->t_info = 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
|
* 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);
|
right_item_sz = ItemIdGetLength(itemid);
|
||||||
item = (IndexTuple) PageGetItem(lpage, itemid);
|
item = (IndexTuple) PageGetItem(lpage, itemid);
|
||||||
right_item = CopyIndexTuple(item);
|
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 */
|
/* NO EREPORT(ERROR) from here till newroot op is logged */
|
||||||
START_CRIT_SECTION();
|
START_CRIT_SECTION();
|
||||||
@ -2208,6 +2244,7 @@ _bt_pgaddtup(Page page,
|
|||||||
{
|
{
|
||||||
trunctuple = *itup;
|
trunctuple = *itup;
|
||||||
trunctuple.t_info = sizeof(IndexTupleData);
|
trunctuple.t_info = sizeof(IndexTupleData);
|
||||||
|
BTreeTupSetNAtts(&trunctuple, 0);
|
||||||
itup = &trunctuple;
|
itup = &trunctuple;
|
||||||
itemsize = sizeof(IndexTupleData);
|
itemsize = sizeof(IndexTupleData);
|
||||||
}
|
}
|
||||||
@ -2226,9 +2263,10 @@ _bt_pgaddtup(Page page,
|
|||||||
* Rule is simple: NOT_NULL not equal NULL, NULL not equal NULL too.
|
* Rule is simple: NOT_NULL not equal NULL, NULL not equal NULL too.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
_bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
|
_bt_isequal(Relation idxrel, Page page, OffsetNumber offnum,
|
||||||
int keysz, ScanKey scankey)
|
int keysz, ScanKey scankey)
|
||||||
{
|
{
|
||||||
|
TupleDesc itupdesc = RelationGetDescr(idxrel);
|
||||||
IndexTuple itup;
|
IndexTuple itup;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -2237,6 +2275,17 @@ _bt_isequal(TupleDesc itupdesc, Page page, OffsetNumber offnum,
|
|||||||
|
|
||||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, 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++)
|
for (i = 1; i <= keysz; i++)
|
||||||
{
|
{
|
||||||
AttrNumber attno;
|
AttrNumber attno;
|
||||||
|
@ -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
|
* Locate the downlink of "child" in the parent (updating the stack entry
|
||||||
* if needed)
|
* if needed)
|
||||||
*/
|
*/
|
||||||
ItemPointerSet(&(stack->bts_btentry.t_tid), child, P_HIKEY);
|
stack->bts_btentry = child;
|
||||||
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
pbuf = _bt_getstackbuf(rel, stack, BT_WRITE);
|
||||||
if (pbuf == InvalidBuffer)
|
if (pbuf == InvalidBuffer)
|
||||||
elog(ERROR, "failed to re-find parent key in index \"%s\" for deletion target page %u",
|
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 */
|
/* we need an insertion scan key for the search, so build one */
|
||||||
itup_scankey = _bt_mkscankey(rel, targetkey);
|
itup_scankey = _bt_mkscankey(rel, targetkey);
|
||||||
/* find the leftmost leaf page containing this key */
|
/* find the leftmost leaf page containing this key */
|
||||||
stack = _bt_search(rel, rel->rd_rel->relnatts, itup_scankey,
|
stack = _bt_search(rel,
|
||||||
false, &lbuf, BT_READ, NULL);
|
IndexRelationGetNumberOfKeyAttributes(rel),
|
||||||
|
itup_scankey, false, &lbuf, BT_READ, NULL);
|
||||||
/* don't need a pin on the page */
|
/* don't need a pin on the page */
|
||||||
_bt_relbuf(rel, lbuf);
|
_bt_relbuf(rel, lbuf);
|
||||||
|
|
||||||
@ -1551,15 +1552,15 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
|
|||||||
#ifdef USE_ASSERT_CHECKING
|
#ifdef USE_ASSERT_CHECKING
|
||||||
itemid = PageGetItemId(page, topoff);
|
itemid = PageGetItemId(page, topoff);
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||||
Assert(ItemPointerGetBlockNumber(&(itup->t_tid)) == target);
|
Assert(BTreeInnerTupleGetDownLink(itup) == target);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
nextoffset = OffsetNumberNext(topoff);
|
nextoffset = OffsetNumberNext(topoff);
|
||||||
itemid = PageGetItemId(page, nextoffset);
|
itemid = PageGetItemId(page, nextoffset);
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
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\"",
|
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));
|
BufferGetBlockNumber(topparent), RelationGetRelationName(rel));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1582,7 +1583,7 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
|
|||||||
|
|
||||||
itemid = PageGetItemId(page, topoff);
|
itemid = PageGetItemId(page, topoff);
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||||
ItemPointerSet(&(itup->t_tid), rightsib, P_HIKEY);
|
BTreeInnerTupleSetDownLink(itup, rightsib);
|
||||||
|
|
||||||
nextoffset = OffsetNumberNext(topoff);
|
nextoffset = OffsetNumberNext(topoff);
|
||||||
PageIndexTupleDelete(page, nextoffset);
|
PageIndexTupleDelete(page, nextoffset);
|
||||||
@ -1601,7 +1602,7 @@ _bt_mark_page_halfdead(Relation rel, Buffer leafbuf, BTStack stack)
|
|||||||
MemSet(&trunctuple, 0, sizeof(IndexTupleData));
|
MemSet(&trunctuple, 0, sizeof(IndexTupleData));
|
||||||
trunctuple.t_info = sizeof(IndexTupleData);
|
trunctuple.t_info = sizeof(IndexTupleData);
|
||||||
if (target != leafblkno)
|
if (target != leafblkno)
|
||||||
ItemPointerSet(&trunctuple.t_tid, target, P_HIKEY);
|
ItemPointerSetBlockNumber(&trunctuple.t_tid, target);
|
||||||
else
|
else
|
||||||
ItemPointerSetInvalid(&trunctuple.t_tid);
|
ItemPointerSetInvalid(&trunctuple.t_tid);
|
||||||
if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
|
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))
|
if (ItemPointerIsValid(leafhikey))
|
||||||
{
|
{
|
||||||
target = ItemPointerGetBlockNumber(leafhikey);
|
target = ItemPointerGetBlockNumberNoCheck(leafhikey);
|
||||||
Assert(target != leafblkno);
|
Assert(target != leafblkno);
|
||||||
|
|
||||||
/* fetch the block number of the topmost parent's left sibling */
|
/* 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. */
|
/* remember the next non-leaf child down in the branch. */
|
||||||
itemid = PageGetItemId(page, P_FIRSTDATAKEY(opaque));
|
itemid = PageGetItemId(page, P_FIRSTDATAKEY(opaque));
|
||||||
nextchild = ItemPointerGetBlockNumber(&((IndexTuple) PageGetItem(page, itemid))->t_tid);
|
nextchild = BTreeInnerTupleGetDownLink((IndexTuple) PageGetItem(page, itemid));
|
||||||
if (nextchild == leafblkno)
|
if (nextchild == leafblkno)
|
||||||
nextchild = InvalidBlockNumber;
|
nextchild = InvalidBlockNumber;
|
||||||
}
|
}
|
||||||
@ -1920,7 +1921,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
|
|||||||
if (nextchild == InvalidBlockNumber)
|
if (nextchild == InvalidBlockNumber)
|
||||||
ItemPointerSetInvalid(leafhikey);
|
ItemPointerSetInvalid(leafhikey);
|
||||||
else
|
else
|
||||||
ItemPointerSet(leafhikey, nextchild, P_HIKEY);
|
ItemPointerSetBlockNumber(leafhikey, nextchild);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -121,6 +121,7 @@ bthandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = true;
|
amroutine->amclusterable = true;
|
||||||
amroutine->ampredlocks = true;
|
amroutine->ampredlocks = true;
|
||||||
amroutine->amcanparallel = true;
|
amroutine->amcanparallel = true;
|
||||||
|
amroutine->amcaninclude = true;
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
amroutine->ambuild = btbuild;
|
amroutine->ambuild = btbuild;
|
||||||
|
@ -147,7 +147,7 @@ _bt_search(Relation rel, int keysz, ScanKey scankey, bool nextkey,
|
|||||||
offnum = _bt_binsrch(rel, *bufP, keysz, scankey, nextkey);
|
offnum = _bt_binsrch(rel, *bufP, keysz, scankey, nextkey);
|
||||||
itemid = PageGetItemId(page, offnum);
|
itemid = PageGetItemId(page, offnum);
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||||
blkno = ItemPointerGetBlockNumber(&(itup->t_tid));
|
blkno = BTreeInnerTupleGetDownLink(itup);
|
||||||
par_blkno = BufferGetBlockNumber(*bufP);
|
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 = (BTStack) palloc(sizeof(BTStackData));
|
||||||
new_stack->bts_blkno = par_blkno;
|
new_stack->bts_blkno = par_blkno;
|
||||||
new_stack->bts_offset = offnum;
|
new_stack->bts_offset = offnum;
|
||||||
memcpy(&new_stack->bts_btentry, itup, sizeof(IndexTupleData));
|
new_stack->bts_btentry = blkno;
|
||||||
new_stack->bts_parent = stack_in;
|
new_stack->bts_parent = stack_in;
|
||||||
|
|
||||||
/* drop the read lock on the parent page, acquire one on the child */
|
/* drop the read lock on the parent page, acquire one on the child */
|
||||||
@ -436,6 +436,15 @@ _bt_compare(Relation rel,
|
|||||||
IndexTuple itup;
|
IndexTuple itup;
|
||||||
int i;
|
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
|
* Force result ">" if target item is first data item on an internal page
|
||||||
* --- see NOTE above.
|
* --- see NOTE above.
|
||||||
@ -1833,7 +1842,7 @@ _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
|
|||||||
offnum = P_FIRSTDATAKEY(opaque);
|
offnum = P_FIRSTDATAKEY(opaque);
|
||||||
|
|
||||||
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
|
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
|
||||||
blkno = ItemPointerGetBlockNumber(&(itup->t_tid));
|
blkno = BTreeInnerTupleGetDownLink(itup);
|
||||||
|
|
||||||
buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ);
|
buf = _bt_relandgetbuf(rel, buf, blkno, BT_READ);
|
||||||
page = BufferGetPage(buf);
|
page = BufferGetPage(buf);
|
||||||
@ -1959,3 +1968,51 @@ _bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
|
|||||||
so->numKilled = 0; /* just paranoia */
|
so->numKilled = 0; /* just paranoia */
|
||||||
so->markItemIndex = -1; /* ditto */
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -752,6 +752,7 @@ _bt_sortaddtup(Page page,
|
|||||||
{
|
{
|
||||||
trunctuple = *itup;
|
trunctuple = *itup;
|
||||||
trunctuple.t_info = sizeof(IndexTupleData);
|
trunctuple.t_info = sizeof(IndexTupleData);
|
||||||
|
BTreeTupSetNAtts(&trunctuple, 0);
|
||||||
itup = &trunctuple;
|
itup = &trunctuple;
|
||||||
itemsize = sizeof(IndexTupleData);
|
itemsize = sizeof(IndexTupleData);
|
||||||
}
|
}
|
||||||
@ -802,6 +803,9 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
|
|||||||
OffsetNumber last_off;
|
OffsetNumber last_off;
|
||||||
Size pgspc;
|
Size pgspc;
|
||||||
Size itupsz;
|
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
|
* 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 ii;
|
||||||
ItemId hii;
|
ItemId hii;
|
||||||
IndexTuple oitup;
|
IndexTuple oitup;
|
||||||
|
IndexTuple keytup;
|
||||||
|
BTPageOpaque opageop = (BTPageOpaque) PageGetSpecialPointer(opage);
|
||||||
|
|
||||||
/* Create new page of same level */
|
/* Create new page of same level */
|
||||||
npage = _bt_blnewpage(state->btps_level);
|
npage = _bt_blnewpage(state->btps_level);
|
||||||
@ -883,6 +889,29 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
|
|||||||
ItemIdSetUnused(ii); /* redundant */
|
ItemIdSetUnused(ii); /* redundant */
|
||||||
((PageHeader) opage)->pd_lower -= sizeof(ItemIdData);
|
((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
|
* 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
|
* 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);
|
state->btps_next = _bt_pagestate(wstate, state->btps_level + 1);
|
||||||
|
|
||||||
Assert(state->btps_minkey != NULL);
|
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);
|
_bt_buildadd(wstate, state->btps_next, state->btps_minkey);
|
||||||
pfree(state->btps_minkey);
|
pfree(state->btps_minkey);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Save a copy of the minimum key for the new page. We have to copy
|
* 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
|
* 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);
|
state->btps_minkey = CopyIndexTuple(oitup);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -927,6 +959,8 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup)
|
|||||||
last_off = P_FIRSTKEY;
|
last_off = P_FIRSTKEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageop = (BTPageOpaque) PageGetSpecialPointer(npage);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the new item is the first for its page, stash a copy for later. Note
|
* 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,
|
* 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)
|
if (last_off == P_HIKEY)
|
||||||
{
|
{
|
||||||
Assert(state->btps_minkey == NULL);
|
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
|
else
|
||||||
{
|
{
|
||||||
Assert(s->btps_minkey != NULL);
|
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);
|
_bt_buildadd(wstate, s->btps_next, s->btps_minkey);
|
||||||
pfree(s->btps_minkey);
|
pfree(s->btps_minkey);
|
||||||
s->btps_minkey = NULL;
|
s->btps_minkey = NULL;
|
||||||
@ -1029,7 +1071,7 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
|
|||||||
bool load1;
|
bool load1;
|
||||||
TupleDesc tupdes = RelationGetDescr(wstate->index);
|
TupleDesc tupdes = RelationGetDescr(wstate->index);
|
||||||
int i,
|
int i,
|
||||||
keysz = RelationGetNumberOfAttributes(wstate->index);
|
keysz = IndexRelationGetNumberOfKeyAttributes(wstate->index);
|
||||||
ScanKey indexScanKey = NULL;
|
ScanKey indexScanKey = NULL;
|
||||||
SortSupport sortKeys;
|
SortSupport sortKeys;
|
||||||
|
|
||||||
|
@ -63,17 +63,28 @@ _bt_mkscankey(Relation rel, IndexTuple itup)
|
|||||||
{
|
{
|
||||||
ScanKey skey;
|
ScanKey skey;
|
||||||
TupleDesc itupdesc;
|
TupleDesc itupdesc;
|
||||||
int natts;
|
int indnatts PG_USED_FOR_ASSERTS_ONLY;
|
||||||
|
int indnkeyatts;
|
||||||
int16 *indoption;
|
int16 *indoption;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
itupdesc = RelationGetDescr(rel);
|
itupdesc = RelationGetDescr(rel);
|
||||||
natts = RelationGetNumberOfAttributes(rel);
|
indnatts = IndexRelationGetNumberOfAttributes(rel);
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
|
||||||
indoption = rel->rd_indoption;
|
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;
|
FmgrInfo *procinfo;
|
||||||
Datum arg;
|
Datum arg;
|
||||||
@ -115,16 +126,16 @@ ScanKey
|
|||||||
_bt_mkscankey_nodata(Relation rel)
|
_bt_mkscankey_nodata(Relation rel)
|
||||||
{
|
{
|
||||||
ScanKey skey;
|
ScanKey skey;
|
||||||
int natts;
|
int indnkeyatts;
|
||||||
int16 *indoption;
|
int16 *indoption;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
natts = RelationGetNumberOfAttributes(rel);
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
|
||||||
indoption = rel->rd_indoption;
|
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;
|
FmgrInfo *procinfo;
|
||||||
int flags;
|
int flags;
|
||||||
@ -2069,3 +2080,30 @@ btproperty(Oid index_oid, int attno,
|
|||||||
return false; /* punt to generic code */
|
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;
|
||||||
|
}
|
||||||
|
@ -202,7 +202,7 @@ btree_xlog_insert(bool isleaf, bool ismeta, XLogReaderState *record)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
btree_xlog_split(bool onleft, XLogReaderState *record)
|
btree_xlog_split(bool onleft, bool lhighkey, XLogReaderState *record)
|
||||||
{
|
{
|
||||||
XLogRecPtr lsn = record->EndRecPtr;
|
XLogRecPtr lsn = record->EndRecPtr;
|
||||||
xl_btree_split *xlrec = (xl_btree_split *) XLogRecGetData(record);
|
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);
|
_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
|
* When the high key isn't present is the wal record, then we assume it to
|
||||||
* on the right page.
|
* be equal to the first key on the right page.
|
||||||
*/
|
*/
|
||||||
if (isleaf)
|
if (!lhighkey)
|
||||||
{
|
{
|
||||||
ItemId hiItemId = PageGetItemId(rpage, P_FIRSTDATAKEY(ropaque));
|
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) */
|
/* Extract left hikey and its size (assuming 16-bit alignment) */
|
||||||
if (!isleaf)
|
if (lhighkey)
|
||||||
{
|
{
|
||||||
left_hikey = (IndexTuple) datapos;
|
left_hikey = (IndexTuple) datapos;
|
||||||
left_hikeysz = MAXALIGN(IndexTupleSize(left_hikey));
|
left_hikeysz = MAXALIGN(IndexTupleSize(left_hikey));
|
||||||
datapos += left_hikeysz;
|
datapos += left_hikeysz;
|
||||||
datalen -= left_hikeysz;
|
datalen -= left_hikeysz;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(datalen == 0);
|
Assert(datalen == 0);
|
||||||
|
|
||||||
newlpage = PageGetTempPageCopySpecial(lpage);
|
newlpage = PageGetTempPageCopySpecial(lpage);
|
||||||
@ -616,7 +620,7 @@ btree_xlog_delete_get_latestRemovedXid(XLogReaderState *record)
|
|||||||
* heap_fetch, since it uses ReadBuffer rather than XLogReadBuffer.
|
* heap_fetch, since it uses ReadBuffer rather than XLogReadBuffer.
|
||||||
* Note that we are not looking at tuple data here, just headers.
|
* 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);
|
hitemid = PageGetItemId(hpage, hoffnum);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -764,11 +768,11 @@ btree_xlog_mark_page_halfdead(uint8 info, XLogReaderState *record)
|
|||||||
nextoffset = OffsetNumberNext(poffset);
|
nextoffset = OffsetNumberNext(poffset);
|
||||||
itemid = PageGetItemId(page, nextoffset);
|
itemid = PageGetItemId(page, nextoffset);
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||||
rightsib = ItemPointerGetBlockNumber(&itup->t_tid);
|
rightsib = BTreeInnerTupleGetDownLink(itup);
|
||||||
|
|
||||||
itemid = PageGetItemId(page, poffset);
|
itemid = PageGetItemId(page, poffset);
|
||||||
itup = (IndexTuple) PageGetItem(page, itemid);
|
itup = (IndexTuple) PageGetItem(page, itemid);
|
||||||
ItemPointerSet(&(itup->t_tid), rightsib, P_HIKEY);
|
BTreeInnerTupleSetDownLink(itup, rightsib);
|
||||||
nextoffset = OffsetNumberNext(poffset);
|
nextoffset = OffsetNumberNext(poffset);
|
||||||
PageIndexTupleDelete(page, nextoffset);
|
PageIndexTupleDelete(page, nextoffset);
|
||||||
|
|
||||||
@ -798,7 +802,7 @@ btree_xlog_mark_page_halfdead(uint8 info, XLogReaderState *record)
|
|||||||
MemSet(&trunctuple, 0, sizeof(IndexTupleData));
|
MemSet(&trunctuple, 0, sizeof(IndexTupleData));
|
||||||
trunctuple.t_info = sizeof(IndexTupleData);
|
trunctuple.t_info = sizeof(IndexTupleData);
|
||||||
if (xlrec->topparent != InvalidBlockNumber)
|
if (xlrec->topparent != InvalidBlockNumber)
|
||||||
ItemPointerSet(&trunctuple.t_tid, xlrec->topparent, P_HIKEY);
|
ItemPointerSetBlockNumber(&trunctuple.t_tid, xlrec->topparent);
|
||||||
else
|
else
|
||||||
ItemPointerSetInvalid(&trunctuple.t_tid);
|
ItemPointerSetInvalid(&trunctuple.t_tid);
|
||||||
if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
|
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));
|
MemSet(&trunctuple, 0, sizeof(IndexTupleData));
|
||||||
trunctuple.t_info = sizeof(IndexTupleData);
|
trunctuple.t_info = sizeof(IndexTupleData);
|
||||||
if (xlrec->topparent != InvalidBlockNumber)
|
if (xlrec->topparent != InvalidBlockNumber)
|
||||||
ItemPointerSet(&trunctuple.t_tid, xlrec->topparent, P_HIKEY);
|
ItemPointerSetBlockNumber(&trunctuple.t_tid, xlrec->topparent);
|
||||||
else
|
else
|
||||||
ItemPointerSetInvalid(&trunctuple.t_tid);
|
ItemPointerSetInvalid(&trunctuple.t_tid);
|
||||||
if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
|
if (PageAddItem(page, (Item) &trunctuple, sizeof(IndexTupleData), P_HIKEY,
|
||||||
@ -1004,10 +1008,16 @@ btree_redo(XLogReaderState *record)
|
|||||||
btree_xlog_insert(false, true, record);
|
btree_xlog_insert(false, true, record);
|
||||||
break;
|
break;
|
||||||
case XLOG_BTREE_SPLIT_L:
|
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;
|
break;
|
||||||
case XLOG_BTREE_SPLIT_R:
|
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;
|
break;
|
||||||
case XLOG_BTREE_VACUUM:
|
case XLOG_BTREE_VACUUM:
|
||||||
btree_xlog_vacuum(record);
|
btree_xlog_vacuum(record);
|
||||||
|
@ -35,6 +35,8 @@ btree_desc(StringInfo buf, XLogReaderState *record)
|
|||||||
}
|
}
|
||||||
case XLOG_BTREE_SPLIT_L:
|
case XLOG_BTREE_SPLIT_L:
|
||||||
case XLOG_BTREE_SPLIT_R:
|
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;
|
xl_btree_split *xlrec = (xl_btree_split *) rec;
|
||||||
|
|
||||||
@ -119,6 +121,12 @@ btree_identify(uint8 info)
|
|||||||
case XLOG_BTREE_SPLIT_R:
|
case XLOG_BTREE_SPLIT_R:
|
||||||
id = "SPLIT_R";
|
id = "SPLIT_R";
|
||||||
break;
|
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:
|
case XLOG_BTREE_VACUUM:
|
||||||
id = "VACUUM";
|
id = "VACUUM";
|
||||||
break;
|
break;
|
||||||
|
@ -50,6 +50,7 @@ spghandler(PG_FUNCTION_ARGS)
|
|||||||
amroutine->amclusterable = false;
|
amroutine->amclusterable = false;
|
||||||
amroutine->ampredlocks = false;
|
amroutine->ampredlocks = false;
|
||||||
amroutine->amcanparallel = false;
|
amroutine->amcanparallel = false;
|
||||||
|
amroutine->amcaninclude = false;
|
||||||
amroutine->amkeytype = InvalidOid;
|
amroutine->amkeytype = InvalidOid;
|
||||||
|
|
||||||
amroutine->ambuild = spgbuild;
|
amroutine->ambuild = spgbuild;
|
||||||
|
@ -302,6 +302,7 @@ Boot_DeclareIndexStmt:
|
|||||||
stmt->accessMethod = $8;
|
stmt->accessMethod = $8;
|
||||||
stmt->tableSpace = NULL;
|
stmt->tableSpace = NULL;
|
||||||
stmt->indexParams = $10;
|
stmt->indexParams = $10;
|
||||||
|
stmt->indexIncludingParams = NIL;
|
||||||
stmt->options = NIL;
|
stmt->options = NIL;
|
||||||
stmt->whereClause = NULL;
|
stmt->whereClause = NULL;
|
||||||
stmt->excludeOpNames = NIL;
|
stmt->excludeOpNames = NIL;
|
||||||
@ -350,6 +351,7 @@ Boot_DeclareUniqueIndexStmt:
|
|||||||
stmt->accessMethod = $9;
|
stmt->accessMethod = $9;
|
||||||
stmt->tableSpace = NULL;
|
stmt->tableSpace = NULL;
|
||||||
stmt->indexParams = $11;
|
stmt->indexParams = $11;
|
||||||
|
stmt->indexIncludingParams = NIL;
|
||||||
stmt->options = NIL;
|
stmt->options = NIL;
|
||||||
stmt->whereClause = NULL;
|
stmt->whereClause = NULL;
|
||||||
stmt->excludeOpNames = NIL;
|
stmt->excludeOpNames = NIL;
|
||||||
|
@ -616,7 +616,7 @@ boot_openrel(char *relname)
|
|||||||
relname, (int) ATTRIBUTE_FIXED_PART_SIZE);
|
relname, (int) ATTRIBUTE_FIXED_PART_SIZE);
|
||||||
|
|
||||||
boot_reldesc = heap_openrv(makeRangeVar(NULL, relname, -1), NoLock);
|
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++)
|
for (i = 0; i < numattr; i++)
|
||||||
{
|
{
|
||||||
if (attrtypes[i] == NULL)
|
if (attrtypes[i] == NULL)
|
||||||
|
@ -2268,7 +2268,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
|
|||||||
InvalidOid, /* no parent constraint */
|
InvalidOid, /* no parent constraint */
|
||||||
RelationGetRelid(rel), /* relation */
|
RelationGetRelid(rel), /* relation */
|
||||||
attNos, /* attrs in the constraint */
|
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, /* not a domain constraint */
|
||||||
InvalidOid, /* no associated index */
|
InvalidOid, /* no associated index */
|
||||||
InvalidOid, /* Foreign key fields */
|
InvalidOid, /* Foreign key fields */
|
||||||
|
@ -238,7 +238,7 @@ index_check_primary_key(Relation heapRel,
|
|||||||
* null, otherwise attempt to ALTER TABLE .. SET NOT NULL
|
* null, otherwise attempt to ALTER TABLE .. SET NOT NULL
|
||||||
*/
|
*/
|
||||||
cmds = NIL;
|
cmds = NIL;
|
||||||
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
|
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
|
||||||
{
|
{
|
||||||
AttrNumber attnum = indexInfo->ii_KeyAttrNumbers[i];
|
AttrNumber attnum = indexInfo->ii_KeyAttrNumbers[i];
|
||||||
HeapTuple atttuple;
|
HeapTuple atttuple;
|
||||||
@ -447,32 +447,40 @@ ConstructTupleDescriptor(Relation heapRelation,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Check the opclass and index AM to see if either provides a keytype
|
* 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]));
|
keyType = amroutine->amkeytype;
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If keytype is specified as ANYELEMENT, and opcintype is ANYARRAY,
|
* Code below is concerned to the opclasses which are not used with
|
||||||
* then the attribute type must be an array (else it'd not have
|
* the included columns.
|
||||||
* matched this opclass); use its element type.
|
|
||||||
*/
|
*/
|
||||||
if (keyType == ANYELEMENTOID && opclassTup->opcintype == ANYARRAYOID)
|
if (i < indexInfo->ii_NumIndexKeyAttrs)
|
||||||
{
|
{
|
||||||
keyType = get_base_element_type(to->atttypid);
|
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(classObjectId[i]));
|
||||||
if (!OidIsValid(keyType))
|
if (!HeapTupleIsValid(tuple))
|
||||||
elog(ERROR, "could not get element type of array type %u",
|
elog(ERROR, "cache lookup failed for opclass %u",
|
||||||
to->atttypid);
|
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
|
* 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++)
|
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
|
||||||
indkey->values[i] = indexInfo->ii_KeyAttrNumbers[i];
|
indkey->values[i] = indexInfo->ii_KeyAttrNumbers[i];
|
||||||
indcollation = buildoidvector(collationOids, indexInfo->ii_NumIndexAttrs);
|
indcollation = buildoidvector(collationOids, indexInfo->ii_NumIndexAttrs);
|
||||||
indclass = buildoidvector(classOids, indexInfo->ii_NumIndexAttrs);
|
indclass = buildoidvector(classOids, indexInfo->ii_NumIndexKeyAttrs);
|
||||||
indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexAttrs);
|
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_indexrelid - 1] = ObjectIdGetDatum(indexoid);
|
||||||
values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
|
values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
|
||||||
values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
|
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_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
|
||||||
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
|
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
|
||||||
values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(isexclusion);
|
values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(isexclusion);
|
||||||
@ -1086,7 +1095,7 @@ index_create(Relation heapRelation,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Store dependency on operator classes */
|
/* 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.classId = OperatorClassRelationId;
|
||||||
referenced.objectId = classObjectId[i];
|
referenced.objectId = classObjectId[i];
|
||||||
@ -1142,6 +1151,8 @@ index_create(Relation heapRelation,
|
|||||||
else
|
else
|
||||||
Assert(indexRelation->rd_indexcxt != NULL);
|
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
|
* 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
|
* index yet. We'll be creating more indexes and classes later, so we
|
||||||
@ -1287,6 +1298,7 @@ index_constraint_create(Relation heapRelation,
|
|||||||
parentConstraintId,
|
parentConstraintId,
|
||||||
RelationGetRelid(heapRelation),
|
RelationGetRelid(heapRelation),
|
||||||
indexInfo->ii_KeyAttrNumbers,
|
indexInfo->ii_KeyAttrNumbers,
|
||||||
|
indexInfo->ii_NumIndexKeyAttrs,
|
||||||
indexInfo->ii_NumIndexAttrs,
|
indexInfo->ii_NumIndexAttrs,
|
||||||
InvalidOid, /* no domain */
|
InvalidOid, /* no domain */
|
||||||
indexRelationId, /* index OID */
|
indexRelationId, /* index OID */
|
||||||
@ -1732,15 +1744,19 @@ BuildIndexInfo(Relation index)
|
|||||||
IndexInfo *ii = makeNode(IndexInfo);
|
IndexInfo *ii = makeNode(IndexInfo);
|
||||||
Form_pg_index indexStruct = index->rd_index;
|
Form_pg_index indexStruct = index->rd_index;
|
||||||
int i;
|
int i;
|
||||||
int numKeys;
|
int numAtts;
|
||||||
|
|
||||||
/* check the number of keys, and copy attr numbers into the IndexInfo */
|
/* check the number of keys, and copy attr numbers into the IndexInfo */
|
||||||
numKeys = indexStruct->indnatts;
|
numAtts = indexStruct->indnatts;
|
||||||
if (numKeys < 1 || numKeys > INDEX_MAX_KEYS)
|
if (numAtts < 1 || numAtts > INDEX_MAX_KEYS)
|
||||||
elog(ERROR, "invalid indnatts %d for index %u",
|
elog(ERROR, "invalid indnatts %d for index %u",
|
||||||
numKeys, RelationGetRelid(index));
|
numAtts, RelationGetRelid(index));
|
||||||
ii->ii_NumIndexAttrs = numKeys;
|
ii->ii_NumIndexAttrs = numAtts;
|
||||||
for (i = 0; i < numKeys; i++)
|
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];
|
ii->ii_KeyAttrNumbers[i] = indexStruct->indkey.values[i];
|
||||||
|
|
||||||
/* fetch any expressions needed for expressional indexes */
|
/* fetch any expressions needed for expressional indexes */
|
||||||
@ -1911,9 +1927,11 @@ CompareIndexInfo(IndexInfo *info1, IndexInfo *info2,
|
|||||||
void
|
void
|
||||||
BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
|
BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
|
||||||
{
|
{
|
||||||
int ncols = index->rd_rel->relnatts;
|
int indnkeyatts;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* fetch info for checking unique indexes
|
* fetch info for checking unique indexes
|
||||||
*/
|
*/
|
||||||
@ -1922,16 +1940,16 @@ BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii)
|
|||||||
if (index->rd_rel->relam != BTREE_AM_OID)
|
if (index->rd_rel->relam != BTREE_AM_OID)
|
||||||
elog(ERROR, "unexpected non-btree speculative unique index");
|
elog(ERROR, "unexpected non-btree speculative unique index");
|
||||||
|
|
||||||
ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * ncols);
|
ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
|
||||||
ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * ncols);
|
ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
|
||||||
ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
|
ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We have to look up the operator's strategy number. This provides a
|
* We have to look up the operator's strategy number. This provides a
|
||||||
* cross-check that the operator does match the index.
|
* cross-check that the operator does match the index.
|
||||||
*/
|
*/
|
||||||
/* We need the func OIDs and strategy numbers too */
|
/* 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_UniqueStrats[i] = BTEqualStrategyNumber;
|
||||||
ii->ii_UniqueOps[i] =
|
ii->ii_UniqueOps[i] =
|
||||||
|
@ -119,6 +119,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
|
|||||||
Assert(indexInfo->ii_Predicate == NIL);
|
Assert(indexInfo->ii_Predicate == NIL);
|
||||||
Assert(indexInfo->ii_ExclusionOps == NULL);
|
Assert(indexInfo->ii_ExclusionOps == NULL);
|
||||||
Assert(relationDescs[i]->rd_index->indimmediate);
|
Assert(relationDescs[i]->rd_index->indimmediate);
|
||||||
|
Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FormIndexDatum fills in its values and isnull parameters with the
|
* FormIndexDatum fills in its values and isnull parameters with the
|
||||||
|
@ -57,6 +57,7 @@ CreateConstraintEntry(const char *constraintName,
|
|||||||
Oid relId,
|
Oid relId,
|
||||||
const int16 *constraintKey,
|
const int16 *constraintKey,
|
||||||
int constraintNKeys,
|
int constraintNKeys,
|
||||||
|
int constraintNTotalKeys,
|
||||||
Oid domainId,
|
Oid domainId,
|
||||||
Oid indexRelId,
|
Oid indexRelId,
|
||||||
Oid foreignRelId,
|
Oid foreignRelId,
|
||||||
@ -83,6 +84,7 @@ CreateConstraintEntry(const char *constraintName,
|
|||||||
bool nulls[Natts_pg_constraint];
|
bool nulls[Natts_pg_constraint];
|
||||||
Datum values[Natts_pg_constraint];
|
Datum values[Natts_pg_constraint];
|
||||||
ArrayType *conkeyArray;
|
ArrayType *conkeyArray;
|
||||||
|
ArrayType *conincludingArray;
|
||||||
ArrayType *confkeyArray;
|
ArrayType *confkeyArray;
|
||||||
ArrayType *conpfeqopArray;
|
ArrayType *conpfeqopArray;
|
||||||
ArrayType *conppeqopArray;
|
ArrayType *conppeqopArray;
|
||||||
@ -113,6 +115,21 @@ CreateConstraintEntry(const char *constraintName,
|
|||||||
else
|
else
|
||||||
conkeyArray = NULL;
|
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)
|
if (foreignNKeys > 0)
|
||||||
{
|
{
|
||||||
Datum *fkdatums;
|
Datum *fkdatums;
|
||||||
@ -186,6 +203,11 @@ CreateConstraintEntry(const char *constraintName,
|
|||||||
else
|
else
|
||||||
nulls[Anum_pg_constraint_conkey - 1] = true;
|
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)
|
if (confkeyArray)
|
||||||
values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray);
|
values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray);
|
||||||
else
|
else
|
||||||
@ -247,9 +269,9 @@ CreateConstraintEntry(const char *constraintName,
|
|||||||
|
|
||||||
relobject.classId = RelationRelationId;
|
relobject.classId = RelationRelationId;
|
||||||
relobject.objectId = relId;
|
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];
|
relobject.objectSubId = constraintKey[i];
|
||||||
|
|
||||||
@ -548,6 +570,7 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
|
|||||||
relationId,
|
relationId,
|
||||||
mapped_conkey,
|
mapped_conkey,
|
||||||
nelem,
|
nelem,
|
||||||
|
nelem,
|
||||||
InvalidOid, /* not a domain constraint */
|
InvalidOid, /* not a domain constraint */
|
||||||
constrForm->conindid, /* same index */
|
constrForm->conindid, /* same index */
|
||||||
constrForm->confrelid, /* same foreign rel */
|
constrForm->confrelid, /* same foreign rel */
|
||||||
|
@ -303,6 +303,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
|
|||||||
|
|
||||||
indexInfo = makeNode(IndexInfo);
|
indexInfo = makeNode(IndexInfo);
|
||||||
indexInfo->ii_NumIndexAttrs = 2;
|
indexInfo->ii_NumIndexAttrs = 2;
|
||||||
|
indexInfo->ii_NumIndexKeyAttrs = 2;
|
||||||
indexInfo->ii_KeyAttrNumbers[0] = 1;
|
indexInfo->ii_KeyAttrNumbers[0] = 1;
|
||||||
indexInfo->ii_KeyAttrNumbers[1] = 2;
|
indexInfo->ii_KeyAttrNumbers[1] = 2;
|
||||||
indexInfo->ii_Expressions = NIL;
|
indexInfo->ii_Expressions = NIL;
|
||||||
|
@ -109,8 +109,10 @@ static void ReindexPartitionedIndex(Relation parentIdx);
|
|||||||
* indexes. We acknowledge this when all operator classes, collations and
|
* indexes. We acknowledge this when all operator classes, collations and
|
||||||
* exclusion operators match. Though we could further permit intra-opfamily
|
* exclusion operators match. Though we could further permit intra-opfamily
|
||||||
* changes for btree and hash indexes, that adds subtle complexity with no
|
* 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
|
* When a comparison or exclusion operator has a polymorphic input type, the
|
||||||
* actual input types must also match. This defends against the possibility
|
* actual input types must also match. This defends against the possibility
|
||||||
* that operators could vary behavior in response to get_fn_expr_argtype().
|
* 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. */
|
/* Any change in operator class or collation breaks compatibility. */
|
||||||
old_natts = indexForm->indnatts;
|
old_natts = indexForm->indnkeyatts;
|
||||||
Assert(old_natts == numberOfAttributes);
|
Assert(old_natts == numberOfAttributes);
|
||||||
|
|
||||||
d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indcollation, &isnull);
|
d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indcollation, &isnull);
|
||||||
@ -351,6 +353,7 @@ DefineIndex(Oid relationId,
|
|||||||
bits16 flags;
|
bits16 flags;
|
||||||
bits16 constr_flags;
|
bits16 constr_flags;
|
||||||
int numberOfAttributes;
|
int numberOfAttributes;
|
||||||
|
int numberOfKeyAttributes;
|
||||||
TransactionId limitXmin;
|
TransactionId limitXmin;
|
||||||
VirtualTransactionId *old_snapshots;
|
VirtualTransactionId *old_snapshots;
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
@ -361,10 +364,28 @@ DefineIndex(Oid relationId,
|
|||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
int i;
|
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 attributes in index
|
* count key attributes in index
|
||||||
*/
|
*/
|
||||||
|
numberOfKeyAttributes = list_length(stmt->indexParams);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
numberOfAttributes = list_length(stmt->indexParams);
|
||||||
|
|
||||||
if (numberOfAttributes <= 0)
|
if (numberOfAttributes <= 0)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||||
@ -568,6 +589,11 @@ DefineIndex(Oid relationId,
|
|||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
errmsg("access method \"%s\" does not support unique indexes",
|
errmsg("access method \"%s\" does not support unique indexes",
|
||||||
accessMethodName)));
|
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)
|
if (numberOfAttributes > 1 && !amRoutine->amcanmulticol)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
@ -605,6 +631,7 @@ DefineIndex(Oid relationId,
|
|||||||
*/
|
*/
|
||||||
indexInfo = makeNode(IndexInfo);
|
indexInfo = makeNode(IndexInfo);
|
||||||
indexInfo->ii_NumIndexAttrs = numberOfAttributes;
|
indexInfo->ii_NumIndexAttrs = numberOfAttributes;
|
||||||
|
indexInfo->ii_NumIndexKeyAttrs = numberOfKeyAttributes;
|
||||||
indexInfo->ii_Expressions = NIL; /* for now */
|
indexInfo->ii_Expressions = NIL; /* for now */
|
||||||
indexInfo->ii_ExpressionsState = NIL;
|
indexInfo->ii_ExpressionsState = NIL;
|
||||||
indexInfo->ii_Predicate = make_ands_implicit((Expr *) stmt->whereClause);
|
indexInfo->ii_Predicate = make_ands_implicit((Expr *) stmt->whereClause);
|
||||||
@ -624,7 +651,7 @@ DefineIndex(Oid relationId,
|
|||||||
|
|
||||||
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
|
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
|
||||||
collationObjectId = (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));
|
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
|
||||||
ComputeIndexAttrs(indexInfo,
|
ComputeIndexAttrs(indexInfo,
|
||||||
typeObjectId, collationObjectId, classObjectId,
|
typeObjectId, collationObjectId, classObjectId,
|
||||||
@ -1348,16 +1375,15 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
|||||||
ListCell *nextExclOp;
|
ListCell *nextExclOp;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
int attn;
|
int attn;
|
||||||
|
int nkeycols = indexInfo->ii_NumIndexKeyAttrs;
|
||||||
|
|
||||||
/* Allocate space for exclusion operator info, if needed */
|
/* Allocate space for exclusion operator info, if needed */
|
||||||
if (exclusionOpNames)
|
if (exclusionOpNames)
|
||||||
{
|
{
|
||||||
int ncols = list_length(attList);
|
Assert(list_length(exclusionOpNames) == nkeycols);
|
||||||
|
indexInfo->ii_ExclusionOps = (Oid *) palloc(sizeof(Oid) * nkeycols);
|
||||||
Assert(list_length(exclusionOpNames) == ncols);
|
indexInfo->ii_ExclusionProcs = (Oid *) palloc(sizeof(Oid) * nkeycols);
|
||||||
indexInfo->ii_ExclusionOps = (Oid *) palloc(sizeof(Oid) * ncols);
|
indexInfo->ii_ExclusionStrats = (uint16 *) palloc(sizeof(uint16) * nkeycols);
|
||||||
indexInfo->ii_ExclusionProcs = (Oid *) palloc(sizeof(Oid) * ncols);
|
|
||||||
indexInfo->ii_ExclusionStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
|
|
||||||
nextExclOp = list_head(exclusionOpNames);
|
nextExclOp = list_head(exclusionOpNames);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1410,6 +1436,11 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
|||||||
Node *expr = attribute->expr;
|
Node *expr = attribute->expr;
|
||||||
|
|
||||||
Assert(expr != NULL);
|
Assert(expr != NULL);
|
||||||
|
|
||||||
|
if (attn >= nkeycols)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("expressions are not supported in included columns")));
|
||||||
atttype = exprType(expr);
|
atttype = exprType(expr);
|
||||||
attcollation = exprCollation(expr);
|
attcollation = exprCollation(expr);
|
||||||
|
|
||||||
@ -1487,6 +1518,16 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
|||||||
|
|
||||||
collationOidP[attn] = attcollation;
|
collationOidP[attn] = attcollation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip opclass and ordering options for included columns.
|
||||||
|
*/
|
||||||
|
if (attn >= nkeycols)
|
||||||
|
{
|
||||||
|
colOptionP[attn] = 0;
|
||||||
|
attn++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Identify the opclass to use.
|
* Identify the opclass to use.
|
||||||
*/
|
*/
|
||||||
|
@ -602,7 +602,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
|
|||||||
RelationGetRelationName(tempRel));
|
RelationGetRelationName(tempRel));
|
||||||
diffname = make_temptable_name_n(tempname, 2);
|
diffname = make_temptable_name_n(tempname, 2);
|
||||||
|
|
||||||
relnatts = matviewRel->rd_rel->relnatts;
|
relnatts = RelationGetNumberOfAttributes(matviewRel);
|
||||||
|
|
||||||
/* Open SPI context. */
|
/* Open SPI context. */
|
||||||
if (SPI_connect() != SPI_OK_CONNECT)
|
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))
|
if (is_usable_unique_index(indexRel))
|
||||||
{
|
{
|
||||||
Form_pg_index indexStruct = indexRel->rd_index;
|
Form_pg_index indexStruct = indexRel->rd_index;
|
||||||
int numatts = indexStruct->indnatts;
|
int indnkeyatts = indexStruct->indnkeyatts;
|
||||||
oidvector *indclass;
|
oidvector *indclass;
|
||||||
Datum indclassDatum;
|
Datum indclassDatum;
|
||||||
bool isnull;
|
bool isnull;
|
||||||
@ -695,7 +695,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
|
|||||||
indclass = (oidvector *) DatumGetPointer(indclassDatum);
|
indclass = (oidvector *) DatumGetPointer(indclassDatum);
|
||||||
|
|
||||||
/* Add quals for all columns from this index. */
|
/* 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];
|
int attnum = indexStruct->indkey.values[i];
|
||||||
Oid opclass = indclass->values[i];
|
Oid opclass = indclass->values[i];
|
||||||
|
@ -5942,7 +5942,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
|
|||||||
* Loop over each attribute in the primary key and see if it
|
* Loop over each attribute in the primary key and see if it
|
||||||
* matches the to-be-altered attribute
|
* 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)
|
if (indexStruct->indkey.values[i] == attnum)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -7641,6 +7641,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
|||||||
RelationGetRelid(rel),
|
RelationGetRelid(rel),
|
||||||
fkattnum,
|
fkattnum,
|
||||||
numfks,
|
numfks,
|
||||||
|
numfks,
|
||||||
InvalidOid, /* not a domain constraint */
|
InvalidOid, /* not a domain constraint */
|
||||||
indexOid,
|
indexOid,
|
||||||
RelationGetRelid(pkrel),
|
RelationGetRelid(pkrel),
|
||||||
@ -8199,7 +8200,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
|
|||||||
* assume a primary key cannot have expressional elements)
|
* assume a primary key cannot have expressional elements)
|
||||||
*/
|
*/
|
||||||
*attnamelist = NIL;
|
*attnamelist = NIL;
|
||||||
for (i = 0; i < indexStruct->indnatts; i++)
|
for (i = 0; i < indexStruct->indnkeyatts; i++)
|
||||||
{
|
{
|
||||||
int pkattno = indexStruct->indkey.values[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
|
* partial index; forget it if there are any expressions, too. Invalid
|
||||||
* indexes are out as well.
|
* indexes are out as well.
|
||||||
*/
|
*/
|
||||||
if (indexStruct->indnatts == numattrs &&
|
if (indexStruct->indnkeyatts == numattrs &&
|
||||||
indexStruct->indisunique &&
|
indexStruct->indisunique &&
|
||||||
IndexIsValid(indexStruct) &&
|
IndexIsValid(indexStruct) &&
|
||||||
heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
|
heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
|
||||||
@ -12529,7 +12530,7 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
|
|||||||
RelationGetRelationName(indexRel))));
|
RelationGetRelationName(indexRel))));
|
||||||
|
|
||||||
/* Check index for nullable columns. */
|
/* 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];
|
int16 attno = indexRel->rd_index->indkey.values[key];
|
||||||
Form_pg_attribute attr;
|
Form_pg_attribute attr;
|
||||||
|
@ -742,6 +742,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
|
|||||||
RelationGetRelid(rel),
|
RelationGetRelid(rel),
|
||||||
NULL, /* no conkey */
|
NULL, /* no conkey */
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
InvalidOid, /* no domain */
|
InvalidOid, /* no domain */
|
||||||
InvalidOid, /* no index */
|
InvalidOid, /* no index */
|
||||||
InvalidOid, /* no foreign key */
|
InvalidOid, /* no foreign key */
|
||||||
|
@ -3157,6 +3157,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
|
|||||||
InvalidOid, /* not a relation constraint */
|
InvalidOid, /* not a relation constraint */
|
||||||
NULL,
|
NULL,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
domainOid, /* domain constraint */
|
domainOid, /* domain constraint */
|
||||||
InvalidOid, /* no associated index */
|
InvalidOid, /* no associated index */
|
||||||
InvalidOid, /* Foreign key fields */
|
InvalidOid, /* Foreign key fields */
|
||||||
|
@ -648,7 +648,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
|
|||||||
Oid *constr_procs;
|
Oid *constr_procs;
|
||||||
uint16 *constr_strats;
|
uint16 *constr_strats;
|
||||||
Oid *index_collations = index->rd_indcollation;
|
Oid *index_collations = index->rd_indcollation;
|
||||||
int index_natts = index->rd_index->indnatts;
|
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
|
||||||
IndexScanDesc index_scan;
|
IndexScanDesc index_scan;
|
||||||
HeapTuple tup;
|
HeapTuple tup;
|
||||||
ScanKeyData scankeys[INDEX_MAX_KEYS];
|
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
|
* If any of the input values are NULL, the constraint check is assumed to
|
||||||
* pass (i.e., we assume the operators are strict).
|
* 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])
|
if (isnull[i])
|
||||||
return true;
|
return true;
|
||||||
@ -687,7 +687,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
|
|||||||
*/
|
*/
|
||||||
InitDirtySnapshot(DirtySnapshot);
|
InitDirtySnapshot(DirtySnapshot);
|
||||||
|
|
||||||
for (i = 0; i < index_natts; i++)
|
for (i = 0; i < indnkeyatts; i++)
|
||||||
{
|
{
|
||||||
ScanKeyEntryInitialize(&scankeys[i],
|
ScanKeyEntryInitialize(&scankeys[i],
|
||||||
0,
|
0,
|
||||||
@ -719,8 +719,8 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
|
|||||||
retry:
|
retry:
|
||||||
conflict = false;
|
conflict = false;
|
||||||
found_self = false;
|
found_self = false;
|
||||||
index_scan = index_beginscan(heap, index, &DirtySnapshot, index_natts, 0);
|
index_scan = index_beginscan(heap, index, &DirtySnapshot, indnkeyatts, 0);
|
||||||
index_rescan(index_scan, scankeys, index_natts, NULL, 0);
|
index_rescan(index_scan, scankeys, indnkeyatts, NULL, 0);
|
||||||
|
|
||||||
while ((tup = index_getnext(index_scan,
|
while ((tup = index_getnext(index_scan,
|
||||||
ForwardScanDirection)) != NULL)
|
ForwardScanDirection)) != NULL)
|
||||||
@ -881,10 +881,10 @@ index_recheck_constraint(Relation index, Oid *constr_procs,
|
|||||||
Datum *existing_values, bool *existing_isnull,
|
Datum *existing_values, bool *existing_isnull,
|
||||||
Datum *new_values)
|
Datum *new_values)
|
||||||
{
|
{
|
||||||
int index_natts = index->rd_index->indnatts;
|
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < index_natts; i++)
|
for (i = 0; i < indnkeyatts; i++)
|
||||||
{
|
{
|
||||||
/* Assume the exclusion operators are strict */
|
/* Assume the exclusion operators are strict */
|
||||||
if (existing_isnull[i])
|
if (existing_isnull[i])
|
||||||
|
@ -63,7 +63,7 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
|
|||||||
opclass = (oidvector *) DatumGetPointer(indclassDatum);
|
opclass = (oidvector *) DatumGetPointer(indclassDatum);
|
||||||
|
|
||||||
/* Build scankey for every attribute in the index. */
|
/* 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 operator;
|
||||||
Oid opfamily;
|
Oid opfamily;
|
||||||
@ -131,7 +131,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
|
|||||||
/* Start an index scan. */
|
/* Start an index scan. */
|
||||||
InitDirtySnapshot(snap);
|
InitDirtySnapshot(snap);
|
||||||
scan = index_beginscan(rel, idxrel, &snap,
|
scan = index_beginscan(rel, idxrel, &snap,
|
||||||
RelationGetNumberOfAttributes(idxrel),
|
IndexRelationGetNumberOfKeyAttributes(idxrel),
|
||||||
0);
|
0);
|
||||||
|
|
||||||
/* Build scan key. */
|
/* Build scan key. */
|
||||||
@ -140,7 +140,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
|
|||||||
retry:
|
retry:
|
||||||
found = false;
|
found = false;
|
||||||
|
|
||||||
index_rescan(scan, skey, RelationGetNumberOfAttributes(idxrel), NULL, 0);
|
index_rescan(scan, skey, IndexRelationGetNumberOfKeyAttributes(idxrel), NULL, 0);
|
||||||
|
|
||||||
/* Try to find the tuple */
|
/* Try to find the tuple */
|
||||||
if ((scantuple = index_getnext(scan, ForwardScanDirection)) != NULL)
|
if ((scantuple = index_getnext(scan, ForwardScanDirection)) != NULL)
|
||||||
|
@ -1227,7 +1227,9 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
|
|||||||
Expr *leftop; /* expr on lhs of operator */
|
Expr *leftop; /* expr on lhs of operator */
|
||||||
Expr *rightop; /* expr on rhs ... */
|
Expr *rightop; /* expr on rhs ... */
|
||||||
AttrNumber varattno; /* att number used in scan */
|
AttrNumber varattno; /* att number used in scan */
|
||||||
|
int indnkeyatts;
|
||||||
|
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
|
||||||
if (IsA(clause, OpExpr))
|
if (IsA(clause, OpExpr))
|
||||||
{
|
{
|
||||||
/* indexkey op const or indexkey op expression */
|
/* 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");
|
elog(ERROR, "indexqual doesn't have key on left side");
|
||||||
|
|
||||||
varattno = ((Var *) leftop)->varattno;
|
varattno = ((Var *) leftop)->varattno;
|
||||||
if (varattno < 1 || varattno > index->rd_index->indnatts)
|
if (varattno < 1 || varattno > indnkeyatts)
|
||||||
elog(ERROR, "bogus index qualification");
|
elog(ERROR, "bogus index qualification");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1375,7 +1377,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
|
|||||||
opnos_cell = lnext(opnos_cell);
|
opnos_cell = lnext(opnos_cell);
|
||||||
|
|
||||||
if (index->rd_rel->relam != BTREE_AM_OID ||
|
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");
|
elog(ERROR, "bogus RowCompare index qualification");
|
||||||
opfamily = index->rd_opfamily[varattno - 1];
|
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");
|
elog(ERROR, "indexqual doesn't have key on left side");
|
||||||
|
|
||||||
varattno = ((Var *) leftop)->varattno;
|
varattno = ((Var *) leftop)->varattno;
|
||||||
if (varattno < 1 || varattno > index->rd_index->indnatts)
|
if (varattno < 1 || varattno > indnkeyatts)
|
||||||
elog(ERROR, "bogus index qualification");
|
elog(ERROR, "bogus index qualification");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2889,6 +2889,7 @@ _copyConstraint(const Constraint *from)
|
|||||||
COPY_STRING_FIELD(cooked_expr);
|
COPY_STRING_FIELD(cooked_expr);
|
||||||
COPY_SCALAR_FIELD(generated_when);
|
COPY_SCALAR_FIELD(generated_when);
|
||||||
COPY_NODE_FIELD(keys);
|
COPY_NODE_FIELD(keys);
|
||||||
|
COPY_NODE_FIELD(including);
|
||||||
COPY_NODE_FIELD(exclusions);
|
COPY_NODE_FIELD(exclusions);
|
||||||
COPY_NODE_FIELD(options);
|
COPY_NODE_FIELD(options);
|
||||||
COPY_STRING_FIELD(indexname);
|
COPY_STRING_FIELD(indexname);
|
||||||
@ -3464,6 +3465,7 @@ _copyIndexStmt(const IndexStmt *from)
|
|||||||
COPY_STRING_FIELD(accessMethod);
|
COPY_STRING_FIELD(accessMethod);
|
||||||
COPY_STRING_FIELD(tableSpace);
|
COPY_STRING_FIELD(tableSpace);
|
||||||
COPY_NODE_FIELD(indexParams);
|
COPY_NODE_FIELD(indexParams);
|
||||||
|
COPY_NODE_FIELD(indexIncludingParams);
|
||||||
COPY_NODE_FIELD(options);
|
COPY_NODE_FIELD(options);
|
||||||
COPY_NODE_FIELD(whereClause);
|
COPY_NODE_FIELD(whereClause);
|
||||||
COPY_NODE_FIELD(excludeOpNames);
|
COPY_NODE_FIELD(excludeOpNames);
|
||||||
|
@ -1368,6 +1368,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
|
|||||||
COMPARE_STRING_FIELD(accessMethod);
|
COMPARE_STRING_FIELD(accessMethod);
|
||||||
COMPARE_STRING_FIELD(tableSpace);
|
COMPARE_STRING_FIELD(tableSpace);
|
||||||
COMPARE_NODE_FIELD(indexParams);
|
COMPARE_NODE_FIELD(indexParams);
|
||||||
|
COMPARE_NODE_FIELD(indexIncludingParams);
|
||||||
COMPARE_NODE_FIELD(options);
|
COMPARE_NODE_FIELD(options);
|
||||||
COMPARE_NODE_FIELD(whereClause);
|
COMPARE_NODE_FIELD(whereClause);
|
||||||
COMPARE_NODE_FIELD(excludeOpNames);
|
COMPARE_NODE_FIELD(excludeOpNames);
|
||||||
@ -2620,6 +2621,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
|
|||||||
COMPARE_STRING_FIELD(cooked_expr);
|
COMPARE_STRING_FIELD(cooked_expr);
|
||||||
COMPARE_SCALAR_FIELD(generated_when);
|
COMPARE_SCALAR_FIELD(generated_when);
|
||||||
COMPARE_NODE_FIELD(keys);
|
COMPARE_NODE_FIELD(keys);
|
||||||
|
COMPARE_NODE_FIELD(including);
|
||||||
COMPARE_NODE_FIELD(exclusions);
|
COMPARE_NODE_FIELD(exclusions);
|
||||||
COMPARE_NODE_FIELD(options);
|
COMPARE_NODE_FIELD(options);
|
||||||
COMPARE_STRING_FIELD(indexname);
|
COMPARE_STRING_FIELD(indexname);
|
||||||
|
@ -2707,6 +2707,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
|
|||||||
WRITE_STRING_FIELD(accessMethod);
|
WRITE_STRING_FIELD(accessMethod);
|
||||||
WRITE_STRING_FIELD(tableSpace);
|
WRITE_STRING_FIELD(tableSpace);
|
||||||
WRITE_NODE_FIELD(indexParams);
|
WRITE_NODE_FIELD(indexParams);
|
||||||
|
WRITE_NODE_FIELD(indexIncludingParams);
|
||||||
WRITE_NODE_FIELD(options);
|
WRITE_NODE_FIELD(options);
|
||||||
WRITE_NODE_FIELD(whereClause);
|
WRITE_NODE_FIELD(whereClause);
|
||||||
WRITE_NODE_FIELD(excludeOpNames);
|
WRITE_NODE_FIELD(excludeOpNames);
|
||||||
@ -3535,6 +3536,7 @@ _outConstraint(StringInfo str, const Constraint *node)
|
|||||||
case CONSTR_PRIMARY:
|
case CONSTR_PRIMARY:
|
||||||
appendStringInfoString(str, "PRIMARY_KEY");
|
appendStringInfoString(str, "PRIMARY_KEY");
|
||||||
WRITE_NODE_FIELD(keys);
|
WRITE_NODE_FIELD(keys);
|
||||||
|
WRITE_NODE_FIELD(including);
|
||||||
WRITE_NODE_FIELD(options);
|
WRITE_NODE_FIELD(options);
|
||||||
WRITE_STRING_FIELD(indexname);
|
WRITE_STRING_FIELD(indexname);
|
||||||
WRITE_STRING_FIELD(indexspace);
|
WRITE_STRING_FIELD(indexspace);
|
||||||
@ -3544,6 +3546,7 @@ _outConstraint(StringInfo str, const Constraint *node)
|
|||||||
case CONSTR_UNIQUE:
|
case CONSTR_UNIQUE:
|
||||||
appendStringInfoString(str, "UNIQUE");
|
appendStringInfoString(str, "UNIQUE");
|
||||||
WRITE_NODE_FIELD(keys);
|
WRITE_NODE_FIELD(keys);
|
||||||
|
WRITE_NODE_FIELD(including);
|
||||||
WRITE_NODE_FIELD(options);
|
WRITE_NODE_FIELD(options);
|
||||||
WRITE_STRING_FIELD(indexname);
|
WRITE_STRING_FIELD(indexname);
|
||||||
WRITE_STRING_FIELD(indexspace);
|
WRITE_STRING_FIELD(indexspace);
|
||||||
@ -3553,6 +3556,7 @@ _outConstraint(StringInfo str, const Constraint *node)
|
|||||||
case CONSTR_EXCLUSION:
|
case CONSTR_EXCLUSION:
|
||||||
appendStringInfoString(str, "EXCLUSION");
|
appendStringInfoString(str, "EXCLUSION");
|
||||||
WRITE_NODE_FIELD(exclusions);
|
WRITE_NODE_FIELD(exclusions);
|
||||||
|
WRITE_NODE_FIELD(including);
|
||||||
WRITE_NODE_FIELD(options);
|
WRITE_NODE_FIELD(options);
|
||||||
WRITE_STRING_FIELD(indexname);
|
WRITE_STRING_FIELD(indexname);
|
||||||
WRITE_STRING_FIELD(indexspace);
|
WRITE_STRING_FIELD(indexspace);
|
||||||
|
@ -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.
|
Sequential scan Paths have NIL pathkeys, indicating no known ordering.
|
||||||
Index scans have Path.pathkeys that represent the chosen index's 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
|
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.
|
multi-column index generates a list with one element per key index column.
|
||||||
(Actually, since an index can be scanned either forward or backward, there
|
Non-key columns specified in the INCLUDE clause of covering indexes don't
|
||||||
are two possible sort orders and two possible PathKey lists it can
|
have corresponding PathKeys in the list, because the have no influence on
|
||||||
generate.)
|
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
|
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
|
the overall order of its result. Also, an indexscan on an unordered type
|
||||||
|
@ -2162,7 +2162,7 @@ match_eclass_clauses_to_index(PlannerInfo *root, IndexOptInfo *index,
|
|||||||
if (!index->rel->has_eclass_joins)
|
if (!index->rel->has_eclass_joins)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
|
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
|
||||||
{
|
{
|
||||||
ec_member_matches_arg arg;
|
ec_member_matches_arg arg;
|
||||||
List *clauses;
|
List *clauses;
|
||||||
|
@ -447,8 +447,10 @@ get_cheapest_parallel_safe_total_inner(List *paths)
|
|||||||
* If 'scandir' is BackwardScanDirection, build pathkeys representing a
|
* If 'scandir' is BackwardScanDirection, build pathkeys representing a
|
||||||
* backwards scan of the index.
|
* backwards scan of the index.
|
||||||
*
|
*
|
||||||
* The result is canonical, meaning that redundant pathkeys are removed;
|
* We iterate only key columns of covering indexes, since non-key columns
|
||||||
* it may therefore have fewer entries than there are index 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
|
* 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,
|
* an index column's sort order is uninteresting for this query. However,
|
||||||
@ -477,6 +479,13 @@ build_index_pathkeys(PlannerInfo *root,
|
|||||||
bool nulls_first;
|
bool nulls_first;
|
||||||
PathKey *cpathkey;
|
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 */
|
/* We assume we don't need to make a copy of the tlist item */
|
||||||
indexkey = indextle->expr;
|
indexkey = indextle->expr;
|
||||||
|
|
||||||
|
@ -185,7 +185,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
|||||||
Form_pg_index index;
|
Form_pg_index index;
|
||||||
IndexAmRoutine *amroutine;
|
IndexAmRoutine *amroutine;
|
||||||
IndexOptInfo *info;
|
IndexOptInfo *info;
|
||||||
int ncolumns;
|
int ncolumns,
|
||||||
|
nkeycolumns;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -238,19 +239,25 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
|||||||
RelationGetForm(indexRelation)->reltablespace;
|
RelationGetForm(indexRelation)->reltablespace;
|
||||||
info->rel = rel;
|
info->rel = rel;
|
||||||
info->ncolumns = ncolumns = index->indnatts;
|
info->ncolumns = ncolumns = index->indnatts;
|
||||||
|
info->nkeycolumns = nkeycolumns = index->indnkeyatts;
|
||||||
|
|
||||||
info->indexkeys = (int *) palloc(sizeof(int) * ncolumns);
|
info->indexkeys = (int *) palloc(sizeof(int) * ncolumns);
|
||||||
info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns);
|
info->indexcollations = (Oid *) palloc(sizeof(Oid) * ncolumns);
|
||||||
info->opfamily = (Oid *) palloc(sizeof(Oid) * ncolumns);
|
info->opfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
|
||||||
info->opcintype = (Oid *) palloc(sizeof(Oid) * ncolumns);
|
info->opcintype = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
|
||||||
info->canreturn = (bool *) palloc(sizeof(bool) * ncolumns);
|
info->canreturn = (bool *) palloc(sizeof(bool) * ncolumns);
|
||||||
|
|
||||||
for (i = 0; i < ncolumns; i++)
|
for (i = 0; i < ncolumns; i++)
|
||||||
{
|
{
|
||||||
info->indexkeys[i] = index->indkey.values[i];
|
info->indexkeys[i] = index->indkey.values[i];
|
||||||
info->indexcollations[i] = indexRelation->rd_indcollation[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->opfamily[i] = indexRelation->rd_opfamily[i];
|
||||||
info->opcintype[i] = indexRelation->rd_opcintype[i];
|
info->opcintype[i] = indexRelation->rd_opcintype[i];
|
||||||
info->canreturn[i] = index_can_return(indexRelation, i + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info->relam = indexRelation->rd_rel->relam;
|
info->relam = indexRelation->rd_rel->relam;
|
||||||
@ -279,10 +286,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
|||||||
Assert(amroutine->amcanorder);
|
Assert(amroutine->amcanorder);
|
||||||
|
|
||||||
info->sortopfamily = info->opfamily;
|
info->sortopfamily = info->opfamily;
|
||||||
info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns);
|
info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns);
|
||||||
info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns);
|
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];
|
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
|
* of current or foreseeable amcanorder index types, it's not
|
||||||
* worth expending more effort on now.
|
* worth expending more effort on now.
|
||||||
*/
|
*/
|
||||||
info->sortopfamily = (Oid *) palloc(sizeof(Oid) * ncolumns);
|
info->sortopfamily = (Oid *) palloc(sizeof(Oid) * nkeycolumns);
|
||||||
info->reverse_sort = (bool *) palloc(sizeof(bool) * ncolumns);
|
info->reverse_sort = (bool *) palloc(sizeof(bool) * nkeycolumns);
|
||||||
info->nulls_first = (bool *) palloc(sizeof(bool) * ncolumns);
|
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];
|
int16 opt = indexRelation->rd_indoption[i];
|
||||||
Oid ltopr;
|
Oid ltopr;
|
||||||
@ -731,7 +738,7 @@ infer_arbiter_indexes(PlannerInfo *root)
|
|||||||
|
|
||||||
/* Build BMS representation of plain (non expression) index attrs */
|
/* Build BMS representation of plain (non expression) index attrs */
|
||||||
indexedAttrs = NULL;
|
indexedAttrs = NULL;
|
||||||
for (natt = 0; natt < idxForm->indnatts; natt++)
|
for (natt = 0; natt < idxForm->indnkeyatts; natt++)
|
||||||
{
|
{
|
||||||
int attno = idxRel->rd_index->indkey.values[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.
|
* just the specified attr is unique.
|
||||||
*/
|
*/
|
||||||
if (index->unique &&
|
if (index->unique &&
|
||||||
index->ncolumns == 1 &&
|
index->nkeycolumns == 1 &&
|
||||||
index->indexkeys[0] == attno &&
|
index->indexkeys[0] == attno &&
|
||||||
(index->indpred == NIL || index->predOK))
|
(index->indpred == NIL || index->predOK))
|
||||||
return true;
|
return true;
|
||||||
|
@ -1051,7 +1051,7 @@ transformOnConflictClause(ParseState *pstate,
|
|||||||
* relation. Have to be careful to use resnos that correspond to
|
* relation. Have to be careful to use resnos that correspond to
|
||||||
* attnos of the underlying relation.
|
* 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);
|
Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
|
||||||
char *name;
|
char *name;
|
||||||
@ -2276,8 +2276,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
|
|||||||
EXPR_KIND_UPDATE_SOURCE);
|
EXPR_KIND_UPDATE_SOURCE);
|
||||||
|
|
||||||
/* Prepare to assign non-conflicting resnos to resjunk attributes */
|
/* Prepare to assign non-conflicting resnos to resjunk attributes */
|
||||||
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
|
if (pstate->p_next_resno <= RelationGetNumberOfAttributes(pstate->p_target_relation))
|
||||||
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
|
pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
|
||||||
|
|
||||||
/* Prepare non-junk columns for assignment to target table */
|
/* Prepare non-junk columns for assignment to target table */
|
||||||
target_rte = pstate->p_target_rangetblentry;
|
target_rte = pstate->p_target_rangetblentry;
|
||||||
|
@ -382,6 +382,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
|||||||
oper_argtypes RuleActionList RuleActionMulti
|
oper_argtypes RuleActionList RuleActionMulti
|
||||||
opt_column_list columnList opt_name_list
|
opt_column_list columnList opt_name_list
|
||||||
sort_clause opt_sort_clause sortby_list index_params
|
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
|
name_list role_list from_clause from_list opt_array_bounds
|
||||||
qualified_name_list any_name any_name_list type_name_list
|
qualified_name_list any_name any_name_list type_name_list
|
||||||
any_operator expr_list attrs
|
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
|
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
|
INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
|
||||||
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
|
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
|
||||||
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
|
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
|
||||||
@ -3686,17 +3687,18 @@ ConstraintElem:
|
|||||||
n->initially_valid = !n->skip_validation;
|
n->initially_valid = !n->skip_validation;
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
|
| UNIQUE '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
|
||||||
ConstraintAttributeSpec
|
ConstraintAttributeSpec
|
||||||
{
|
{
|
||||||
Constraint *n = makeNode(Constraint);
|
Constraint *n = makeNode(Constraint);
|
||||||
n->contype = CONSTR_UNIQUE;
|
n->contype = CONSTR_UNIQUE;
|
||||||
n->location = @1;
|
n->location = @1;
|
||||||
n->keys = $3;
|
n->keys = $3;
|
||||||
n->options = $5;
|
n->including = $5;
|
||||||
|
n->options = $6;
|
||||||
n->indexname = NULL;
|
n->indexname = NULL;
|
||||||
n->indexspace = $6;
|
n->indexspace = $7;
|
||||||
processCASbits($7, @7, "UNIQUE",
|
processCASbits($8, @8, "UNIQUE",
|
||||||
&n->deferrable, &n->initdeferred, NULL,
|
&n->deferrable, &n->initdeferred, NULL,
|
||||||
NULL, yyscanner);
|
NULL, yyscanner);
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
@ -3707,6 +3709,7 @@ ConstraintElem:
|
|||||||
n->contype = CONSTR_UNIQUE;
|
n->contype = CONSTR_UNIQUE;
|
||||||
n->location = @1;
|
n->location = @1;
|
||||||
n->keys = NIL;
|
n->keys = NIL;
|
||||||
|
n->including = NIL;
|
||||||
n->options = NIL;
|
n->options = NIL;
|
||||||
n->indexname = $2;
|
n->indexname = $2;
|
||||||
n->indexspace = NULL;
|
n->indexspace = NULL;
|
||||||
@ -3715,17 +3718,18 @@ ConstraintElem:
|
|||||||
NULL, yyscanner);
|
NULL, yyscanner);
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
|
| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
|
||||||
ConstraintAttributeSpec
|
ConstraintAttributeSpec
|
||||||
{
|
{
|
||||||
Constraint *n = makeNode(Constraint);
|
Constraint *n = makeNode(Constraint);
|
||||||
n->contype = CONSTR_PRIMARY;
|
n->contype = CONSTR_PRIMARY;
|
||||||
n->location = @1;
|
n->location = @1;
|
||||||
n->keys = $4;
|
n->keys = $4;
|
||||||
n->options = $6;
|
n->including = $6;
|
||||||
|
n->options = $7;
|
||||||
n->indexname = NULL;
|
n->indexname = NULL;
|
||||||
n->indexspace = $7;
|
n->indexspace = $8;
|
||||||
processCASbits($8, @8, "PRIMARY KEY",
|
processCASbits($9, @9, "PRIMARY KEY",
|
||||||
&n->deferrable, &n->initdeferred, NULL,
|
&n->deferrable, &n->initdeferred, NULL,
|
||||||
NULL, yyscanner);
|
NULL, yyscanner);
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
@ -3736,6 +3740,7 @@ ConstraintElem:
|
|||||||
n->contype = CONSTR_PRIMARY;
|
n->contype = CONSTR_PRIMARY;
|
||||||
n->location = @1;
|
n->location = @1;
|
||||||
n->keys = NIL;
|
n->keys = NIL;
|
||||||
|
n->including = NIL;
|
||||||
n->options = NIL;
|
n->options = NIL;
|
||||||
n->indexname = $3;
|
n->indexname = $3;
|
||||||
n->indexspace = NULL;
|
n->indexspace = NULL;
|
||||||
@ -3745,7 +3750,7 @@ ConstraintElem:
|
|||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
}
|
}
|
||||||
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
|
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
|
||||||
opt_definition OptConsTableSpace ExclusionWhereClause
|
opt_c_include opt_definition OptConsTableSpace ExclusionWhereClause
|
||||||
ConstraintAttributeSpec
|
ConstraintAttributeSpec
|
||||||
{
|
{
|
||||||
Constraint *n = makeNode(Constraint);
|
Constraint *n = makeNode(Constraint);
|
||||||
@ -3753,11 +3758,12 @@ ConstraintElem:
|
|||||||
n->location = @1;
|
n->location = @1;
|
||||||
n->access_method = $2;
|
n->access_method = $2;
|
||||||
n->exclusions = $4;
|
n->exclusions = $4;
|
||||||
n->options = $6;
|
n->including = $6;
|
||||||
|
n->options = $7;
|
||||||
n->indexname = NULL;
|
n->indexname = NULL;
|
||||||
n->indexspace = $7;
|
n->indexspace = $8;
|
||||||
n->where_clause = $8;
|
n->where_clause = $9;
|
||||||
processCASbits($9, @9, "EXCLUDE",
|
processCASbits($10, @10, "EXCLUDE",
|
||||||
&n->deferrable, &n->initdeferred, NULL,
|
&n->deferrable, &n->initdeferred, NULL,
|
||||||
NULL, yyscanner);
|
NULL, yyscanner);
|
||||||
$$ = (Node *)n;
|
$$ = (Node *)n;
|
||||||
@ -3803,6 +3809,10 @@ columnElem: ColId
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
opt_c_include: INCLUDE '(' columnList ')' { $$ = $3; }
|
||||||
|
| /* EMPTY */ { $$ = NIL; }
|
||||||
|
;
|
||||||
|
|
||||||
key_match: MATCH FULL
|
key_match: MATCH FULL
|
||||||
{
|
{
|
||||||
$$ = FKCONSTR_MATCH_FULL;
|
$$ = FKCONSTR_MATCH_FULL;
|
||||||
@ -7373,7 +7383,7 @@ defacl_privilege_target:
|
|||||||
|
|
||||||
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
||||||
ON relation_expr access_method_clause '(' index_params ')'
|
ON relation_expr access_method_clause '(' index_params ')'
|
||||||
opt_reloptions OptTableSpace where_clause
|
opt_include opt_reloptions OptTableSpace where_clause
|
||||||
{
|
{
|
||||||
IndexStmt *n = makeNode(IndexStmt);
|
IndexStmt *n = makeNode(IndexStmt);
|
||||||
n->unique = $2;
|
n->unique = $2;
|
||||||
@ -7383,9 +7393,10 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
|||||||
n->relationId = InvalidOid;
|
n->relationId = InvalidOid;
|
||||||
n->accessMethod = $8;
|
n->accessMethod = $8;
|
||||||
n->indexParams = $10;
|
n->indexParams = $10;
|
||||||
n->options = $12;
|
n->indexIncludingParams = $12;
|
||||||
n->tableSpace = $13;
|
n->options = $13;
|
||||||
n->whereClause = $14;
|
n->tableSpace = $14;
|
||||||
|
n->whereClause = $15;
|
||||||
n->excludeOpNames = NIL;
|
n->excludeOpNames = NIL;
|
||||||
n->idxcomment = NULL;
|
n->idxcomment = NULL;
|
||||||
n->indexOid = InvalidOid;
|
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
|
| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
|
||||||
ON relation_expr access_method_clause '(' index_params ')'
|
ON relation_expr access_method_clause '(' index_params ')'
|
||||||
opt_reloptions OptTableSpace where_clause
|
opt_include opt_reloptions OptTableSpace where_clause
|
||||||
{
|
{
|
||||||
IndexStmt *n = makeNode(IndexStmt);
|
IndexStmt *n = makeNode(IndexStmt);
|
||||||
n->unique = $2;
|
n->unique = $2;
|
||||||
@ -7410,9 +7421,10 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
|||||||
n->relationId = InvalidOid;
|
n->relationId = InvalidOid;
|
||||||
n->accessMethod = $11;
|
n->accessMethod = $11;
|
||||||
n->indexParams = $13;
|
n->indexParams = $13;
|
||||||
n->options = $15;
|
n->indexIncludingParams = $15;
|
||||||
n->tableSpace = $16;
|
n->options = $16;
|
||||||
n->whereClause = $17;
|
n->tableSpace = $17;
|
||||||
|
n->whereClause = $18;
|
||||||
n->excludeOpNames = NIL;
|
n->excludeOpNames = NIL;
|
||||||
n->idxcomment = NULL;
|
n->idxcomment = NULL;
|
||||||
n->indexOid = InvalidOid;
|
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; }
|
opt_collate: COLLATE any_name { $$ = $2; }
|
||||||
| /*EMPTY*/ { $$ = NIL; }
|
| /*EMPTY*/ { $$ = NIL; }
|
||||||
;
|
;
|
||||||
@ -15206,6 +15226,7 @@ unreserved_keyword:
|
|||||||
| IMMUTABLE
|
| IMMUTABLE
|
||||||
| IMPLICIT_P
|
| IMPLICIT_P
|
||||||
| IMPORT_P
|
| IMPORT_P
|
||||||
|
| INCLUDE
|
||||||
| INCLUDING
|
| INCLUDING
|
||||||
| INCREMENT
|
| INCREMENT
|
||||||
| INDEX
|
| INDEX
|
||||||
|
@ -3089,7 +3089,7 @@ attnameAttNum(Relation rd, const char *attname, bool sysColOK)
|
|||||||
{
|
{
|
||||||
int i;
|
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);
|
Form_pg_attribute att = TupleDescAttr(rd->rd_att, i);
|
||||||
|
|
||||||
|
@ -978,7 +978,8 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
|
|||||||
/*
|
/*
|
||||||
* Generate default column list for INSERT.
|
* Generate default column list for INSERT.
|
||||||
*/
|
*/
|
||||||
int numcol = pstate->p_target_relation->rd_rel->relnatts;
|
int numcol = RelationGetNumberOfAttributes(pstate->p_target_relation);
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < numcol; i++)
|
for (i = 0; i < numcol; i++)
|
||||||
|
@ -1468,9 +1468,10 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
|
|||||||
|
|
||||||
/* Build the list of IndexElem */
|
/* Build the list of IndexElem */
|
||||||
index->indexParams = NIL;
|
index->indexParams = NIL;
|
||||||
|
index->indexIncludingParams = NIL;
|
||||||
|
|
||||||
indexpr_item = list_head(indexprs);
|
indexpr_item = list_head(indexprs);
|
||||||
for (keyno = 0; keyno < idxrec->indnatts; keyno++)
|
for (keyno = 0; keyno < idxrec->indnkeyatts; keyno++)
|
||||||
{
|
{
|
||||||
IndexElem *iparam;
|
IndexElem *iparam;
|
||||||
AttrNumber attnum = idxrec->indkey.values[keyno];
|
AttrNumber attnum = idxrec->indkey.values[keyno];
|
||||||
@ -1559,6 +1560,40 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
|
|||||||
index->indexParams = lappend(index->indexParams, iparam);
|
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 */
|
/* Copy reloptions if any */
|
||||||
datum = SysCacheGetAttr(RELOID, ht_idxrel,
|
datum = SysCacheGetAttr(RELOID, ht_idxrel,
|
||||||
Anum_pg_class_reloptions, &isnull);
|
Anum_pg_class_reloptions, &isnull);
|
||||||
@ -1829,6 +1864,7 @@ transformIndexConstraints(CreateStmtContext *cxt)
|
|||||||
IndexStmt *priorindex = lfirst(k);
|
IndexStmt *priorindex = lfirst(k);
|
||||||
|
|
||||||
if (equal(index->indexParams, priorindex->indexParams) &&
|
if (equal(index->indexParams, priorindex->indexParams) &&
|
||||||
|
equal(index->indexIncludingParams, priorindex->indexIncludingParams) &&
|
||||||
equal(index->whereClause, priorindex->whereClause) &&
|
equal(index->whereClause, priorindex->whereClause) &&
|
||||||
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
|
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
|
||||||
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
|
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
|
||||||
@ -1900,6 +1936,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
|||||||
index->tableSpace = constraint->indexspace;
|
index->tableSpace = constraint->indexspace;
|
||||||
index->whereClause = constraint->where_clause;
|
index->whereClause = constraint->where_clause;
|
||||||
index->indexParams = NIL;
|
index->indexParams = NIL;
|
||||||
|
index->indexIncludingParams = NIL;
|
||||||
index->excludeOpNames = NIL;
|
index->excludeOpNames = NIL;
|
||||||
index->idxcomment = NULL;
|
index->idxcomment = NULL;
|
||||||
index->indexOid = InvalidOid;
|
index->indexOid = InvalidOid;
|
||||||
@ -2049,24 +2086,29 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
|||||||
heap_rel->rd_rel->relhasoids);
|
heap_rel->rd_rel->relhasoids);
|
||||||
attname = pstrdup(NameStr(attform->attname));
|
attname = pstrdup(NameStr(attform->attname));
|
||||||
|
|
||||||
/*
|
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
|
* Insist on default opclass and sort options. While the
|
||||||
* you'd get from a normally-created constraint; and there's also
|
* index would still work as a constraint with non-default
|
||||||
* the dump/reload problem mentioned above.
|
* settings, it might not provide exactly the same uniqueness
|
||||||
*/
|
* semantics as you'd get from a normally-created constraint;
|
||||||
defopclass = GetDefaultOpClass(attform->atttypid,
|
* and there's also the dump/reload problem mentioned above.
|
||||||
index_rel->rd_rel->relam);
|
*/
|
||||||
if (indclass->values[i] != defopclass ||
|
defopclass = GetDefaultOpClass(attform->atttypid,
|
||||||
index_rel->rd_indoption[i] != 0)
|
index_rel->rd_rel->relam);
|
||||||
ereport(ERROR,
|
if (indclass->values[i] != defopclass ||
|
||||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
index_rel->rd_indoption[i] != 0)
|
||||||
errmsg("index \"%s\" does not have default sorting behavior", index_name),
|
ereport(ERROR,
|
||||||
errdetail("Cannot create a primary key or unique constraint using such an index."),
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||||
parser_errposition(cxt->pstate, constraint->location)));
|
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 */
|
/* Close the index relation but keep the lock */
|
||||||
@ -2095,8 +2137,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
|||||||
index->indexParams = lappend(index->indexParams, elem);
|
index->indexParams = lappend(index->indexParams, elem);
|
||||||
index->excludeOpNames = lappend(index->excludeOpNames, opname);
|
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
|
* it to DefineIndex to mark the columns NOT NULL, it's more efficient to
|
||||||
* get it right the first time.)
|
* 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));
|
char *key = strVal(lfirst(lc));
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@ -2124,65 +2293,63 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
|||||||
break;
|
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;
|
* column will be a system column in the new table, so accept
|
||||||
int count;
|
* 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;
|
||||||
|
|
||||||
rel = heap_openrv(inh, AccessShareLock);
|
foreach(inher, cxt->inhRelations)
|
||||||
/* 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,
|
RangeVar *inh = lfirst_node(RangeVar, inher);
|
||||||
count);
|
Relation rel;
|
||||||
char *inhname = NameStr(inhattr->attname);
|
int count;
|
||||||
|
|
||||||
if (inhattr->attisdropped)
|
rel = heap_openrv(inh, AccessShareLock);
|
||||||
continue;
|
/* check user requested inheritance from valid relkind */
|
||||||
if (strcmp(key, inhname) == 0)
|
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;
|
Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
|
||||||
|
count);
|
||||||
|
char *inhname = NameStr(inhattr->attname);
|
||||||
|
|
||||||
/*
|
if (inhattr->attisdropped)
|
||||||
* We currently have no easy way to force an inherited
|
continue;
|
||||||
* column to be NOT NULL at creation, if its parent
|
if (strcmp(key, inhname) == 0)
|
||||||
* wasn't so already. We leave it to DefineIndex to
|
{
|
||||||
* fix things up in this case.
|
found = true;
|
||||||
*/
|
|
||||||
break;
|
/*
|
||||||
|
* 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),
|
errmsg("column \"%s\" named in key does not exist", key),
|
||||||
parser_errposition(cxt->pstate, constraint->location)));
|
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 */
|
/* OK, add it to the index definition */
|
||||||
iparam = makeNode(IndexElem);
|
iparam = makeNode(IndexElem);
|
||||||
iparam->name = pstrdup(key);
|
iparam->name = pstrdup(key);
|
||||||
@ -2226,9 +2372,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
|||||||
iparam->indexcolname = NULL;
|
iparam->indexcolname = NULL;
|
||||||
iparam->collation = NIL;
|
iparam->collation = NIL;
|
||||||
iparam->opclass = NIL;
|
iparam->opclass = NIL;
|
||||||
iparam->ordering = SORTBY_DEFAULT;
|
index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
|
||||||
iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
|
|
||||||
index->indexParams = lappend(index->indexParams, iparam);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
|
@ -1296,6 +1296,21 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
|
|||||||
Oid keycoltype;
|
Oid keycoltype;
|
||||||
Oid keycolcollation;
|
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)
|
if (!colno)
|
||||||
appendStringInfoString(&buf, sep);
|
appendStringInfoString(&buf, sep);
|
||||||
sep = ", ";
|
sep = ", ";
|
||||||
@ -1347,6 +1362,9 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
|
|||||||
appendStringInfo(&buf, " COLLATE %s",
|
appendStringInfo(&buf, " COLLATE %s",
|
||||||
generate_collation_name((indcoll)));
|
generate_collation_name((indcoll)));
|
||||||
|
|
||||||
|
if (keyno >= idxrec->indnkeyatts)
|
||||||
|
continue;
|
||||||
|
|
||||||
/* Add the operator class name, if not default */
|
/* Add the operator class name, if not default */
|
||||||
get_opclass_name(indclass->values[keyno], keycoltype, &buf);
|
get_opclass_name(indclass->values[keyno], keycoltype, &buf);
|
||||||
|
|
||||||
@ -2047,6 +2065,19 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
|
|||||||
|
|
||||||
appendStringInfoChar(&buf, ')');
|
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);
|
indexId = get_constraint_index(constraintId);
|
||||||
|
|
||||||
/* XXX why do we only print these bits if fullCommand? */
|
/* XXX why do we only print these bits if fullCommand? */
|
||||||
|
@ -4902,7 +4902,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
|
|||||||
* should match has_unique_index().
|
* should match has_unique_index().
|
||||||
*/
|
*/
|
||||||
if (index->unique &&
|
if (index->unique &&
|
||||||
index->ncolumns == 1 &&
|
index->nkeycolumns == 1 &&
|
||||||
(index->indpred == NIL || index->predOK))
|
(index->indpred == NIL || index->predOK))
|
||||||
vardata->isunique = true;
|
vardata->isunique = true;
|
||||||
|
|
||||||
@ -7053,7 +7053,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
|||||||
* NullTest invalidates that theory, even though it sets eqQualHere.
|
* NullTest invalidates that theory, even though it sets eqQualHere.
|
||||||
*/
|
*/
|
||||||
if (index->unique &&
|
if (index->unique &&
|
||||||
indexcol == index->ncolumns - 1 &&
|
indexcol == index->nkeycolumns - 1 &&
|
||||||
eqQualHere &&
|
eqQualHere &&
|
||||||
!found_saop &&
|
!found_saop &&
|
||||||
!found_is_null_op)
|
!found_is_null_op)
|
||||||
|
87
src/backend/utils/cache/relcache.c
vendored
87
src/backend/utils/cache/relcache.c
vendored
@ -538,7 +538,7 @@ RelationBuildTupleDesc(Relation relation)
|
|||||||
/*
|
/*
|
||||||
* add attribute data to relation->rd_att
|
* 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)))
|
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);
|
attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple);
|
||||||
|
|
||||||
attnum = attp->attnum;
|
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",
|
elog(ERROR, "invalid attribute number %d for %s",
|
||||||
attp->attnum, RelationGetRelationName(relation));
|
attp->attnum, RelationGetRelationName(relation));
|
||||||
|
|
||||||
@ -567,7 +567,7 @@ RelationBuildTupleDesc(Relation relation)
|
|||||||
if (attrdef == NULL)
|
if (attrdef == NULL)
|
||||||
attrdef = (AttrDefault *)
|
attrdef = (AttrDefault *)
|
||||||
MemoryContextAllocZero(CacheMemoryContext,
|
MemoryContextAllocZero(CacheMemoryContext,
|
||||||
relation->rd_rel->relnatts *
|
RelationGetNumberOfAttributes(relation) *
|
||||||
sizeof(AttrDefault));
|
sizeof(AttrDefault));
|
||||||
attrdef[ndef].adnum = attnum;
|
attrdef[ndef].adnum = attnum;
|
||||||
attrdef[ndef].adbin = NULL;
|
attrdef[ndef].adbin = NULL;
|
||||||
@ -650,7 +650,7 @@ RelationBuildTupleDesc(Relation relation)
|
|||||||
{
|
{
|
||||||
int i;
|
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);
|
Assert(TupleDescAttr(relation->rd_att, i)->attcacheoff == -1);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -660,7 +660,7 @@ RelationBuildTupleDesc(Relation relation)
|
|||||||
* attribute: it must be zero. This eliminates the need for special cases
|
* attribute: it must be zero. This eliminates the need for special cases
|
||||||
* for attnum=1 that used to exist in fastgetattr() and index_getattr().
|
* 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;
|
TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -673,7 +673,7 @@ RelationBuildTupleDesc(Relation relation)
|
|||||||
|
|
||||||
if (ndef > 0) /* DEFAULTs */
|
if (ndef > 0) /* DEFAULTs */
|
||||||
{
|
{
|
||||||
if (ndef < relation->rd_rel->relnatts)
|
if (ndef < RelationGetNumberOfAttributes(relation))
|
||||||
constr->defval = (AttrDefault *)
|
constr->defval = (AttrDefault *)
|
||||||
repalloc(attrdef, ndef * sizeof(AttrDefault));
|
repalloc(attrdef, ndef * sizeof(AttrDefault));
|
||||||
else
|
else
|
||||||
@ -1557,7 +1557,8 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
int2vector *indoption;
|
int2vector *indoption;
|
||||||
MemoryContext indexcxt;
|
MemoryContext indexcxt;
|
||||||
MemoryContext oldcontext;
|
MemoryContext oldcontext;
|
||||||
int natts;
|
int indnatts;
|
||||||
|
int indnkeyatts;
|
||||||
uint16 amsupport;
|
uint16 amsupport;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1587,10 +1588,11 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
relation->rd_amhandler = aform->amhandler;
|
relation->rd_amhandler = aform->amhandler;
|
||||||
ReleaseSysCache(tuple);
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
natts = relation->rd_rel->relnatts;
|
indnatts = RelationGetNumberOfAttributes(relation);
|
||||||
if (natts != relation->rd_index->indnatts)
|
if (indnatts != IndexRelationGetNumberOfAttributes(relation))
|
||||||
elog(ERROR, "relnatts disagrees with indnatts for index %u",
|
elog(ERROR, "relnatts disagrees with indnatts for index %u",
|
||||||
RelationGetRelid(relation));
|
RelationGetRelid(relation));
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(relation);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make the private context to hold index access info. The reason we need
|
* Make the private context to hold index access info. The reason we need
|
||||||
@ -1610,17 +1612,18 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
InitIndexAmRoutine(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 *)
|
relation->rd_opfamily = (Oid *)
|
||||||
MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
|
MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
|
||||||
relation->rd_opcintype = (Oid *)
|
relation->rd_opcintype = (Oid *)
|
||||||
MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
|
MemoryContextAllocZero(indexcxt, indnkeyatts * sizeof(Oid));
|
||||||
|
|
||||||
amsupport = relation->rd_amroutine->amsupport;
|
amsupport = relation->rd_amroutine->amsupport;
|
||||||
if (amsupport > 0)
|
if (amsupport > 0)
|
||||||
{
|
{
|
||||||
int nsupport = natts * amsupport;
|
int nsupport = indnatts * amsupport;
|
||||||
|
|
||||||
relation->rd_support = (RegProcedure *)
|
relation->rd_support = (RegProcedure *)
|
||||||
MemoryContextAllocZero(indexcxt, nsupport * sizeof(RegProcedure));
|
MemoryContextAllocZero(indexcxt, nsupport * sizeof(RegProcedure));
|
||||||
@ -1634,10 +1637,10 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
}
|
}
|
||||||
|
|
||||||
relation->rd_indcollation = (Oid *)
|
relation->rd_indcollation = (Oid *)
|
||||||
MemoryContextAllocZero(indexcxt, natts * sizeof(Oid));
|
MemoryContextAllocZero(indexcxt, indnatts * sizeof(Oid));
|
||||||
|
|
||||||
relation->rd_indoption = (int16 *)
|
relation->rd_indoption = (int16 *)
|
||||||
MemoryContextAllocZero(indexcxt, natts * sizeof(int16));
|
MemoryContextAllocZero(indexcxt, indnatts * sizeof(int16));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* indcollation cannot be referenced directly through the C struct,
|
* indcollation cannot be referenced directly through the C struct,
|
||||||
@ -1650,7 +1653,7 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
&isnull);
|
&isnull);
|
||||||
Assert(!isnull);
|
Assert(!isnull);
|
||||||
indcoll = (oidvector *) DatumGetPointer(indcollDatum);
|
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
|
* indclass cannot be referenced directly through the C struct, because it
|
||||||
@ -1671,7 +1674,7 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
*/
|
*/
|
||||||
IndexSupportInitialize(indclass, relation->rd_support,
|
IndexSupportInitialize(indclass, relation->rd_support,
|
||||||
relation->rd_opfamily, relation->rd_opcintype,
|
relation->rd_opfamily, relation->rd_opcintype,
|
||||||
amsupport, natts);
|
amsupport, indnkeyatts);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Similarly extract indoption and copy it to the cache entry
|
* Similarly extract indoption and copy it to the cache entry
|
||||||
@ -1682,7 +1685,7 @@ RelationInitIndexAccessInfo(Relation relation)
|
|||||||
&isnull);
|
&isnull);
|
||||||
Assert(!isnull);
|
Assert(!isnull);
|
||||||
indoption = (int2vector *) DatumGetPointer(indoptionDatum);
|
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
|
* expressions, predicate, exclusion caches will be filled later
|
||||||
@ -5064,20 +5067,28 @@ restart:
|
|||||||
{
|
{
|
||||||
int attrnum = indexInfo->ii_KeyAttrNumbers[i];
|
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)
|
if (attrnum != 0)
|
||||||
{
|
{
|
||||||
indexattrs = bms_add_member(indexattrs,
|
indexattrs = bms_add_member(indexattrs,
|
||||||
attrnum - FirstLowInvalidHeapAttributeNumber);
|
attrnum - FirstLowInvalidHeapAttributeNumber);
|
||||||
|
|
||||||
if (isKey)
|
if (isKey && i < indexInfo->ii_NumIndexKeyAttrs)
|
||||||
uindexattrs = bms_add_member(uindexattrs,
|
uindexattrs = bms_add_member(uindexattrs,
|
||||||
attrnum - FirstLowInvalidHeapAttributeNumber);
|
attrnum - FirstLowInvalidHeapAttributeNumber);
|
||||||
|
|
||||||
if (isPK)
|
if (isPK && i < indexInfo->ii_NumIndexKeyAttrs)
|
||||||
pkindexattrs = bms_add_member(pkindexattrs,
|
pkindexattrs = bms_add_member(pkindexattrs,
|
||||||
attrnum - FirstLowInvalidHeapAttributeNumber);
|
attrnum - FirstLowInvalidHeapAttributeNumber);
|
||||||
|
|
||||||
if (isIDKey)
|
if (isIDKey && i < indexInfo->ii_NumIndexKeyAttrs)
|
||||||
idindexattrs = bms_add_member(idindexattrs,
|
idindexattrs = bms_add_member(idindexattrs,
|
||||||
attrnum - FirstLowInvalidHeapAttributeNumber);
|
attrnum - FirstLowInvalidHeapAttributeNumber);
|
||||||
}
|
}
|
||||||
@ -5195,7 +5206,7 @@ RelationGetExclusionInfo(Relation indexRelation,
|
|||||||
Oid **procs,
|
Oid **procs,
|
||||||
uint16 **strategies)
|
uint16 **strategies)
|
||||||
{
|
{
|
||||||
int ncols = indexRelation->rd_rel->relnatts;
|
int indnkeyatts;
|
||||||
Oid *ops;
|
Oid *ops;
|
||||||
Oid *funcs;
|
Oid *funcs;
|
||||||
uint16 *strats;
|
uint16 *strats;
|
||||||
@ -5207,17 +5218,19 @@ RelationGetExclusionInfo(Relation indexRelation,
|
|||||||
MemoryContext oldcxt;
|
MemoryContext oldcxt;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation);
|
||||||
|
|
||||||
/* Allocate result space in caller context */
|
/* Allocate result space in caller context */
|
||||||
*operators = ops = (Oid *) palloc(sizeof(Oid) * ncols);
|
*operators = ops = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
|
||||||
*procs = funcs = (Oid *) palloc(sizeof(Oid) * ncols);
|
*procs = funcs = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
|
||||||
*strategies = strats = (uint16 *) palloc(sizeof(uint16) * ncols);
|
*strategies = strats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts);
|
||||||
|
|
||||||
/* Quick exit if we have the data cached already */
|
/* Quick exit if we have the data cached already */
|
||||||
if (indexRelation->rd_exclstrats != NULL)
|
if (indexRelation->rd_exclstrats != NULL)
|
||||||
{
|
{
|
||||||
memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * ncols);
|
memcpy(ops, indexRelation->rd_exclops, sizeof(Oid) * indnkeyatts);
|
||||||
memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * ncols);
|
memcpy(funcs, indexRelation->rd_exclprocs, sizeof(Oid) * indnkeyatts);
|
||||||
memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * ncols);
|
memcpy(strats, indexRelation->rd_exclstrats, sizeof(uint16) * indnkeyatts);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5266,12 +5279,12 @@ RelationGetExclusionInfo(Relation indexRelation,
|
|||||||
arr = DatumGetArrayTypeP(val); /* ensure not toasted */
|
arr = DatumGetArrayTypeP(val); /* ensure not toasted */
|
||||||
nelem = ARR_DIMS(arr)[0];
|
nelem = ARR_DIMS(arr)[0];
|
||||||
if (ARR_NDIM(arr) != 1 ||
|
if (ARR_NDIM(arr) != 1 ||
|
||||||
nelem != ncols ||
|
nelem != indnkeyatts ||
|
||||||
ARR_HASNULL(arr) ||
|
ARR_HASNULL(arr) ||
|
||||||
ARR_ELEMTYPE(arr) != OIDOID)
|
ARR_ELEMTYPE(arr) != OIDOID)
|
||||||
elog(ERROR, "conexclop is not a 1-D Oid array");
|
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);
|
systable_endscan(conscan);
|
||||||
@ -5282,7 +5295,7 @@ RelationGetExclusionInfo(Relation indexRelation,
|
|||||||
RelationGetRelationName(indexRelation));
|
RelationGetRelationName(indexRelation));
|
||||||
|
|
||||||
/* We need the func OIDs and strategy numbers too */
|
/* 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]);
|
funcs[i] = get_opcode(ops[i]);
|
||||||
strats[i] = get_op_opfamily_strategy(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. */
|
/* Save a copy of the results in the relcache entry. */
|
||||||
oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
|
oldcxt = MemoryContextSwitchTo(indexRelation->rd_indexcxt);
|
||||||
indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * ncols);
|
indexRelation->rd_exclops = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
|
||||||
indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * ncols);
|
indexRelation->rd_exclprocs = (Oid *) palloc(sizeof(Oid) * indnkeyatts);
|
||||||
indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * ncols);
|
indexRelation->rd_exclstrats = (uint16 *) palloc(sizeof(uint16) * indnkeyatts);
|
||||||
memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * ncols);
|
memcpy(indexRelation->rd_exclops, ops, sizeof(Oid) * indnkeyatts);
|
||||||
memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * ncols);
|
memcpy(indexRelation->rd_exclprocs, funcs, sizeof(Oid) * indnkeyatts);
|
||||||
memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * ncols);
|
memcpy(indexRelation->rd_exclstrats, strats, sizeof(uint16) * indnkeyatts);
|
||||||
MemoryContextSwitchTo(oldcxt);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -900,7 +900,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
|
|||||||
workMem, randomAccess ? 't' : 'f');
|
workMem, randomAccess ? 't' : 'f');
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
state->nKeys = RelationGetNumberOfAttributes(indexRel);
|
state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel);
|
||||||
|
|
||||||
TRACE_POSTGRESQL_SORT_START(CLUSTER_SORT,
|
TRACE_POSTGRESQL_SORT_START(CLUSTER_SORT,
|
||||||
false, /* no unique check */
|
false, /* no unique check */
|
||||||
@ -995,7 +995,7 @@ tuplesort_begin_index_btree(Relation heapRel,
|
|||||||
workMem, randomAccess ? 't' : 'f');
|
workMem, randomAccess ? 't' : 'f');
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
state->nKeys = RelationGetNumberOfAttributes(indexRel);
|
state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel);
|
||||||
|
|
||||||
TRACE_POSTGRESQL_SORT_START(INDEX_SORT,
|
TRACE_POSTGRESQL_SORT_START(INDEX_SORT,
|
||||||
enforceUnique,
|
enforceUnique,
|
||||||
@ -1015,7 +1015,6 @@ tuplesort_begin_index_btree(Relation heapRel,
|
|||||||
state->enforceUnique = enforceUnique;
|
state->enforceUnique = enforceUnique;
|
||||||
|
|
||||||
indexScanKey = _bt_mkscankey_nodata(indexRel);
|
indexScanKey = _bt_mkscankey_nodata(indexRel);
|
||||||
state->nKeys = RelationGetNumberOfAttributes(indexRel);
|
|
||||||
|
|
||||||
/* Prepare SortSupport data for each column */
|
/* Prepare SortSupport data for each column */
|
||||||
state->sortKeys = (SortSupport) palloc0(state->nKeys *
|
state->sortKeys = (SortSupport) palloc0(state->nKeys *
|
||||||
|
@ -6726,7 +6726,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
i_indexname,
|
i_indexname,
|
||||||
i_parentidx,
|
i_parentidx,
|
||||||
i_indexdef,
|
i_indexdef,
|
||||||
i_indnkeys,
|
i_indnnkeyatts,
|
||||||
|
i_indnatts,
|
||||||
i_indkey,
|
i_indkey,
|
||||||
i_indisclustered,
|
i_indisclustered,
|
||||||
i_indisreplident,
|
i_indisreplident,
|
||||||
@ -6783,6 +6784,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"t.relname AS indexname, "
|
"t.relname AS indexname, "
|
||||||
"inh.inhparent AS parentidx, "
|
"inh.inhparent AS parentidx, "
|
||||||
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
||||||
|
"i.indnkeyatts AS indnkeyatts, "
|
||||||
|
"i.indnatts AS indnatts, "
|
||||||
"t.relnatts AS indnkeys, "
|
"t.relnatts AS indnkeys, "
|
||||||
"i.indkey, i.indisclustered, "
|
"i.indkey, i.indisclustered, "
|
||||||
"i.indisreplident, t.relpages, "
|
"i.indisreplident, t.relpages, "
|
||||||
@ -6819,6 +6822,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"t.relname AS indexname, "
|
"t.relname AS indexname, "
|
||||||
"0 AS parentidx, "
|
"0 AS parentidx, "
|
||||||
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
||||||
|
"i.indnatts AS indnkeyatts, "
|
||||||
|
"i.indnatts AS indnatts, "
|
||||||
"t.relnatts AS indnkeys, "
|
"t.relnatts AS indnkeys, "
|
||||||
"i.indkey, i.indisclustered, "
|
"i.indkey, i.indisclustered, "
|
||||||
"i.indisreplident, t.relpages, "
|
"i.indisreplident, t.relpages, "
|
||||||
@ -6851,6 +6856,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"t.relname AS indexname, "
|
"t.relname AS indexname, "
|
||||||
"0 AS parentidx, "
|
"0 AS parentidx, "
|
||||||
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
||||||
|
"i.indnatts AS indnkeyatts, "
|
||||||
|
"i.indnatts AS indnatts, "
|
||||||
"t.relnatts AS indnkeys, "
|
"t.relnatts AS indnkeys, "
|
||||||
"i.indkey, i.indisclustered, "
|
"i.indkey, i.indisclustered, "
|
||||||
"false AS indisreplident, t.relpages, "
|
"false AS indisreplident, t.relpages, "
|
||||||
@ -6879,6 +6886,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
"t.relname AS indexname, "
|
"t.relname AS indexname, "
|
||||||
"0 AS parentidx, "
|
"0 AS parentidx, "
|
||||||
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
|
||||||
|
"i.indnatts AS indnkeyatts, "
|
||||||
|
"i.indnatts AS indnatts, "
|
||||||
"t.relnatts AS indnkeys, "
|
"t.relnatts AS indnkeys, "
|
||||||
"i.indkey, i.indisclustered, "
|
"i.indkey, i.indisclustered, "
|
||||||
"false AS indisreplident, t.relpages, "
|
"false AS indisreplident, t.relpages, "
|
||||||
@ -6943,7 +6952,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
|||||||
i_indexname = PQfnumber(res, "indexname");
|
i_indexname = PQfnumber(res, "indexname");
|
||||||
i_parentidx = PQfnumber(res, "parentidx");
|
i_parentidx = PQfnumber(res, "parentidx");
|
||||||
i_indexdef = PQfnumber(res, "indexdef");
|
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_indkey = PQfnumber(res, "indkey");
|
||||||
i_indisclustered = PQfnumber(res, "indisclustered");
|
i_indisclustered = PQfnumber(res, "indisclustered");
|
||||||
i_indisreplident = PQfnumber(res, "indisreplident");
|
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].dobj.namespace = tbinfo->dobj.namespace;
|
||||||
indxinfo[j].indextable = tbinfo;
|
indxinfo[j].indextable = tbinfo;
|
||||||
indxinfo[j].indexdef = pg_strdup(PQgetvalue(res, j, i_indexdef));
|
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].tablespace = pg_strdup(PQgetvalue(res, j, i_tablespace));
|
||||||
indxinfo[j].indreloptions = pg_strdup(PQgetvalue(res, j, i_indreloptions));
|
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),
|
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].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
|
||||||
indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
|
indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
|
||||||
indxinfo[j].parentidx = atooid(PQgetvalue(res, j, i_parentidx));
|
indxinfo[j].parentidx = atooid(PQgetvalue(res, j, i_parentidx));
|
||||||
@ -16342,7 +16353,7 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
|
|||||||
{
|
{
|
||||||
appendPQExpBuffer(q, "%s (",
|
appendPQExpBuffer(q, "%s (",
|
||||||
coninfo->contype == 'p' ? "PRIMARY KEY" : "UNIQUE");
|
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];
|
int indkey = (int) indxinfo->indkeys[k];
|
||||||
const char *attname;
|
const char *attname;
|
||||||
@ -16356,6 +16367,23 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
|
|||||||
fmtId(attname));
|
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, ')');
|
appendPQExpBufferChar(q, ')');
|
||||||
|
|
||||||
if (nonemptyReloptions(indxinfo->indreloptions))
|
if (nonemptyReloptions(indxinfo->indreloptions))
|
||||||
|
@ -360,8 +360,10 @@ typedef struct _indxInfo
|
|||||||
char *indexdef;
|
char *indexdef;
|
||||||
char *tablespace; /* tablespace in which index is stored */
|
char *tablespace; /* tablespace in which index is stored */
|
||||||
char *indreloptions; /* options specified by WITH (...) */
|
char *indreloptions; /* options specified by WITH (...) */
|
||||||
int indnkeys;
|
int indnkeyattrs; /* number of index key attributes */
|
||||||
Oid *indkeys;
|
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 indisclustered;
|
||||||
bool indisreplident;
|
bool indisreplident;
|
||||||
Oid parentidx; /* if partitioned, parent index OID */
|
Oid parentidx; /* if partitioned, parent index OID */
|
||||||
|
@ -191,6 +191,8 @@ typedef struct IndexAmRoutine
|
|||||||
bool ampredlocks;
|
bool ampredlocks;
|
||||||
/* does AM support parallel scan? */
|
/* does AM support parallel scan? */
|
||||||
bool amcanparallel;
|
bool amcanparallel;
|
||||||
|
/* does AM support columns included with clause INCLUDE? */
|
||||||
|
bool amcaninclude;
|
||||||
/* type of data stored in index, or InvalidOid if variable */
|
/* type of data stored in index, or InvalidOid if variable */
|
||||||
Oid amkeytype;
|
Oid amkeytype;
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ typedef HashMetaPageData *HashMetaPage;
|
|||||||
sizeof(ItemIdData) - \
|
sizeof(ItemIdData) - \
|
||||||
MAXALIGN(sizeof(HashPageOpaqueData)))
|
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_MIN_FILLFACTOR 10
|
||||||
#define HASH_DEFAULT_FILLFACTOR 75
|
#define HASH_DEFAULT_FILLFACTOR 75
|
||||||
|
@ -41,7 +41,7 @@ typedef struct IndexTupleData
|
|||||||
*
|
*
|
||||||
* 15th (high) bit: has nulls
|
* 15th (high) bit: has nulls
|
||||||
* 14th bit: has var-width attributes
|
* 14th bit: has var-width attributes
|
||||||
* 13th bit: unused
|
* 13th bit: AM-defined meaning
|
||||||
* 12-0 bit: size of tuple
|
* 12-0 bit: size of tuple
|
||||||
* ---------------
|
* ---------------
|
||||||
*/
|
*/
|
||||||
@ -63,7 +63,8 @@ typedef IndexAttributeBitMapData * IndexAttributeBitMap;
|
|||||||
* t_info manipulation macros
|
* t_info manipulation macros
|
||||||
*/
|
*/
|
||||||
#define INDEX_SIZE_MASK 0x1FFF
|
#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_VAR_MASK 0x4000
|
||||||
#define INDEX_NULL_MASK 0x8000
|
#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,
|
extern void index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
|
||||||
Datum *values, bool *isnull);
|
Datum *values, bool *isnull);
|
||||||
extern IndexTuple CopyIndexTuple(IndexTuple source);
|
extern IndexTuple CopyIndexTuple(IndexTuple source);
|
||||||
|
extern IndexTuple index_truncate_tuple(TupleDesc tupleDescriptor,
|
||||||
|
IndexTuple olditup, int new_indnatts);
|
||||||
|
|
||||||
#endif /* ITUP_H */
|
#endif /* ITUP_H */
|
||||||
|
@ -139,31 +139,6 @@ typedef struct BTMetaPageData
|
|||||||
#define BTREE_DEFAULT_FILLFACTOR 90
|
#define BTREE_DEFAULT_FILLFACTOR 90
|
||||||
#define BTREE_NONLEAF_FILLFACTOR 70
|
#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
|
* In general, the btree code tries to localize its knowledge about
|
||||||
* page layout to a couple of routines. However, we need a special
|
* 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)
|
#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,
|
* Operator strategy numbers for B-tree have been moved to access/stratnum.h,
|
||||||
* because many places need to use them in ScanKeyInit() calls.
|
* because many places need to use them in ScanKeyInit() calls.
|
||||||
@ -265,7 +302,7 @@ typedef struct BTStackData
|
|||||||
{
|
{
|
||||||
BlockNumber bts_blkno;
|
BlockNumber bts_blkno;
|
||||||
OffsetNumber bts_offset;
|
OffsetNumber bts_offset;
|
||||||
IndexTupleData bts_btentry;
|
BlockNumber bts_btentry;
|
||||||
struct BTStackData *bts_parent;
|
struct BTStackData *bts_parent;
|
||||||
} BTStackData;
|
} BTStackData;
|
||||||
|
|
||||||
@ -524,6 +561,7 @@ extern bool _bt_first(IndexScanDesc scan, ScanDirection dir);
|
|||||||
extern bool _bt_next(IndexScanDesc scan, ScanDirection dir);
|
extern bool _bt_next(IndexScanDesc scan, ScanDirection dir);
|
||||||
extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
|
extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
|
||||||
Snapshot snapshot);
|
Snapshot snapshot);
|
||||||
|
extern bool _bt_check_natts(Relation index, Page page, OffsetNumber offnum);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prototypes for functions in nbtutils.c
|
* 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,
|
extern bool btproperty(Oid index_oid, int attno,
|
||||||
IndexAMProperty prop, const char *propname,
|
IndexAMProperty prop, const char *propname,
|
||||||
bool *res, bool *isnull);
|
bool *res, bool *isnull);
|
||||||
|
extern IndexTuple _bt_truncate_tuple(Relation idxrel, IndexTuple olditup);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* prototypes for functions in nbtvalidate.c
|
* prototypes for functions in nbtvalidate.c
|
||||||
|
@ -28,7 +28,8 @@
|
|||||||
#define XLOG_BTREE_INSERT_META 0x20 /* same, plus update metapage */
|
#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_L 0x30 /* add index tuple with split */
|
||||||
#define XLOG_BTREE_SPLIT_R 0x40 /* as above, new item on right */
|
#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_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 0x80 /* delete a half-dead page */
|
||||||
#define XLOG_BTREE_UNLINK_PAGE_META 0x90 /* same, and update metapage */
|
#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.
|
* 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
|
* 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
|
* 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
|
* are stored or not). The _HIGHKEY variants indicate that we've logged
|
||||||
* the root page, and thus that a newroot record rather than an insert or
|
* explicitly left page high key value, otherwise redo should use right page
|
||||||
* split record should follow. Note that a split record never carries a
|
* leftmost key as a left page high key. _HIGHKEY is specified for internal
|
||||||
* metapage update --- we'll do that in the parent-level update.
|
* 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
|
* Backup Blk 0: original page / new left page
|
||||||
*
|
*
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 201804072
|
#define CATALOG_VERSION_NO 201804073
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -104,6 +104,12 @@ CATALOG(pg_constraint,2606)
|
|||||||
*/
|
*/
|
||||||
int16 conkey[1];
|
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
|
* 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
|
* 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_conname 1
|
||||||
#define Anum_pg_constraint_connamespace 2
|
#define Anum_pg_constraint_connamespace 2
|
||||||
#define Anum_pg_constraint_contype 3
|
#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_coninhcount 16
|
||||||
#define Anum_pg_constraint_connoinherit 17
|
#define Anum_pg_constraint_connoinherit 17
|
||||||
#define Anum_pg_constraint_conkey 18
|
#define Anum_pg_constraint_conkey 18
|
||||||
#define Anum_pg_constraint_confkey 19
|
#define Anum_pg_constraint_conincluding 19
|
||||||
#define Anum_pg_constraint_conpfeqop 20
|
#define Anum_pg_constraint_confkey 20
|
||||||
#define Anum_pg_constraint_conppeqop 21
|
#define Anum_pg_constraint_conpfeqop 21
|
||||||
#define Anum_pg_constraint_conffeqop 22
|
#define Anum_pg_constraint_conppeqop 22
|
||||||
#define Anum_pg_constraint_conexclop 23
|
#define Anum_pg_constraint_conffeqop 23
|
||||||
#define Anum_pg_constraint_conbin 24
|
#define Anum_pg_constraint_conexclop 24
|
||||||
#define Anum_pg_constraint_consrc 25
|
#define Anum_pg_constraint_conbin 25
|
||||||
|
#define Anum_pg_constraint_consrc 26
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
* initial contents of pg_constraint
|
* initial contents of pg_constraint
|
||||||
|
@ -50,6 +50,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
|
|||||||
Oid relId,
|
Oid relId,
|
||||||
const int16 *constraintKey,
|
const int16 *constraintKey,
|
||||||
int constraintNKeys,
|
int constraintNKeys,
|
||||||
|
int constraintNTotalKeys,
|
||||||
Oid domainId,
|
Oid domainId,
|
||||||
Oid indexRelId,
|
Oid indexRelId,
|
||||||
Oid foreignRelId,
|
Oid foreignRelId,
|
||||||
|
@ -32,7 +32,8 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
|
|||||||
{
|
{
|
||||||
Oid indexrelid; /* OID of the index */
|
Oid indexrelid; /* OID of the index */
|
||||||
Oid indrelid; /* OID of the relation it indexes */
|
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 indisunique; /* is this a unique index? */
|
||||||
bool indisprimary; /* is this index for primary key? */
|
bool indisprimary; /* is this index for primary key? */
|
||||||
bool indisexclusion; /* is this index for exclusion constraint? */
|
bool indisexclusion; /* is this index for exclusion constraint? */
|
||||||
@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index;
|
|||||||
* compiler constants for 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_indexrelid 1
|
||||||
#define Anum_pg_index_indrelid 2
|
#define Anum_pg_index_indrelid 2
|
||||||
#define Anum_pg_index_indnatts 3
|
#define Anum_pg_index_indnatts 3
|
||||||
#define Anum_pg_index_indisunique 4
|
#define Anum_pg_index_indnkeyatts 4
|
||||||
#define Anum_pg_index_indisprimary 5
|
#define Anum_pg_index_indisunique 5
|
||||||
#define Anum_pg_index_indisexclusion 6
|
#define Anum_pg_index_indisprimary 6
|
||||||
#define Anum_pg_index_indimmediate 7
|
#define Anum_pg_index_indisexclusion 7
|
||||||
#define Anum_pg_index_indisclustered 8
|
#define Anum_pg_index_indimmediate 8
|
||||||
#define Anum_pg_index_indisvalid 9
|
#define Anum_pg_index_indisclustered 9
|
||||||
#define Anum_pg_index_indcheckxmin 10
|
#define Anum_pg_index_indisvalid 10
|
||||||
#define Anum_pg_index_indisready 11
|
#define Anum_pg_index_indcheckxmin 11
|
||||||
#define Anum_pg_index_indislive 12
|
#define Anum_pg_index_indisready 12
|
||||||
#define Anum_pg_index_indisreplident 13
|
#define Anum_pg_index_indislive 13
|
||||||
#define Anum_pg_index_indkey 14
|
#define Anum_pg_index_indisreplident 14
|
||||||
#define Anum_pg_index_indcollation 15
|
#define Anum_pg_index_indkey 15
|
||||||
#define Anum_pg_index_indclass 16
|
#define Anum_pg_index_indcollation 16
|
||||||
#define Anum_pg_index_indoption 17
|
#define Anum_pg_index_indclass 17
|
||||||
#define Anum_pg_index_indexprs 18
|
#define Anum_pg_index_indoption 18
|
||||||
#define Anum_pg_index_indpred 19
|
#define Anum_pg_index_indexprs 19
|
||||||
|
#define Anum_pg_index_indpred 20
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Index AMs that support ordered scans must support these two indoption
|
* Index AMs that support ordered scans must support these two indoption
|
||||||
|
@ -118,9 +118,11 @@ typedef struct ExprState
|
|||||||
* entries for a particular index. Used for both index_build and
|
* entries for a particular index. Used for both index_build and
|
||||||
* retail creation of index entries.
|
* 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
|
* 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
|
* Expressions expr trees for expression entries, or NIL if none
|
||||||
* ExpressionsState exec state for expressions, or NIL if none
|
* ExpressionsState exec state for expressions, or NIL if none
|
||||||
* Predicate partial-index predicate, or NIL if none
|
* Predicate partial-index predicate, or NIL if none
|
||||||
@ -146,7 +148,8 @@ typedef struct ExprState
|
|||||||
typedef struct IndexInfo
|
typedef struct IndexInfo
|
||||||
{
|
{
|
||||||
NodeTag type;
|
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];
|
AttrNumber ii_KeyAttrNumbers[INDEX_MAX_KEYS];
|
||||||
List *ii_Expressions; /* list of Expr */
|
List *ii_Expressions; /* list of Expr */
|
||||||
List *ii_ExpressionsState; /* list of ExprState */
|
List *ii_ExpressionsState; /* list of ExprState */
|
||||||
|
@ -2147,7 +2147,10 @@ typedef struct Constraint
|
|||||||
char generated_when;
|
char generated_when;
|
||||||
|
|
||||||
/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
|
/* 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: */
|
/* Fields used for EXCLUSION constraints: */
|
||||||
List *exclusions; /* list of (IndexElem, operator name) pairs */
|
List *exclusions; /* list of (IndexElem, operator name) pairs */
|
||||||
@ -2760,6 +2763,8 @@ typedef struct IndexStmt
|
|||||||
char *accessMethod; /* name of access method (eg. btree) */
|
char *accessMethod; /* name of access method (eg. btree) */
|
||||||
char *tableSpace; /* tablespace, or NULL for default */
|
char *tableSpace; /* tablespace, or NULL for default */
|
||||||
List *indexParams; /* columns to index: a list of IndexElem */
|
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 */
|
List *options; /* WITH clause options: a list of DefElem */
|
||||||
Node *whereClause; /* qualification (partial-index predicate) */
|
Node *whereClause; /* qualification (partial-index predicate) */
|
||||||
List *excludeOpNames; /* exclusion operator names, or NIL if none */
|
List *excludeOpNames; /* exclusion operator names, or NIL if none */
|
||||||
|
@ -707,11 +707,12 @@ typedef struct RelOptInfo
|
|||||||
* IndexOptInfo
|
* IndexOptInfo
|
||||||
* Per-index information for planning/optimization
|
* Per-index information for planning/optimization
|
||||||
*
|
*
|
||||||
* indexkeys[], indexcollations[], opfamily[], and opcintype[]
|
* indexkeys[], indexcollations[] each have ncolumns entries.
|
||||||
* 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
|
* sortopfamily[], reverse_sort[], and nulls_first[] have
|
||||||
* ncolumns entries, if the index is ordered; but if it is unordered,
|
* nkeycolumns entries, if the index is ordered; but if it is unordered,
|
||||||
* those pointers are NULL.
|
* those pointers are NULL.
|
||||||
*
|
*
|
||||||
* Zeroes in the indexkeys[] array indicate index columns that are
|
* Zeroes in the indexkeys[] array indicate index columns that are
|
||||||
@ -748,7 +749,9 @@ typedef struct IndexOptInfo
|
|||||||
|
|
||||||
/* index descriptor information */
|
/* index descriptor information */
|
||||||
int ncolumns; /* number of columns in index */
|
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 *indexcollations; /* OIDs of collations of index columns */
|
||||||
Oid *opfamily; /* OIDs of operator families for columns */
|
Oid *opfamily; /* OIDs of operator families for columns */
|
||||||
Oid *opcintype; /* OIDs of opclass declared input data types */
|
Oid *opcintype; /* OIDs of opclass declared input data types */
|
||||||
|
@ -196,6 +196,7 @@ PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD)
|
|||||||
PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
|
PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
|
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
|
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
|
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
|
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
|
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
|
||||||
|
@ -438,10 +438,24 @@ typedef struct ViewOptions
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* RelationGetNumberOfAttributes
|
* 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)
|
#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
|
* RelationGetDescr
|
||||||
* Returns tuple descriptor for a relation.
|
* Returns tuple descriptor for a relation.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
setup
|
setup
|
||||||
{
|
{
|
||||||
CREATE TABLE ints (key int primary key, val text);
|
CREATE TABLE ints (key int, val text, PRIMARY KEY (key) INCLUDE (val));
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown
|
teardown
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
setup
|
setup
|
||||||
{
|
{
|
||||||
CREATE TABLE upsert (key text not null, payload text);
|
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
|
teardown
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
setup
|
setup
|
||||||
{
|
{
|
||||||
DROP TABLE IF EXISTS lcku_table;
|
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 (1, 'one');
|
||||||
INSERT INTO lcku_table VALUES (3, 'two');
|
INSERT INTO lcku_table VALUES (3, 'two');
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
setup
|
setup
|
||||||
{
|
{
|
||||||
CREATE TABLE foo (
|
CREATE TABLE foo (
|
||||||
key int PRIMARY KEY,
|
key int,
|
||||||
value int
|
value int,
|
||||||
|
PRIMARY KEY (key) INCLUDE (value)
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO foo VALUES (1, 1);
|
INSERT INTO foo VALUES (1, 1);
|
||||||
|
@ -2433,6 +2433,25 @@ DETAIL: Key ((f1 || f2))=(ABCDEF) already exists.
|
|||||||
-- but this shouldn't:
|
-- but this shouldn't:
|
||||||
INSERT INTO func_index_heap VALUES('QWERTY');
|
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
|
-- Also try building functional, expressional, and partial indexes on
|
||||||
-- tables that already contain data.
|
-- tables that already contain data.
|
||||||
--
|
--
|
||||||
|
346
src/test/regress/expected/index_including.out
Normal file
346
src/test/regress/expected/index_including.out
Normal file
@ -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;
|
@ -55,7 +55,7 @@ test: copy copyselect copydml
|
|||||||
# ----------
|
# ----------
|
||||||
test: create_misc create_operator create_procedure
|
test: create_misc create_operator create_procedure
|
||||||
# These depend on the above two
|
# These depend on the above two
|
||||||
test: create_index create_view
|
test: create_index create_view index_including
|
||||||
|
|
||||||
# ----------
|
# ----------
|
||||||
# Another group of parallel tests
|
# Another group of parallel tests
|
||||||
|
@ -65,6 +65,7 @@ test: create_misc
|
|||||||
test: create_operator
|
test: create_operator
|
||||||
test: create_procedure
|
test: create_procedure
|
||||||
test: create_index
|
test: create_index
|
||||||
|
test: index_including
|
||||||
test: create_view
|
test: create_view
|
||||||
test: create_aggregate
|
test: create_aggregate
|
||||||
test: create_function_3
|
test: create_function_3
|
||||||
|
@ -741,6 +741,26 @@ INSERT INTO func_index_heap VALUES('ABCD', 'EF');
|
|||||||
-- but this shouldn't:
|
-- but this shouldn't:
|
||||||
INSERT INTO func_index_heap VALUES('QWERTY');
|
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
|
-- Also try building functional, expressional, and partial indexes on
|
||||||
-- tables that already contain data.
|
-- tables that already contain data.
|
||||||
|
203
src/test/regress/sql/index_including.sql
Normal file
203
src/test/regress/sql/index_including.sql
Normal file
@ -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;
|
||||||
|
|
@ -3,7 +3,7 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
use PostgresNode;
|
use PostgresNode;
|
||||||
use TestLib;
|
use TestLib;
|
||||||
use Test::More tests => 16;
|
use Test::More tests => 17;
|
||||||
|
|
||||||
# Initialize publisher node
|
# Initialize publisher node
|
||||||
my $node_publisher = get_new_node('publisher');
|
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)");
|
"CREATE TABLE tab_mixed (a int primary key, b text)");
|
||||||
$node_publisher->safe_psql('postgres',
|
$node_publisher->safe_psql('postgres',
|
||||||
"INSERT INTO tab_mixed (a, b) VALUES (1, 'foo')");
|
"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
|
# Setup structure on subscriber
|
||||||
$node_subscriber->safe_psql('postgres', "CREATE TABLE tab_notrep (a int)");
|
$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',
|
$node_subscriber->safe_psql('postgres',
|
||||||
"CREATE TABLE tab_mixed (c text, b text, a int primary key)");
|
"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
|
# Setup logical replication
|
||||||
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
|
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");
|
||||||
$node_publisher->safe_psql('postgres',
|
$node_publisher->safe_psql('postgres',
|
||||||
"CREATE PUBLICATION tap_pub_ins_only WITH (publish = insert)");
|
"CREATE PUBLICATION tap_pub_ins_only WITH (publish = insert)");
|
||||||
$node_publisher->safe_psql('postgres',
|
$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',
|
$node_publisher->safe_psql('postgres',
|
||||||
"ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
|
"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',
|
$node_publisher->safe_psql('postgres',
|
||||||
"INSERT INTO tab_mixed VALUES (2, 'bar')");
|
"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);
|
$node_publisher->wait_for_catchup($appname);
|
||||||
|
|
||||||
$result = $node_subscriber->safe_psql('postgres',
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
@ -104,6 +115,10 @@ $result =
|
|||||||
is( $result, qq(|foo|1
|
is( $result, qq(|foo|1
|
||||||
|bar|2), 'check replicated changes with different column order');
|
|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
|
# insert some duplicate rows
|
||||||
$node_publisher->safe_psql('postgres',
|
$node_publisher->safe_psql('postgres',
|
||||||
"INSERT INTO tab_full SELECT generate_series(1,10)");
|
"INSERT INTO tab_full SELECT generate_series(1,10)");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user