aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/indexcmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/indexcmds.c')
-rw-r--r--src/backend/commands/indexcmds.c246
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.
*/