1
0
mirror of https://github.com/postgres/postgres.git synced 2025-10-31 10:30:33 +03:00

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:
Teodor Sigaev
2018-04-07 23:00:39 +03:00
parent 01bb85169a
commit 8224de4f42
89 changed files with 2112 additions and 467 deletions

View File

@@ -1051,7 +1051,7 @@ transformOnConflictClause(ParseState *pstate,
* relation. Have to be careful to use resnos that correspond to
* attnos of the underlying relation.
*/
for (attno = 0; attno < targetrel->rd_rel->relnatts; attno++)
for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
{
Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
char *name;
@@ -2276,8 +2276,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
EXPR_KIND_UPDATE_SOURCE);
/* Prepare to assign non-conflicting resnos to resjunk attributes */
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
if (pstate->p_next_resno <= RelationGetNumberOfAttributes(pstate->p_target_relation))
pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
/* Prepare non-junk columns for assignment to target table */
target_rte = pstate->p_target_rangetblentry;

View File

@@ -382,6 +382,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
sort_clause opt_sort_clause sortby_list index_params
opt_include opt_c_include index_including_params
name_list role_list from_clause from_list opt_array_bounds
qualified_name_list any_name any_name_list type_name_list
any_operator expr_list attrs
@@ -645,7 +646,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -3686,17 +3687,18 @@ ConstraintElem:
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
| UNIQUE '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
n->location = @1;
n->keys = $3;
n->options = $5;
n->including = $5;
n->options = $6;
n->indexname = NULL;
n->indexspace = $6;
processCASbits($7, @7, "UNIQUE",
n->indexspace = $7;
processCASbits($8, @8, "UNIQUE",
&n->deferrable, &n->initdeferred, NULL,
NULL, yyscanner);
$$ = (Node *)n;
@@ -3707,6 +3709,7 @@ ConstraintElem:
n->contype = CONSTR_UNIQUE;
n->location = @1;
n->keys = NIL;
n->including = NIL;
n->options = NIL;
n->indexname = $2;
n->indexspace = NULL;
@@ -3715,17 +3718,18 @@ ConstraintElem:
NULL, yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_PRIMARY;
n->location = @1;
n->keys = $4;
n->options = $6;
n->including = $6;
n->options = $7;
n->indexname = NULL;
n->indexspace = $7;
processCASbits($8, @8, "PRIMARY KEY",
n->indexspace = $8;
processCASbits($9, @9, "PRIMARY KEY",
&n->deferrable, &n->initdeferred, NULL,
NULL, yyscanner);
$$ = (Node *)n;
@@ -3736,6 +3740,7 @@ ConstraintElem:
n->contype = CONSTR_PRIMARY;
n->location = @1;
n->keys = NIL;
n->including = NIL;
n->options = NIL;
n->indexname = $3;
n->indexspace = NULL;
@@ -3745,7 +3750,7 @@ ConstraintElem:
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
opt_definition OptConsTableSpace ExclusionWhereClause
opt_c_include opt_definition OptConsTableSpace ExclusionWhereClause
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
@@ -3753,11 +3758,12 @@ ConstraintElem:
n->location = @1;
n->access_method = $2;
n->exclusions = $4;
n->options = $6;
n->including = $6;
n->options = $7;
n->indexname = NULL;
n->indexspace = $7;
n->where_clause = $8;
processCASbits($9, @9, "EXCLUDE",
n->indexspace = $8;
n->where_clause = $9;
processCASbits($10, @10, "EXCLUDE",
&n->deferrable, &n->initdeferred, NULL,
NULL, yyscanner);
$$ = (Node *)n;
@@ -3803,6 +3809,10 @@ columnElem: ColId
}
;
opt_c_include: INCLUDE '(' columnList ')' { $$ = $3; }
| /* EMPTY */ { $$ = NIL; }
;
key_match: MATCH FULL
{
$$ = FKCONSTR_MATCH_FULL;
@@ -7373,7 +7383,7 @@ defacl_privilege_target:
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
ON relation_expr access_method_clause '(' index_params ')'
opt_reloptions OptTableSpace where_clause
opt_include opt_reloptions OptTableSpace where_clause
{
IndexStmt *n = makeNode(IndexStmt);
n->unique = $2;
@@ -7383,9 +7393,10 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->relationId = InvalidOid;
n->accessMethod = $8;
n->indexParams = $10;
n->options = $12;
n->tableSpace = $13;
n->whereClause = $14;
n->indexIncludingParams = $12;
n->options = $13;
n->tableSpace = $14;
n->whereClause = $15;
n->excludeOpNames = NIL;
n->idxcomment = NULL;
n->indexOid = InvalidOid;
@@ -7400,7 +7411,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
}
| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
ON relation_expr access_method_clause '(' index_params ')'
opt_reloptions OptTableSpace where_clause
opt_include opt_reloptions OptTableSpace where_clause
{
IndexStmt *n = makeNode(IndexStmt);
n->unique = $2;
@@ -7410,9 +7421,10 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->relationId = InvalidOid;
n->accessMethod = $11;
n->indexParams = $13;
n->options = $15;
n->tableSpace = $16;
n->whereClause = $17;
n->indexIncludingParams = $15;
n->options = $16;
n->tableSpace = $17;
n->whereClause = $18;
n->excludeOpNames = NIL;
n->idxcomment = NULL;
n->indexOid = InvalidOid;
@@ -7491,6 +7503,14 @@ index_elem: ColId opt_collate opt_class opt_asc_desc opt_nulls_order
}
;
opt_include: INCLUDE '(' index_including_params ')' { $$ = $3; }
| /* EMPTY */ { $$ = NIL; }
;
index_including_params: index_elem { $$ = list_make1($1); }
| index_including_params ',' index_elem { $$ = lappend($1, $3); }
;
opt_collate: COLLATE any_name { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
;
@@ -15206,6 +15226,7 @@ unreserved_keyword:
| IMMUTABLE
| IMPLICIT_P
| IMPORT_P
| INCLUDE
| INCLUDING
| INCREMENT
| INDEX

View File

@@ -3089,7 +3089,7 @@ attnameAttNum(Relation rd, const char *attname, bool sysColOK)
{
int i;
for (i = 0; i < rd->rd_rel->relnatts; i++)
for (i = 0; i < RelationGetNumberOfAttributes(rd); i++)
{
Form_pg_attribute att = TupleDescAttr(rd->rd_att, i);

View File

@@ -978,7 +978,8 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
/*
* Generate default column list for INSERT.
*/
int numcol = pstate->p_target_relation->rd_rel->relnatts;
int numcol = RelationGetNumberOfAttributes(pstate->p_target_relation);
int i;
for (i = 0; i < numcol; i++)

View File

@@ -1468,9 +1468,10 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
/* Build the list of IndexElem */
index->indexParams = NIL;
index->indexIncludingParams = NIL;
indexpr_item = list_head(indexprs);
for (keyno = 0; keyno < idxrec->indnatts; keyno++)
for (keyno = 0; keyno < idxrec->indnkeyatts; keyno++)
{
IndexElem *iparam;
AttrNumber attnum = idxrec->indkey.values[keyno];
@@ -1559,6 +1560,40 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
index->indexParams = lappend(index->indexParams, iparam);
}
/* Handle included columns separately */
for (keyno = idxrec->indnkeyatts; keyno < idxrec->indnatts; keyno++)
{
IndexElem *iparam;
AttrNumber attnum = idxrec->indkey.values[keyno];
Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx),
keyno);
iparam = makeNode(IndexElem);
if (AttributeNumberIsValid(attnum))
{
/* Simple index column */
char *attname;
attname = get_attname(indrelid, attnum, false);
keycoltype = get_atttype(indrelid, attnum);
iparam->name = attname;
iparam->expr = NULL;
}
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("expressions are not supported in included columns")));
/* Copy the original index column name */
iparam->indexcolname = pstrdup(NameStr(attr->attname));
/* Add the collation name, if non-default */
iparam->collation = get_collation(indcollation->values[keyno], keycoltype);
index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
}
/* Copy reloptions if any */
datum = SysCacheGetAttr(RELOID, ht_idxrel,
Anum_pg_class_reloptions, &isnull);
@@ -1829,6 +1864,7 @@ transformIndexConstraints(CreateStmtContext *cxt)
IndexStmt *priorindex = lfirst(k);
if (equal(index->indexParams, priorindex->indexParams) &&
equal(index->indexIncludingParams, priorindex->indexIncludingParams) &&
equal(index->whereClause, priorindex->whereClause) &&
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
@@ -1900,6 +1936,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->tableSpace = constraint->indexspace;
index->whereClause = constraint->where_clause;
index->indexParams = NIL;
index->indexIncludingParams = NIL;
index->excludeOpNames = NIL;
index->idxcomment = NULL;
index->indexOid = InvalidOid;
@@ -2049,24 +2086,29 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
heap_rel->rd_rel->relhasoids);
attname = pstrdup(NameStr(attform->attname));
/*
* Insist on default opclass and sort options. While the index
* would still work as a constraint with non-default settings, it
* might not provide exactly the same uniqueness semantics as
* you'd get from a normally-created constraint; and there's also
* the dump/reload problem mentioned above.
*/
defopclass = GetDefaultOpClass(attform->atttypid,
index_rel->rd_rel->relam);
if (indclass->values[i] != defopclass ||
index_rel->rd_indoption[i] != 0)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("index \"%s\" does not have default sorting behavior", index_name),
errdetail("Cannot create a primary key or unique constraint using such an index."),
parser_errposition(cxt->pstate, constraint->location)));
if (i < index_form->indnkeyatts)
{
/*
* Insist on default opclass and sort options. While the
* index would still work as a constraint with non-default
* settings, it might not provide exactly the same uniqueness
* semantics as you'd get from a normally-created constraint;
* and there's also the dump/reload problem mentioned above.
*/
defopclass = GetDefaultOpClass(attform->atttypid,
index_rel->rd_rel->relam);
if (indclass->values[i] != defopclass ||
index_rel->rd_indoption[i] != 0)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("index \"%s\" does not have default sorting behavior", index_name),
errdetail("Cannot create a primary key or unique constraint using such an index."),
parser_errposition(cxt->pstate, constraint->location)));
constraint->keys = lappend(constraint->keys, makeString(attname));
constraint->keys = lappend(constraint->keys, makeString(attname));
}
else
constraint->including = lappend(constraint->including, makeString(attname));
}
/* Close the index relation but keep the lock */
@@ -2095,8 +2137,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->indexParams = lappend(index->indexParams, elem);
index->excludeOpNames = lappend(index->excludeOpNames, opname);
}
return index;
}
/*
@@ -2107,7 +2147,136 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
* it to DefineIndex to mark the columns NOT NULL, it's more efficient to
* get it right the first time.)
*/
foreach(lc, constraint->keys)
else
{
foreach(lc, constraint->keys)
{
char *key = strVal(lfirst(lc));
bool found = false;
ColumnDef *column = NULL;
ListCell *columns;
IndexElem *iparam;
/* Make sure referenced column exist. */
foreach(columns, cxt->columns)
{
column = castNode(ColumnDef, lfirst(columns));
if (strcmp(column->colname, key) == 0)
{
found = true;
break;
}
}
if (found)
{
/* found column in the new table; force it to be NOT NULL */
if (constraint->contype == CONSTR_PRIMARY)
column->is_not_null = true;
}
else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
{
/*
* column will be a system column in the new table, so accept
* it. System columns can't ever be null, so no need to worry
* about PRIMARY/NOT NULL constraint.
*/
found = true;
}
else if (cxt->inhRelations)
{
/* try inherited tables */
ListCell *inher;
foreach(inher, cxt->inhRelations)
{
RangeVar *inh = castNode(RangeVar, lfirst(inher));
Relation rel;
int count;
rel = heap_openrv(inh, AccessShareLock);
/* check user requested inheritance from valid relkind */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
inh->relname)));
for (count = 0; count < rel->rd_att->natts; count++)
{
Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
count);
char *inhname = NameStr(inhattr->attname);
if (inhattr->attisdropped)
continue;
if (strcmp(key, inhname) == 0)
{
found = true;
/*
* We currently have no easy way to force an
* inherited column to be NOT NULL at creation, if
* its parent wasn't so already. We leave it to
* DefineIndex to fix things up in this case.
*/
break;
}
}
heap_close(rel, NoLock);
if (found)
break;
}
}
/*
* In the ALTER TABLE case, don't complain about index keys not
* created in the command; they may well exist already.
* DefineIndex will complain about them if not, and will also take
* care of marking them NOT NULL.
*/
if (!found && !cxt->isalter)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" named in key does not exist", key),
parser_errposition(cxt->pstate, constraint->location)));
/* Check for PRIMARY KEY(foo, foo) */
foreach(columns, index->indexParams)
{
iparam = (IndexElem *) lfirst(columns);
if (iparam->name && strcmp(key, iparam->name) == 0)
{
if (index->primary)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" appears twice in primary key constraint",
key),
parser_errposition(cxt->pstate, constraint->location)));
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" appears twice in unique constraint",
key),
parser_errposition(cxt->pstate, constraint->location)));
}
}
/* OK, add it to the index definition */
iparam = makeNode(IndexElem);
iparam->name = pstrdup(key);
iparam->expr = NULL;
iparam->indexcolname = NULL;
iparam->collation = NIL;
iparam->opclass = NIL;
iparam->ordering = SORTBY_DEFAULT;
iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
index->indexParams = lappend(index->indexParams, iparam);
}
}
/* Add included columns to index definition */
foreach(lc, constraint->including)
{
char *key = strVal(lfirst(lc));
bool found = false;
@@ -2124,65 +2293,63 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
break;
}
}
if (found)
{
/* found column in the new table; force it to be NOT NULL */
if (constraint->contype == CONSTR_PRIMARY)
column->is_not_null = true;
}
else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
{
/*
* column will be a system column in the new table, so accept it.
* System columns can't ever be null, so no need to worry about
* PRIMARY/NOT NULL constraint.
*/
found = true;
}
else if (cxt->inhRelations)
{
/* try inherited tables */
ListCell *inher;
foreach(inher, cxt->inhRelations)
if (!found)
{
if (SystemAttributeByName(key, cxt->hasoids) != NULL)
{
RangeVar *inh = lfirst_node(RangeVar, inher);
Relation rel;
int count;
/*
* 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;
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++)
foreach(inher, cxt->inhRelations)
{
Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
count);
char *inhname = NameStr(inhattr->attname);
RangeVar *inh = lfirst_node(RangeVar, inher);
Relation rel;
int count;
if (inhattr->attisdropped)
continue;
if (strcmp(key, inhname) == 0)
rel = heap_openrv(inh, AccessShareLock);
/* check user requested inheritance from valid relkind */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
inh->relname)));
for (count = 0; count < rel->rd_att->natts; count++)
{
found = true;
Form_pg_attribute inhattr = TupleDescAttr(rel->rd_att,
count);
char *inhname = NameStr(inhattr->attname);
/*
* 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;
if (inhattr->attisdropped)
continue;
if (strcmp(key, inhname) == 0)
{
found = true;
/*
* We currently have no easy way to force an
* inherited column to be NOT NULL at creation, if
* its parent wasn't so already. We leave it to
* DefineIndex to fix things up in this case.
*/
break;
}
}
heap_close(rel, NoLock);
if (found)
break;
}
heap_close(rel, NoLock);
if (found)
break;
}
}
@@ -2198,27 +2365,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
errmsg("column \"%s\" named in key does not exist", key),
parser_errposition(cxt->pstate, constraint->location)));
/* Check for PRIMARY KEY(foo, foo) */
foreach(columns, index->indexParams)
{
iparam = (IndexElem *) lfirst(columns);
if (iparam->name && strcmp(key, iparam->name) == 0)
{
if (index->primary)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" appears twice in primary key constraint",
key),
parser_errposition(cxt->pstate, constraint->location)));
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" appears twice in unique constraint",
key),
parser_errposition(cxt->pstate, constraint->location)));
}
}
/* OK, add it to the index definition */
iparam = makeNode(IndexElem);
iparam->name = pstrdup(key);
@@ -2226,9 +2372,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
iparam->indexcolname = NULL;
iparam->collation = NIL;
iparam->opclass = NIL;
iparam->ordering = SORTBY_DEFAULT;
iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
index->indexParams = lappend(index->indexParams, iparam);
index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
}
return index;