mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
CREATE INDEX ... INCLUDING (column[, ...])
Now indexes (but only B-tree for now) can contain "extra" column(s) which doesn't participate in index structure, they are just stored in leaf tuples. It allows to use index only scan by using single index instead of two or more indexes. Author: Anastasia Lubennikova with minor editorializing by me Reviewers: David Rowley, Peter Geoghegan, Jeff Janes
This commit is contained in:
@ -920,7 +920,7 @@ transformOnConflictClause(ParseState *pstate,
|
||||
* relation.
|
||||
*/
|
||||
Assert(pstate->p_next_resno == 1);
|
||||
for (attno = 0; attno < targetrel->rd_rel->relnatts; attno++)
|
||||
for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
|
||||
{
|
||||
Form_pg_attribute attr = targetrel->rd_att->attrs[attno];
|
||||
char *name;
|
||||
@ -2122,8 +2122,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;
|
||||
|
@ -356,6 +356,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
|
||||
optincluding opt_including 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
|
||||
@ -372,6 +373,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
create_generic_options alter_generic_options
|
||||
relation_expr_list dostmt_opt_list
|
||||
transform_element_list transform_type_list
|
||||
optcincluding opt_c_including
|
||||
|
||||
%type <list> group_by_list
|
||||
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
|
||||
@ -3217,17 +3219,18 @@ ConstraintElem:
|
||||
n->initially_valid = !n->skip_validation;
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
|
||||
| UNIQUE '(' columnList ')' opt_c_including 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;
|
||||
@ -3238,6 +3241,7 @@ ConstraintElem:
|
||||
n->contype = CONSTR_UNIQUE;
|
||||
n->location = @1;
|
||||
n->keys = NIL;
|
||||
n->including = NIL;
|
||||
n->options = NIL;
|
||||
n->indexname = $2;
|
||||
n->indexspace = NULL;
|
||||
@ -3246,17 +3250,18 @@ ConstraintElem:
|
||||
NULL, yyscanner);
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
|
||||
| PRIMARY KEY '(' columnList ')' opt_c_including 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;
|
||||
@ -3267,6 +3272,7 @@ ConstraintElem:
|
||||
n->contype = CONSTR_PRIMARY;
|
||||
n->location = @1;
|
||||
n->keys = NIL;
|
||||
n->including = NIL;
|
||||
n->options = NIL;
|
||||
n->indexname = $3;
|
||||
n->indexspace = NULL;
|
||||
@ -3334,6 +3340,13 @@ columnElem: ColId
|
||||
}
|
||||
;
|
||||
|
||||
opt_c_including: INCLUDING optcincluding { $$ = $2; }
|
||||
| /* EMPTY */ { $$ = NIL; }
|
||||
;
|
||||
|
||||
optcincluding : '(' columnList ')' { $$ = $2; }
|
||||
;
|
||||
|
||||
key_match: MATCH FULL
|
||||
{
|
||||
$$ = FKCONSTR_MATCH_FULL;
|
||||
@ -6626,7 +6639,7 @@ defacl_privilege_target:
|
||||
|
||||
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
||||
ON qualified_name access_method_clause '(' index_params ')'
|
||||
opt_reloptions OptTableSpace where_clause
|
||||
opt_including opt_reloptions OptTableSpace where_clause
|
||||
{
|
||||
IndexStmt *n = makeNode(IndexStmt);
|
||||
n->unique = $2;
|
||||
@ -6635,9 +6648,10 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
||||
n->relation = $7;
|
||||
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;
|
||||
@ -6652,7 +6666,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
||||
}
|
||||
| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
|
||||
ON qualified_name access_method_clause '(' index_params ')'
|
||||
opt_reloptions OptTableSpace where_clause
|
||||
opt_including opt_reloptions OptTableSpace where_clause
|
||||
{
|
||||
IndexStmt *n = makeNode(IndexStmt);
|
||||
n->unique = $2;
|
||||
@ -6661,9 +6675,10 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
|
||||
n->relation = $10;
|
||||
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;
|
||||
@ -6742,6 +6757,16 @@ index_elem: ColId opt_collate opt_class opt_asc_desc opt_nulls_order
|
||||
}
|
||||
;
|
||||
|
||||
optincluding : '(' index_including_params ')' { $$ = $2; }
|
||||
;
|
||||
opt_including: INCLUDING optincluding { $$ = $2; }
|
||||
| /* 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; }
|
||||
;
|
||||
|
@ -2875,7 +2875,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 = rd->rd_att->attrs[i];
|
||||
|
||||
|
@ -898,7 +898,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
|
||||
* Generate default column list for INSERT.
|
||||
*/
|
||||
Form_pg_attribute *attr = pstate->p_target_relation->rd_att->attrs;
|
||||
int numcol = pstate->p_target_relation->rd_rel->relnatts;
|
||||
int numcol = RelationGetNumberOfAttributes(pstate->p_target_relation);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numcol; i++)
|
||||
|
@ -1242,14 +1242,14 @@ generateClonedIndexStmt(CreateStmtContext *cxt, 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];
|
||||
int16 opt = source_idx->rd_indoption[keyno];
|
||||
|
||||
iparam = makeNode(IndexElem);
|
||||
|
||||
if (AttributeNumberIsValid(attnum))
|
||||
@ -1331,6 +1331,38 @@ generateClonedIndexStmt(CreateStmtContext *cxt, 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];
|
||||
|
||||
iparam = makeNode(IndexElem);
|
||||
|
||||
if (AttributeNumberIsValid(attnum))
|
||||
{
|
||||
/* Simple index column */
|
||||
char *attname;
|
||||
|
||||
attname = get_relid_attribute_name(indrelid, attnum);
|
||||
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(attrs[keyno]->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);
|
||||
@ -1523,6 +1555,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 &&
|
||||
@ -1594,6 +1627,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;
|
||||
@ -1743,24 +1777,30 @@ 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));
|
||||
}
|
||||
else
|
||||
constraint->including = lappend(constraint->including, makeString(attname));
|
||||
|
||||
constraint->keys = lappend(constraint->keys, makeString(attname));
|
||||
}
|
||||
|
||||
/* Close the index relation but keep the lock */
|
||||
@ -1773,6 +1813,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
||||
* If it's an EXCLUDE constraint, the grammar returns a list of pairs of
|
||||
* IndexElems and operator names. We have to break that apart into
|
||||
* separate lists.
|
||||
* NOTE that exclusion constraints don't support included nonkey attributes
|
||||
*/
|
||||
if (constraint->contype == CONSTR_EXCLUSION)
|
||||
{
|
||||
@ -1927,6 +1968,48 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
||||
index->indexParams = lappend(index->indexParams, iparam);
|
||||
}
|
||||
|
||||
/* Here is some ugly code duplication. But we do need it. */
|
||||
foreach(lc, constraint->including)
|
||||
{
|
||||
char *key = strVal(lfirst(lc));
|
||||
bool found = false;
|
||||
ColumnDef *column = NULL;
|
||||
ListCell *columns;
|
||||
IndexElem *iparam;
|
||||
|
||||
foreach(columns, cxt->columns)
|
||||
{
|
||||
column = (ColumnDef *) lfirst(columns);
|
||||
Assert(IsA(column, ColumnDef));
|
||||
if (strcmp(column->colname, key) == 0)
|
||||
{
|
||||
found = true;
|
||||
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)));
|
||||
|
||||
/* 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;
|
||||
index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user