diff options
Diffstat (limited to 'src/backend/commands/indexcmds.c')
-rw-r--r-- | src/backend/commands/indexcmds.c | 246 |
1 files changed, 211 insertions, 35 deletions
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 507bcf50d5f..efcc70c6856 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.117 2003/12/28 21:57:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.118 2004/05/05 04:48:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,6 +31,7 @@ #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" +#include "parser/analyze.h" #include "parser/parsetree.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" @@ -38,38 +39,62 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/relcache.h" #include "utils/syscache.h" /* non-export function prototypes */ static void CheckPredicate(Expr *predicate); static void ComputeIndexAttrs(IndexInfo *indexInfo, Oid *classOidP, - List *attList, - Oid relId, - char *accessMethodName, Oid accessMethodId); + List *attList, + Oid relId, + char *accessMethodName, Oid accessMethodId, + bool isconstraint); static Oid GetIndexOpClass(List *opclass, Oid attrType, char *accessMethodName, Oid accessMethodId); static Oid GetDefaultOpClass(Oid attrType, Oid accessMethodId); +static char *CreateIndexName(const char *table_name, const char *column_name, + const char *label, Oid inamespace); +static bool relationHasPrimaryKey(Relation rel); + /* * DefineIndex * Creates a new index. * - * 'attributeList' is a list of IndexElem specifying columns and expressions + * 'heapRelation': the relation the index will apply to. + * 'indexRelationName': the name for the new index, or NULL to indicate + * that a nonconflicting default name should be picked. + * 'accessMethodName': name of the AM to use. + * 'attributeList': a list of IndexElem specifying columns and expressions * to index on. - * 'predicate' is the qual specified in the where clause. - * 'rangetable' is needed to interpret the predicate. + * 'predicate': the partial-index condition, or NULL if none. + * 'rangetable': needed to interpret the predicate. + * 'unique': make the index enforce uniqueness. + * 'primary': mark the index as a primary key in the catalogs. + * 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint, + * so build a pg_constraint entry for it. + * 'is_alter_table': this is due to an ALTER rather than a CREATE operation. + * 'check_rights': check for CREATE rights in the namespace. (This should + * be true except when ALTER is deleting/recreating an index.) + * 'skip_build': make the catalog entries but leave the index file empty; + * it will be filled later. + * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints. */ void DefineIndex(RangeVar *heapRelation, char *indexRelationName, char *accessMethodName, List *attributeList, + Expr *predicate, + List *rangetable, bool unique, bool primary, bool isconstraint, - Expr *predicate, - List *rangetable) + bool is_alter_table, + bool check_rights, + bool skip_build, + bool quiet) { Oid *classObjectId; Oid accessMethodId; @@ -111,15 +136,13 @@ DefineIndex(RangeVar *heapRelation, relationId = RelationGetRelid(rel); namespaceId = RelationGetNamespace(rel); - heap_close(rel, NoLock); - /* * Verify we (still) have CREATE rights in the rel's namespace. * (Presumably we did when the rel was created, but maybe not - * anymore.) Skip check if bootstrapping, since permissions machinery - * may not be working yet. + * anymore.) Skip check if caller doesn't want it. Also skip check + * if bootstrapping, since permissions machinery may not be working yet. */ - if (!IsBootstrapProcessingMode()) + if (check_rights && !IsBootstrapProcessingMode()) { AclResult aclresult; @@ -131,6 +154,27 @@ DefineIndex(RangeVar *heapRelation, } /* + * Select name for index if caller didn't specify + */ + if (indexRelationName == NULL) + { + if (primary) + indexRelationName = CreateIndexName(RelationGetRelationName(rel), + NULL, + "pkey", + namespaceId); + else + { + IndexElem *iparam = (IndexElem *) lfirst(attributeList); + + indexRelationName = CreateIndexName(RelationGetRelationName(rel), + iparam->name, + "key", + namespaceId); + } + } + + /* * look up the access method, verify it can handle the requested * features */ @@ -177,13 +221,33 @@ DefineIndex(RangeVar *heapRelation, CheckPredicate(predicate); /* - * Check that all of the attributes in a primary key are marked as not - * null, otherwise attempt to ALTER TABLE .. SET NOT NULL + * Extra checks when creating a PRIMARY KEY index. */ if (primary) { + List *cmds; List *keys; + /* + * If ALTER TABLE, check that there isn't already a PRIMARY KEY. + * In CREATE TABLE, we have faith that the parser rejected multiple + * pkey clauses; and CREATE INDEX doesn't have a way to say + * PRIMARY KEY, so it's no problem either. + */ + if (is_alter_table && + relationHasPrimaryKey(rel)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("multiple primary keys for table \"%s\" are not allowed", + RelationGetRelationName(rel)))); + } + + /* + * Check that all of the attributes in a primary key are marked as not + * null, otherwise attempt to ALTER TABLE .. SET NOT NULL + */ + cmds = NIL; foreach(keys, attributeList) { IndexElem *key = (IndexElem *) lfirst(keys); @@ -203,29 +267,43 @@ DefineIndex(RangeVar *heapRelation, { if (!((Form_pg_attribute) GETSTRUCT(atttuple))->attnotnull) { - /* - * Try to make it NOT NULL. - * - * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade - * to child tables? Currently, since the PRIMARY KEY - * itself doesn't cascade, we don't cascade the - * notnull constraint either; but this is pretty - * debatable. - */ - AlterTableAlterColumnSetNotNull(relationId, false, - key->name); + /* Add a subcommand to make this one NOT NULL */ + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + cmd->subtype = AT_SetNotNull; + cmd->name = key->name; + + cmds = lappend(cmds, cmd); } ReleaseSysCache(atttuple); } else { - /* This shouldn't happen if parser did its job ... */ + /* + * This shouldn't happen during CREATE TABLE, but can + * happen during ALTER TABLE. Keep message in sync with + * transformIndexConstraints() in parser/analyze.c. + */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" named in key does not exist", key->name))); } } + + /* + * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade + * to child tables? Currently, since the PRIMARY KEY + * itself doesn't cascade, we don't cascade the + * notnull constraint(s) either; but this is pretty debatable. + * + * XXX: possible future improvement: when being called from + * ALTER TABLE, it would be more efficient to merge this with + * the outer ALTER TABLE, so as to avoid two scans. But that + * seems to complicate DefineIndex's API unduly. + */ + if (cmds) + AlterTableInternal(relationId, cmds, false); } /* @@ -242,11 +320,26 @@ DefineIndex(RangeVar *heapRelation, classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); ComputeIndexAttrs(indexInfo, classObjectId, attributeList, - relationId, accessMethodName, accessMethodId); + relationId, accessMethodName, accessMethodId, + isconstraint); + + heap_close(rel, NoLock); + + /* + * Report index creation if appropriate (delay this till after most + * of the error checks) + */ + if (isconstraint && !quiet) + ereport(NOTICE, + (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"", + is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /", + primary ? "PRIMARY KEY" : "UNIQUE", + indexRelationName, RelationGetRelationName(rel)))); index_create(relationId, indexRelationName, indexInfo, accessMethodId, classObjectId, - primary, isconstraint, allowSystemTableMods); + primary, isconstraint, + allowSystemTableMods, skip_build); /* * We update the relation's pg_class tuple even if it already has @@ -303,7 +396,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo, List *attList, /* list of IndexElem's */ Oid relId, char *accessMethodName, - Oid accessMethodId) + Oid accessMethodId, + bool isconstraint) { List *rest; int attn = 0; @@ -325,10 +419,19 @@ ComputeIndexAttrs(IndexInfo *indexInfo, Assert(attribute->expr == NULL); atttuple = SearchSysCacheAttName(relId, attribute->name); if (!HeapTupleIsValid(atttuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" does not exist", - attribute->name))); + { + /* difference in error message spellings is historical */ + if (isconstraint) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" named in key does not exist", + attribute->name))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" does not exist", + attribute->name))); + } attform = (Form_pg_attribute) GETSTRUCT(atttuple); indexInfo->ii_KeyAttrNumbers[attn] = attform->attnum; atttype = attform->atttypid; @@ -554,6 +657,79 @@ GetDefaultOpClass(Oid attrType, Oid accessMethodId) } /* + * Select a nonconflicting name for an index. + */ +static char * +CreateIndexName(const char *table_name, const char *column_name, + const char *label, Oid inamespace) +{ + int pass = 0; + char *iname = NULL; + char typename[NAMEDATALEN]; + + /* + * The type name for makeObjectName is label, or labelN if that's + * necessary to prevent collision with existing indexes. + */ + strncpy(typename, label, sizeof(typename)); + + for (;;) + { + iname = makeObjectName(table_name, column_name, typename); + + if (!OidIsValid(get_relname_relid(iname, inamespace))) + break; + + /* found a conflict, so try a new name component */ + pfree(iname); + snprintf(typename, sizeof(typename), "%s%d", label, ++pass); + } + + return iname; +} + +/* + * relationHasPrimaryKey - + * + * See whether an existing relation has a primary key. + */ +static bool +relationHasPrimaryKey(Relation rel) +{ + bool result = false; + List *indexoidlist, + *indexoidscan; + + /* + * Get the list of index OIDs for the table from the relcache, and + * look up each one in the pg_index syscache until we find one marked + * primary key (hopefully there isn't more than one such). + */ + indexoidlist = RelationGetIndexList(rel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsto(indexoidscan); + HeapTuple indexTuple; + + indexTuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) /* should not happen */ + elog(ERROR, "cache lookup failed for index %u", indexoid); + result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary; + ReleaseSysCache(indexTuple); + if (result) + break; + } + + freeList(indexoidlist); + + return result; +} + + +/* * RemoveIndex * Deletes an index. */ |