Patch to add a primary key using an existing index

Started by Gurjeet Singhover 15 years ago50 messages
#1Gurjeet Singh
singh.gurjeet@gmail.com
2 attachment(s)

This is a continuation from this thread:
http://archives.postgresql.org/pgsql-hackers/2010-09/msg02153.php

The attached patch allows creating a primary key using an existing index.

This capability would be helpful in situations where one wishes to
rebuild/reindex the primary key, but associated downtime is not desirable.
It also allows one to create a table and start using it, while creating a
unique index 'concurrently' and later adding the primary key using the
concurrently built index. Maybe pg_dump can also use it.

The command syntax is:

ALTER TABLE sometable ADD PRIMARY KEY( col1, col2 ) WITH ( INDEX =
'indexname' );

A typical use case:

CREATE INDEX CONCURRENTLY new_pkey_idx ON sometable( a, b );

ALTER TABLE sometable ADD PRIMARY KEY ( a, b ) WITH (INDEX = 'new_pkey_idx'
);

- OR -

ALTER TABLE sometable DROP CONSTRAINT sometable_pkey,
ADD PRIMARY KEY ( a, b ) WITH (INDEX = 'new_pkey_idx' );

Notes for the reviewers:
------------------------

Don't be scared by the size of changes to index.c :) These are mostly
indentation diffs. I have attached two versions of the patch: one is context
diff, and the other is the same except ignoring whitespace changes.

The pseudocode is as follows:

In ATExecAddIndex()
If this ALTER command specifies a PRIMARY KEY
Call get_pkey_index_oid() to perform checks.

In get_pkey_index_oid()
Look for the WITH INDEX option
Reject
if more than one WITH INDEX clause specified
if the index doesn't exist or not found in table's schema
if the index is associated with any CONSTRAINT
if index is not ready or not valid (CONCURRENT buiild? Canceled
CONCURRENT?)
if index is on some other table
if index is not unique
if index is an expression index
if index is a partial index
if index columns do not match the PRIMARY KEY clause in the command
if index is not B-tree
If PRIMARY KEY clause doesn't have a constraint name, assign it one.
(code comments explain why)
Rename the index to match constraint name in the PRIMARY KEY clause

Back in ATExecAddIndex()
Use the index OID returned by get_pkey_index_oid() to tell DefineIndex()
to not create index.
Now mark the index as having 'indisprimary' flag.

In DefineIndex() and index_create() APIs
pass an additional flag: index_exists
Skip various actions based on this flag.

The patch contains a few tests, and doesn't yet have a docs patch.

The development branch is at
http://github.com/gurjeet/postgres/tree/replace_pkey_index

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Attachments:

add_pkey_with_index.patchtext/x-diff; charset=US-ASCII; name=add_pkey_with_index.patchDownload
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index e475403..0bfc2fd 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -288,7 +288,7 @@ Boot_DeclareIndexStmt:
 								$10,
 								NULL, NIL, NIL,
 								false, false, false, false, false,
-								false, false, true, false, false);
+								false, false, true, false, false, false);
 					do_end();
 				}
 		;
@@ -306,7 +306,7 @@ Boot_DeclareUniqueIndexStmt:
 								$11,
 								NULL, NIL, NIL,
 								true, false, false, false, false,
-								false, false, true, false, false);
+								false, false, true, false, false, false);
 					do_end();
 				}
 		;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 2b92e46..f919040 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -512,6 +512,7 @@ UpdateIndexRelation(Oid indexoid,
  * allow_system_table_mods: allow table to be a system catalog
  * skip_build: true to skip the index_build() step for the moment; caller
  *		must do it later (typically via reindex_index())
+ * index_exists: the index already exists, we are here just for formalities.
  * concurrent: if true, do not lock the table against writers.	The index
  *		will be marked "invalid" and the caller must take additional steps
  *		to fix it up.
@@ -535,6 +536,7 @@ index_create(Oid heapRelationId,
 			 bool initdeferred,
 			 bool allow_system_table_mods,
 			 bool skip_build,
+			 bool index_exists,
 			 bool concurrent)
 {
 	Relation	pg_class;
@@ -615,7 +617,8 @@ index_create(Oid heapRelationId,
 	if (shared_relation && tableSpaceId != GLOBALTABLESPACE_OID)
 		elog(ERROR, "shared relations must be placed in pg_global tablespace");
 
-	if (get_relname_relid(indexRelationName, namespaceId))
+	/* We don't check existence if index already exists */
+	if (!index_exists && get_relname_relid(indexRelationName, namespaceId))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_TABLE),
 				 errmsg("relation \"%s\" already exists",
@@ -653,78 +656,90 @@ index_create(Oid heapRelationId,
 		}
 	}
 
-	/*
-	 * create the index relation's relcache entry and physical disk file. (If
-	 * we fail further down, it's the smgr's responsibility to remove the disk
-	 * file again.)
-	 */
-	indexRelation = heap_create(indexRelationName,
-								namespaceId,
-								tableSpaceId,
-								indexRelationId,
-								indexTupDesc,
-								RELKIND_INDEX,
-								shared_relation,
-								mapped_relation,
-								allow_system_table_mods);
+	if (index_exists)
+	{
+		Assert( skip_build && !IsBootstrapProcessingMode() );
 
-	Assert(indexRelationId == RelationGetRelid(indexRelation));
+		indexRelation = relation_open(indexRelationId, AccessExclusiveLock);
 
-	/*
-	 * Obtain exclusive lock on it.  Although no other backends can see it
-	 * until we commit, this prevents deadlock-risk complaints from lock
-	 * manager in cases such as CLUSTER.
-	 */
-	LockRelation(indexRelation, AccessExclusiveLock);
+		/* done with pg_class */
+		heap_close(pg_class, RowExclusiveLock);
+	}
+	else
+	{
+		/*
+		 * create the index relation's relcache entry and physical disk file. (If
+		 * we fail further down, it's the smgr's responsibility to remove the disk
+		 * file again.)
+		 */
+		indexRelation = heap_create(indexRelationName,
+									namespaceId,
+									tableSpaceId,
+									indexRelationId,
+									indexTupDesc,
+									RELKIND_INDEX,
+									shared_relation,
+									mapped_relation,
+									allow_system_table_mods);
+
+		Assert(indexRelationId == RelationGetRelid(indexRelation));
 
-	/*
-	 * Fill in fields of the index's pg_class entry that are not set correctly
-	 * by heap_create.
-	 *
-	 * XXX should have a cleaner way to create cataloged indexes
-	 */
-	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
-	indexRelation->rd_rel->relam = accessMethodObjectId;
-	indexRelation->rd_rel->relkind = RELKIND_INDEX;
-	indexRelation->rd_rel->relhasoids = false;
-	indexRelation->rd_rel->relhasexclusion = is_exclusion;
+		/*
+		 * Obtain exclusive lock on it.  Although no other backends can see it
+		 * until we commit, this prevents deadlock-risk complaints from lock
+		 * manager in cases such as CLUSTER.
+		 */
+		LockRelation(indexRelation, AccessExclusiveLock);
 
-	/*
-	 * store index's pg_class entry
-	 */
-	InsertPgClassTuple(pg_class, indexRelation,
-					   RelationGetRelid(indexRelation),
-					   (Datum) 0,
-					   reloptions);
+		/*
+		 * Fill in fields of the index's pg_class entry that are not set correctly
+		 * by heap_create.
+		 *
+		 * XXX should have a cleaner way to create cataloged indexes
+		 */
+		indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
+		indexRelation->rd_rel->relam = accessMethodObjectId;
+		indexRelation->rd_rel->relkind = RELKIND_INDEX;
+		indexRelation->rd_rel->relhasoids = false;
+		indexRelation->rd_rel->relhasexclusion = is_exclusion;
 
-	/* done with pg_class */
-	heap_close(pg_class, RowExclusiveLock);
+		/*
+		 * store index's pg_class entry
+		 */
+		InsertPgClassTuple(pg_class, indexRelation,
+						   RelationGetRelid(indexRelation),
+						   (Datum) 0,
+						   reloptions);
 
-	/*
-	 * now update the object id's of all the attribute tuple forms in the
-	 * index relation's tuple descriptor
-	 */
-	InitializeAttributeOids(indexRelation,
-							indexInfo->ii_NumIndexAttrs,
-							indexRelationId);
+		/* done with pg_class */
+		heap_close(pg_class, RowExclusiveLock);
 
-	/*
-	 * append ATTRIBUTE tuples for the index
-	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs);
+		/*
+		 * now update the object id's of all the attribute tuple forms in the
+		 * index relation's tuple descriptor
+		 */
+		InitializeAttributeOids(indexRelation,
+								indexInfo->ii_NumIndexAttrs,
+								indexRelationId);
 
-	/* ----------------
-	 *	  update pg_index
-	 *	  (append INDEX tuple)
-	 *
-	 *	  Note that this stows away a representation of "predicate".
-	 *	  (Or, could define a rule to maintain the predicate) --Nels, Feb '92
-	 * ----------------
-	 */
-	UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
-						classObjectId, coloptions, isprimary,
-						!deferrable,
-						!concurrent);
+		/*
+		 * append ATTRIBUTE tuples for the index
+		 */
+		AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs);
+
+		/* ----------------
+		 *	  update pg_index
+		 *	  (append INDEX tuple)
+		 *
+		 *	  Note that this stows away a representation of "predicate".
+		 *	  (Or, could define a rule to maintain the predicate) --Nels, Feb '92
+		 * ----------------
+		 */
+		UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
+							classObjectId, coloptions, isprimary,
+							!deferrable,
+							!concurrent);
+	}
 
 	/*
 	 * Register constraint and dependencies for the index.
@@ -838,7 +853,7 @@ index_create(Oid heapRelationId,
 									 true);
 			}
 		}
-		else
+		else if(!index_exists)
 		{
 			bool		have_simple_col = false;
 
@@ -881,34 +896,37 @@ index_create(Oid heapRelationId,
 			Assert(!initdeferred);
 		}
 
-		/* Store dependency on operator classes */
-		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+		if (!index_exists)
 		{
-			referenced.classId = OperatorClassRelationId;
-			referenced.objectId = classObjectId[i];
-			referenced.objectSubId = 0;
+			/* Store dependency on operator classes */
+			for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+			{
+				referenced.classId = OperatorClassRelationId;
+				referenced.objectId = classObjectId[i];
+				referenced.objectSubId = 0;
 
-			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-		}
+				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+			}
 
-		/* Store dependencies on anything mentioned in index expressions */
-		if (indexInfo->ii_Expressions)
-		{
-			recordDependencyOnSingleRelExpr(&myself,
-										  (Node *) indexInfo->ii_Expressions,
-											heapRelationId,
-											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
-		}
+			/* Store dependencies on anything mentioned in index expressions */
+			if (indexInfo->ii_Expressions)
+			{
+				recordDependencyOnSingleRelExpr(&myself,
+											  (Node *) indexInfo->ii_Expressions,
+												heapRelationId,
+												DEPENDENCY_NORMAL,
+												DEPENDENCY_AUTO);
+			}
 
-		/* Store dependencies on anything mentioned in predicate */
-		if (indexInfo->ii_Predicate)
-		{
-			recordDependencyOnSingleRelExpr(&myself,
-											(Node *) indexInfo->ii_Predicate,
-											heapRelationId,
-											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO);
+			/* Store dependencies on anything mentioned in predicate */
+			if (indexInfo->ii_Predicate)
+			{
+				recordDependencyOnSingleRelExpr(&myself,
+												(Node *) indexInfo->ii_Predicate,
+												heapRelationId,
+												DEPENDENCY_NORMAL,
+												DEPENDENCY_AUTO);
+			}
 		}
 	}
 	else
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 7bf64e2..593db28 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -271,7 +271,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
 							   rel->rd_rel->reltablespace,
 							   classObjectId, coloptions, (Datum) 0,
 							   true, false, false, false,
-							   true, false, false);
+							   true, false, false, false);
 
 	/*
 	 * Store the toast table's OID in the parent relation's pg_class row
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9407d0f..65390d8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -122,6 +122,7 @@ DefineIndex(RangeVar *heapRelation,
 			bool is_alter_table,
 			bool check_rights,
 			bool skip_build,
+			bool index_exists,
 			bool quiet,
 			bool concurrent)
 {
@@ -483,6 +484,7 @@ DefineIndex(RangeVar *heapRelation,
 					 isconstraint, deferrable, initdeferred,
 					 allowSystemTableMods,
 					 skip_build || concurrent,
+					 index_exists,
 					 concurrent);
 
 	if (!concurrent)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 403e55a..6714612 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -336,7 +336,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
 				   ForkNumber forkNum, bool istemp);
 static const char *storage_name(char c);
-
+static Oid get_pkey_index_oid(IndexStmt *idx_stmt);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -4799,6 +4799,191 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		tab->new_changeoids = true;
 	}
 }
+/*
+ * If the PRIMARY KEY clasue has WITH INDEX option set, then prepare to use
+ * that index as the primary key.
+ */
+static Oid
+get_pkey_index_oid(IndexStmt *idx_stmt)
+{
+	Oid			index_oid = InvalidOid;	/* Oid of the index to create/replace primary key with */
+	ListCell   *l;
+	ListCell   *prev = NULL;
+	ListCell   *option = NULL;
+	Relation	rel;
+
+	rel = relation_openrv(idx_stmt->relation, AccessExclusiveLock);
+
+	foreach(l, idx_stmt->options)
+	{
+		DefElem		   *def = (DefElem*)lfirst(l);
+		int				i;
+		char		   *index_name;
+		ListCell	   *cell;
+		Relation		index_rel;
+		Form_pg_index	index_form;
+
+		if (def->defnamespace != NULL || strcmp(def->defname, "index") != 0)
+		{
+			prev = l;
+			continue;
+		}
+
+		option = l;
+
+		/*
+		 * If we don't do this, WITH INDEX option will reach DefineIndex(), and
+		 * it will throw a fit.
+		 */
+		if (OidIsValid(index_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("only one WITH INDEX option can be specified for a primary key")));
+
+		if (!IsA(def->arg, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("syntax error"),
+						errdetail("WITH INDEX option for primary key should be a string value.")));
+
+		index_name = strVal(def->arg);
+
+		/* Look for the index in the same schema as the table */
+		index_oid = get_relname_relid(index_name, RelationGetNamespace(rel));
+
+		if (!OidIsValid(index_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("relation \"%s\" not found", index_name)));
+
+		/* This will throw an error if it is not an index */
+		index_rel = index_open(index_oid, AccessExclusiveLock);
+
+		/* Check that it does not have an associated constraint */
+		if (OidIsValid(get_index_constraint(index_oid)))
+			ereport(ERROR,
+					(errmsg("index \"%s\" is associated with a constraint",
+								index_name)));
+
+		/* Perform validity checks on the index */
+		index_form = index_rel->rd_index;
+
+		if (!index_form->indisvalid)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("index \"%s\" is not valid", index_name)));
+
+		if (!index_form->indisready)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("index \"%s\" is not ready", index_name)));
+
+		if (index_form->indrelid != RelationGetRelid(rel))
+			elog(ERROR, "index \"%s\" belongs some other table", index_name);
+
+		if (!index_form->indisunique)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is not a unique index", index_name),
+					errdetail("cannot create primary key using a non-unique index.")));
+
+		if (index_rel->rd_indextuple != NULL &&
+			!heap_attisnull(index_rel->rd_indextuple, Anum_pg_index_indexprs))
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("index \"%s\" contains expressions", index_name),
+					errdetail("cannot create primary key using an expression index.")));
+
+		if (index_rel->rd_indextuple != NULL &&
+			!heap_attisnull(index_rel->rd_indextuple, Anum_pg_index_indpred))
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partial index", index_name),
+					errdetail("cannot create primary key using a partial index.")));
+
+		/* Match the PRIMARY KEY clasue from the ALTER statement with the index */
+		if (index_form->indnatts != list_length(idx_stmt->indexParams))
+			elog(ERROR, "primary key definition does not match the index");
+
+		/* XXX: Assert here? */
+		if (index_form->indnatts > rel->rd_att->natts)
+			elog(ERROR, "index \"%s\" has more columns than the table",
+							index_name);
+
+		i = 0;
+		foreach(cell, idx_stmt->indexParams)
+		{
+			IndexElem  *elem = (IndexElem*)lfirst(cell);
+			int16		attnum = index_form->indkey.values[i];
+			char	   *attname;
+
+			if (elem->name == NULL)
+			{
+				Assert(elem->expr != NULL);
+
+				/* Grammar already prevents this by disallowing expressions. */
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("PRIMARY KEY clause contains expressions"),
+						errdetail("cannot create primary key using an expression index.")));
+			}
+
+			/*
+			 * We need not worry about attisdropped, since this index's
+			 * existence guarantees that the column exists.
+			 */
+			attname = NameStr(rel->rd_att->attrs[attnum-1]->attname);
+
+			if (strcmp(elem->name, attname) != 0)
+				elog(ERROR, "index columns do not match primary key definition");
+
+			++i;
+		}
+
+		/* Currently only B-tree indexes are suupported for primary keys */
+		if (index_rel->rd_rel->relam != BTREE_AM_OID)
+			elog(ERROR, "\"%s\" is not a B-Tree index", index_name);
+
+		relation_close(index_rel, NoLock);
+
+		/*
+		 * Do not break out of the loop. Use this opprtunity to catch
+		 * multiple 'WITH INDEX' clauses.
+		 */
+	}
+
+	if (OidIsValid(index_oid))
+	{
+		/* Remove the WITH INDEX clause. DefineIndex() does not understand it.*/
+		idx_stmt->options = list_delete_cell(idx_stmt->options, option, prev);
+
+		/*
+		 * Assign a constraint name, if the clause doesn't have one.
+		 * If we don't do it here, that implies DefineIndex() will choose the
+		 * name, and after that it is too late to rename the index to match
+		 * constraint name since doing that will complain 'constraint already
+		 * exists'.
+		 */
+		if (idx_stmt->idxname == NULL)
+		{
+			idx_stmt->idxname = ChooseIndexName(RelationGetRelationName(rel),
+										RelationGetNamespace(rel),
+										NULL,
+										/* Don't need this, but it won't hurt */
+										idx_stmt->excludeOpNames,
+										true,
+										true);
+		}
+
+		/* Rename index to maintain consistency with the rest of the code */
+		RenameRelation(index_oid, idx_stmt->idxname, OBJECT_INDEX);
+
+		relation_close(rel, NoLock);
+
+	}
+
+	return index_oid;
+}
 
 /*
  * ALTER TABLE ADD INDEX
@@ -4814,6 +4999,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	bool		check_rights;
 	bool		skip_build;
 	bool		quiet;
+	Oid			index_oid = InvalidOid;
 
 	Assert(IsA(stmt, IndexStmt));
 
@@ -4824,11 +5010,26 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* suppress notices when rebuilding existing index */
 	quiet = is_rebuild;
 
+	if (stmt->primary && stmt->options)
+	{
+		index_oid = get_pkey_index_oid(stmt);
+
+		/* If we have the WITH INDEX option set, use that for the primary key */
+		if (OidIsValid(index_oid))
+		{
+			/* We override the params set above */
+			skip_build = true;
+
+			/* We don't want the 'will create implicit index' message */
+			quiet = true;
+		}
+	}
+
 	/* The IndexStmt has already been through transformIndexStmt */
 
 	DefineIndex(stmt->relation, /* relation */
 				stmt->idxname,	/* index name */
-				InvalidOid,		/* no predefined OID */
+				index_oid,		/* predefined OID, if any */
 				stmt->accessMethod,		/* am name */
 				stmt->tableSpace,
 				stmt->indexParams,		/* parameters */
@@ -4843,8 +5044,38 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 				true,			/* is_alter_table */
 				check_rights,
 				skip_build,
+				OidIsValid(index_oid),
 				quiet,
 				false);
+
+	/*
+	 * Mark the index as indisprimary. We can't do this before DefineIndex()
+	 * because it complains about duplicate primary key.
+	 */
+	if (stmt->primary && OidIsValid(index_oid))
+	{
+		Relation		pg_index;
+		HeapTuple		indexTuple;
+		Form_pg_index	indexForm;
+
+		pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+
+		indexTuple = SearchSysCacheCopy1(INDEXRELID,
+										 ObjectIdGetDatum(index_oid));
+		if (!HeapTupleIsValid(indexTuple))
+			elog(ERROR, "cache lookup failed for index %u", index_oid);
+		indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
+		indexForm->indisprimary = true;
+		simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
+		CatalogUpdateIndexes(pg_index, indexTuple);
+
+		heap_freetuple(indexTuple);
+		heap_close(pg_index, RowExclusiveLock);
+
+		/* Make these changes visible to later commands */
+		CommandCounterIncrement();
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 75cb354..59c52bd 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -903,6 +903,7 @@ standard_ProcessUtility(Node *parsetree,
 							false,		/* is_alter_table */
 							true,		/* check_rights */
 							false,		/* skip_build */
+							false,		/* index_exists */
 							false,		/* quiet */
 							stmt->concurrent);	/* concurrent */
 			}
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 66a6002..80ddce6 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -44,6 +44,7 @@ extern Oid index_create(Oid heapRelationId,
 			 bool initdeferred,
 			 bool allow_system_table_mods,
 			 bool skip_build,
+			 bool index_exists,
 			 bool concurrent);
 
 extern void index_drop(Oid indexId);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1dc1a7d..189e133 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -35,6 +35,7 @@ extern void DefineIndex(RangeVar *heapRelation,
 			bool is_alter_table,
 			bool check_rights,
 			bool skip_build,
+			bool index_exists,
 			bool quiet,
 			bool concurrent);
 extern void ReindexIndex(RangeVar *indexRelation);
diff --git a/src/test/regress/expected/pkey_with_index.out b/src/test/regress/expected/pkey_with_index.out
new file mode 100644
index 0000000..38141f0
--- /dev/null
+++ b/src/test/regress/expected/pkey_with_index.out
@@ -0,0 +1,59 @@
+CREATE TABLE rpi_test( a int , b varchar(10), c char);
+-- add some data so that all tests have something to work with.
+INSERT INTO rpi_test VALUES( 1, 2 );
+INSERT INTO rpi_test VALUES( 3, 4 );
+INSERT INTO rpi_test VALUES( 5, 6 );
+CREATE UNIQUE INDEX rpi_uniq_idx ON rpi_test(a , b);
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_uniq_idx');
+CREATE UNIQUE INDEX rpi_uniq2_idx ON rpi_test(b , a);
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD primary key(b, a) WITH (INDEX = 'rpi_uniq2_idx');
+DROP INDEX rpi_uniq_idx;
+ERROR:  index "rpi_uniq_idx" does not exist
+DROP INDEX rpi_uniq2_idx;
+ERROR:  index "rpi_uniq2_idx" does not exist
+-- Negative test cases
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 3 );
+ERROR:  syntax error
+DETAIL:  WITH INDEX option for primary key should be a string value.
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = del );
+ERROR:  syntax error
+DETAIL:  WITH INDEX option for primary key should be a string value.
+ALTER TABLE rpi_test ADD PRIMARY KEY(b, a) WITH (INDEX = 'rpi_idx_doesnt_exist');
+ERROR:  relation "rpi_idx_doesnt_exist" not found
+ALTER TABLE rpi_test ADD UNIQUE (a);
+NOTICE:  ALTER TABLE / ADD UNIQUE will create implicit index "rpi_test_a_key" for table "rpi_test"
+ALTER TABLE rpi_test ADD PRIMARY KEY(a) WITH (INDEX = 'rpi_test_a_key');
+ERROR:  index "rpi_test_a_key" is associated with a constraint
+CREATE INDEX rpi_idx1 ON rpi_test(a , b);
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx1'); -- should fail; non-unique
+ERROR:  "rpi_idx1" is not a unique index
+DETAIL:  cannot create primary key using a non-unique index.
+CREATE UNIQUE INDEX rpi_idx2 ON rpi_test(a , b);
+-- should fail; WITH INDEX option specified more than once.
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 'rpi_idx2', INDEX = 'rpi_idx2' );
+ERROR:  only one WITH INDEX option can be specified for a primary key
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx2');
+ERROR:  multiple primary keys for table "rpi_test" are not allowed
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey;
+ALTER TABLE rpi_test ADD primary key(a, b);
+NOTICE:  ALTER TABLE / ADD PRIMARY KEY will create implicit index "rpi_test_pkey" for table "rpi_test"
+CREATE UNIQUE INDEX rpi_idx3 ON rpi_test(a);
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD PRIMARY KEY(a, b) WITH (INDEX = 'rpi_idx3'); -- should fail
+ERROR:  primary key definition does not match the index
+DROP INDEX rpi_idx3;
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%' ORDER BY relkind DESC, relname ASC;
+    relname     | relkind 
+----------------+---------
+ rpi_test       | r
+ rpi_idx1       | i
+ rpi_idx2       | i
+ rpi_test_a_key | i
+ rpi_test_pkey  | i
+(5 rows)
+
+DROP TABLE rpi_test;
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%';
+ relname | relkind 
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3b99e86..83a1987 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -91,7 +91,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml pkey_with_index
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index b348f0e..74ed275 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -125,3 +125,4 @@ test: largeobject
 test: with
 test: xml
 test: stats
+test: pkey_with_index
diff --git a/src/test/regress/sql/pkey_with_index.sql b/src/test/regress/sql/pkey_with_index.sql
new file mode 100644
index 0000000..ec21b21
--- /dev/null
+++ b/src/test/regress/sql/pkey_with_index.sql
@@ -0,0 +1,54 @@
+
+CREATE TABLE rpi_test( a int , b varchar(10), c char);
+
+-- add some data so that all tests have something to work with.
+
+INSERT INTO rpi_test VALUES( 1, 2 );
+INSERT INTO rpi_test VALUES( 3, 4 );
+INSERT INTO rpi_test VALUES( 5, 6 );
+
+CREATE UNIQUE INDEX rpi_uniq_idx ON rpi_test(a , b);
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_uniq_idx');
+
+CREATE UNIQUE INDEX rpi_uniq2_idx ON rpi_test(b , a);
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD primary key(b, a) WITH (INDEX = 'rpi_uniq2_idx');
+
+DROP INDEX rpi_uniq_idx;
+DROP INDEX rpi_uniq2_idx;
+
+
+
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 3 );
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = del );
+ALTER TABLE rpi_test ADD PRIMARY KEY(b, a) WITH (INDEX = 'rpi_idx_doesnt_exist');
+
+ALTER TABLE rpi_test ADD UNIQUE (a);
+ALTER TABLE rpi_test ADD PRIMARY KEY(a) WITH (INDEX = 'rpi_test_a_key');
+
+CREATE INDEX rpi_idx1 ON rpi_test(a , b);
+
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx1'); -- should fail; non-unique
+
+CREATE UNIQUE INDEX rpi_idx2 ON rpi_test(a , b);
+
+-- should fail; WITH INDEX option specified more than once.
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 'rpi_idx2', INDEX = 'rpi_idx2' );
+
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx2');
+
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey;
+
+ALTER TABLE rpi_test ADD primary key(a, b);
+
+CREATE UNIQUE INDEX rpi_idx3 ON rpi_test(a);
+
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD PRIMARY KEY(a, b) WITH (INDEX = 'rpi_idx3'); -- should fail
+
+DROP INDEX rpi_idx3;
+
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%' ORDER BY relkind DESC, relname ASC;
+
+DROP TABLE rpi_test;
+
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%';
+
add_pkey_with_index.ignore_ws.patchtext/x-diff; charset=US-ASCII; name=add_pkey_with_index.ignore_ws.patchDownload
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index e475403..0bfc2fd 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -288,7 +288,7 @@ Boot_DeclareIndexStmt:
 								$10,
 								NULL, NIL, NIL,
 								false, false, false, false, false,
-								false, false, true, false, false);
+								false, false, true, false, false, false);
 					do_end();
 				}
 		;
@@ -306,7 +306,7 @@ Boot_DeclareUniqueIndexStmt:
 								$11,
 								NULL, NIL, NIL,
 								true, false, false, false, false,
-								false, false, true, false, false);
+								false, false, true, false, false, false);
 					do_end();
 				}
 		;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 2b92e46..f919040 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -512,6 +512,7 @@ UpdateIndexRelation(Oid indexoid,
  * allow_system_table_mods: allow table to be a system catalog
  * skip_build: true to skip the index_build() step for the moment; caller
  *		must do it later (typically via reindex_index())
+ * index_exists: the index already exists, we are here just for formalities.
  * concurrent: if true, do not lock the table against writers.	The index
  *		will be marked "invalid" and the caller must take additional steps
  *		to fix it up.
@@ -535,6 +536,7 @@ index_create(Oid heapRelationId,
 			 bool initdeferred,
 			 bool allow_system_table_mods,
 			 bool skip_build,
+			 bool index_exists,
 			 bool concurrent)
 {
 	Relation	pg_class;
@@ -615,7 +617,8 @@ index_create(Oid heapRelationId,
 	if (shared_relation && tableSpaceId != GLOBALTABLESPACE_OID)
 		elog(ERROR, "shared relations must be placed in pg_global tablespace");
 
-	if (get_relname_relid(indexRelationName, namespaceId))
+	/* We don't check existence if index already exists */
+	if (!index_exists && get_relname_relid(indexRelationName, namespaceId))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_TABLE),
 				 errmsg("relation \"%s\" already exists",
@@ -653,6 +656,17 @@ index_create(Oid heapRelationId,
 		}
 	}
 
+	if (index_exists)
+	{
+		Assert( skip_build && !IsBootstrapProcessingMode() );
+
+		indexRelation = relation_open(indexRelationId, AccessExclusiveLock);
+
+		/* done with pg_class */
+		heap_close(pg_class, RowExclusiveLock);
+	}
+	else
+	{
 	/*
 	 * create the index relation's relcache entry and physical disk file. (If
 	 * we fail further down, it's the smgr's responsibility to remove the disk
@@ -725,6 +739,7 @@ index_create(Oid heapRelationId,
 						classObjectId, coloptions, isprimary,
 						!deferrable,
 						!concurrent);
+	}
 
 	/*
 	 * Register constraint and dependencies for the index.
@@ -838,7 +853,7 @@ index_create(Oid heapRelationId,
 									 true);
 			}
 		}
-		else
+		else if(!index_exists)
 		{
 			bool		have_simple_col = false;
 
@@ -881,6 +896,8 @@ index_create(Oid heapRelationId,
 			Assert(!initdeferred);
 		}
 
+		if (!index_exists)
+		{
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 		{
@@ -911,6 +928,7 @@ index_create(Oid heapRelationId,
 											DEPENDENCY_AUTO);
 		}
 	}
+	}
 	else
 	{
 		/* Bootstrap mode - assert we weren't asked for constraint support */
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 7bf64e2..593db28 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -271,7 +271,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
 							   rel->rd_rel->reltablespace,
 							   classObjectId, coloptions, (Datum) 0,
 							   true, false, false, false,
-							   true, false, false);
+							   true, false, false, false);
 
 	/*
 	 * Store the toast table's OID in the parent relation's pg_class row
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9407d0f..65390d8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -122,6 +122,7 @@ DefineIndex(RangeVar *heapRelation,
 			bool is_alter_table,
 			bool check_rights,
 			bool skip_build,
+			bool index_exists,
 			bool quiet,
 			bool concurrent)
 {
@@ -483,6 +484,7 @@ DefineIndex(RangeVar *heapRelation,
 					 isconstraint, deferrable, initdeferred,
 					 allowSystemTableMods,
 					 skip_build || concurrent,
+					 index_exists,
 					 concurrent);
 
 	if (!concurrent)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 403e55a..6714612 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -336,7 +336,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
 				   ForkNumber forkNum, bool istemp);
 static const char *storage_name(char c);
-
+static Oid get_pkey_index_oid(IndexStmt *idx_stmt);
 
 /* ----------------------------------------------------------------
  *		DefineRelation
@@ -4799,6 +4799,191 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 		tab->new_changeoids = true;
 	}
 }
+/*
+ * If the PRIMARY KEY clasue has WITH INDEX option set, then prepare to use
+ * that index as the primary key.
+ */
+static Oid
+get_pkey_index_oid(IndexStmt *idx_stmt)
+{
+	Oid			index_oid = InvalidOid;	/* Oid of the index to create/replace primary key with */
+	ListCell   *l;
+	ListCell   *prev = NULL;
+	ListCell   *option = NULL;
+	Relation	rel;
+
+	rel = relation_openrv(idx_stmt->relation, AccessExclusiveLock);
+
+	foreach(l, idx_stmt->options)
+	{
+		DefElem		   *def = (DefElem*)lfirst(l);
+		int				i;
+		char		   *index_name;
+		ListCell	   *cell;
+		Relation		index_rel;
+		Form_pg_index	index_form;
+
+		if (def->defnamespace != NULL || strcmp(def->defname, "index") != 0)
+		{
+			prev = l;
+			continue;
+		}
+
+		option = l;
+
+		/*
+		 * If we don't do this, WITH INDEX option will reach DefineIndex(), and
+		 * it will throw a fit.
+		 */
+		if (OidIsValid(index_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("only one WITH INDEX option can be specified for a primary key")));
+
+		if (!IsA(def->arg, String))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("syntax error"),
+						errdetail("WITH INDEX option for primary key should be a string value.")));
+
+		index_name = strVal(def->arg);
+
+		/* Look for the index in the same schema as the table */
+		index_oid = get_relname_relid(index_name, RelationGetNamespace(rel));
+
+		if (!OidIsValid(index_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("relation \"%s\" not found", index_name)));
+
+		/* This will throw an error if it is not an index */
+		index_rel = index_open(index_oid, AccessExclusiveLock);
+
+		/* Check that it does not have an associated constraint */
+		if (OidIsValid(get_index_constraint(index_oid)))
+			ereport(ERROR,
+					(errmsg("index \"%s\" is associated with a constraint",
+								index_name)));
+
+		/* Perform validity checks on the index */
+		index_form = index_rel->rd_index;
+
+		if (!index_form->indisvalid)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("index \"%s\" is not valid", index_name)));
+
+		if (!index_form->indisready)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					errmsg("index \"%s\" is not ready", index_name)));
+
+		if (index_form->indrelid != RelationGetRelid(rel))
+			elog(ERROR, "index \"%s\" belongs some other table", index_name);
+
+		if (!index_form->indisunique)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is not a unique index", index_name),
+					errdetail("cannot create primary key using a non-unique index.")));
+
+		if (index_rel->rd_indextuple != NULL &&
+			!heap_attisnull(index_rel->rd_indextuple, Anum_pg_index_indexprs))
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("index \"%s\" contains expressions", index_name),
+					errdetail("cannot create primary key using an expression index.")));
+
+		if (index_rel->rd_indextuple != NULL &&
+			!heap_attisnull(index_rel->rd_indextuple, Anum_pg_index_indpred))
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					errmsg("\"%s\" is a partial index", index_name),
+					errdetail("cannot create primary key using a partial index.")));
+
+		/* Match the PRIMARY KEY clasue from the ALTER statement with the index */
+		if (index_form->indnatts != list_length(idx_stmt->indexParams))
+			elog(ERROR, "primary key definition does not match the index");
+
+		/* XXX: Assert here? */
+		if (index_form->indnatts > rel->rd_att->natts)
+			elog(ERROR, "index \"%s\" has more columns than the table",
+							index_name);
+
+		i = 0;
+		foreach(cell, idx_stmt->indexParams)
+		{
+			IndexElem  *elem = (IndexElem*)lfirst(cell);
+			int16		attnum = index_form->indkey.values[i];
+			char	   *attname;
+
+			if (elem->name == NULL)
+			{
+				Assert(elem->expr != NULL);
+
+				/* Grammar already prevents this by disallowing expressions. */
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("PRIMARY KEY clause contains expressions"),
+						errdetail("cannot create primary key using an expression index.")));
+			}
+
+			/*
+			 * We need not worry about attisdropped, since this index's
+			 * existence guarantees that the column exists.
+			 */
+			attname = NameStr(rel->rd_att->attrs[attnum-1]->attname);
+
+			if (strcmp(elem->name, attname) != 0)
+				elog(ERROR, "index columns do not match primary key definition");
+
+			++i;
+		}
+
+		/* Currently only B-tree indexes are suupported for primary keys */
+		if (index_rel->rd_rel->relam != BTREE_AM_OID)
+			elog(ERROR, "\"%s\" is not a B-Tree index", index_name);
+
+		relation_close(index_rel, NoLock);
+
+		/*
+		 * Do not break out of the loop. Use this opprtunity to catch
+		 * multiple 'WITH INDEX' clauses.
+		 */
+	}
+
+	if (OidIsValid(index_oid))
+	{
+		/* Remove the WITH INDEX clause. DefineIndex() does not understand it.*/
+		idx_stmt->options = list_delete_cell(idx_stmt->options, option, prev);
+
+		/*
+		 * Assign a constraint name, if the clause doesn't have one.
+		 * If we don't do it here, that implies DefineIndex() will choose the
+		 * name, and after that it is too late to rename the index to match
+		 * constraint name since doing that will complain 'constraint already
+		 * exists'.
+		 */
+		if (idx_stmt->idxname == NULL)
+		{
+			idx_stmt->idxname = ChooseIndexName(RelationGetRelationName(rel),
+										RelationGetNamespace(rel),
+										NULL,
+										/* Don't need this, but it won't hurt */
+										idx_stmt->excludeOpNames,
+										true,
+										true);
+		}
+
+		/* Rename index to maintain consistency with the rest of the code */
+		RenameRelation(index_oid, idx_stmt->idxname, OBJECT_INDEX);
+
+		relation_close(rel, NoLock);
+
+	}
+
+	return index_oid;
+}
 
 /*
  * ALTER TABLE ADD INDEX
@@ -4814,6 +4999,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	bool		check_rights;
 	bool		skip_build;
 	bool		quiet;
+	Oid			index_oid = InvalidOid;
 
 	Assert(IsA(stmt, IndexStmt));
 
@@ -4824,11 +5010,26 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* suppress notices when rebuilding existing index */
 	quiet = is_rebuild;
 
+	if (stmt->primary && stmt->options)
+	{
+		index_oid = get_pkey_index_oid(stmt);
+
+		/* If we have the WITH INDEX option set, use that for the primary key */
+		if (OidIsValid(index_oid))
+		{
+			/* We override the params set above */
+			skip_build = true;
+
+			/* We don't want the 'will create implicit index' message */
+			quiet = true;
+		}
+	}
+
 	/* The IndexStmt has already been through transformIndexStmt */
 
 	DefineIndex(stmt->relation, /* relation */
 				stmt->idxname,	/* index name */
-				InvalidOid,		/* no predefined OID */
+				index_oid,		/* predefined OID, if any */
 				stmt->accessMethod,		/* am name */
 				stmt->tableSpace,
 				stmt->indexParams,		/* parameters */
@@ -4843,8 +5044,38 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 				true,			/* is_alter_table */
 				check_rights,
 				skip_build,
+				OidIsValid(index_oid),
 				quiet,
 				false);
+
+	/*
+	 * Mark the index as indisprimary. We can't do this before DefineIndex()
+	 * because it complains about duplicate primary key.
+	 */
+	if (stmt->primary && OidIsValid(index_oid))
+	{
+		Relation		pg_index;
+		HeapTuple		indexTuple;
+		Form_pg_index	indexForm;
+
+		pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+
+		indexTuple = SearchSysCacheCopy1(INDEXRELID,
+										 ObjectIdGetDatum(index_oid));
+		if (!HeapTupleIsValid(indexTuple))
+			elog(ERROR, "cache lookup failed for index %u", index_oid);
+		indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
+		indexForm->indisprimary = true;
+		simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
+		CatalogUpdateIndexes(pg_index, indexTuple);
+
+		heap_freetuple(indexTuple);
+		heap_close(pg_index, RowExclusiveLock);
+
+		/* Make these changes visible to later commands */
+		CommandCounterIncrement();
+	}
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 75cb354..59c52bd 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -903,6 +903,7 @@ standard_ProcessUtility(Node *parsetree,
 							false,		/* is_alter_table */
 							true,		/* check_rights */
 							false,		/* skip_build */
+							false,		/* index_exists */
 							false,		/* quiet */
 							stmt->concurrent);	/* concurrent */
 			}
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 66a6002..80ddce6 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -44,6 +44,7 @@ extern Oid index_create(Oid heapRelationId,
 			 bool initdeferred,
 			 bool allow_system_table_mods,
 			 bool skip_build,
+			 bool index_exists,
 			 bool concurrent);
 
 extern void index_drop(Oid indexId);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1dc1a7d..189e133 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -35,6 +35,7 @@ extern void DefineIndex(RangeVar *heapRelation,
 			bool is_alter_table,
 			bool check_rights,
 			bool skip_build,
+			bool index_exists,
 			bool quiet,
 			bool concurrent);
 extern void ReindexIndex(RangeVar *indexRelation);
diff --git a/src/test/regress/expected/pkey_with_index.out b/src/test/regress/expected/pkey_with_index.out
new file mode 100644
index 0000000..38141f0
--- /dev/null
+++ b/src/test/regress/expected/pkey_with_index.out
@@ -0,0 +1,59 @@
+CREATE TABLE rpi_test( a int , b varchar(10), c char);
+-- add some data so that all tests have something to work with.
+INSERT INTO rpi_test VALUES( 1, 2 );
+INSERT INTO rpi_test VALUES( 3, 4 );
+INSERT INTO rpi_test VALUES( 5, 6 );
+CREATE UNIQUE INDEX rpi_uniq_idx ON rpi_test(a , b);
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_uniq_idx');
+CREATE UNIQUE INDEX rpi_uniq2_idx ON rpi_test(b , a);
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD primary key(b, a) WITH (INDEX = 'rpi_uniq2_idx');
+DROP INDEX rpi_uniq_idx;
+ERROR:  index "rpi_uniq_idx" does not exist
+DROP INDEX rpi_uniq2_idx;
+ERROR:  index "rpi_uniq2_idx" does not exist
+-- Negative test cases
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 3 );
+ERROR:  syntax error
+DETAIL:  WITH INDEX option for primary key should be a string value.
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = del );
+ERROR:  syntax error
+DETAIL:  WITH INDEX option for primary key should be a string value.
+ALTER TABLE rpi_test ADD PRIMARY KEY(b, a) WITH (INDEX = 'rpi_idx_doesnt_exist');
+ERROR:  relation "rpi_idx_doesnt_exist" not found
+ALTER TABLE rpi_test ADD UNIQUE (a);
+NOTICE:  ALTER TABLE / ADD UNIQUE will create implicit index "rpi_test_a_key" for table "rpi_test"
+ALTER TABLE rpi_test ADD PRIMARY KEY(a) WITH (INDEX = 'rpi_test_a_key');
+ERROR:  index "rpi_test_a_key" is associated with a constraint
+CREATE INDEX rpi_idx1 ON rpi_test(a , b);
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx1'); -- should fail; non-unique
+ERROR:  "rpi_idx1" is not a unique index
+DETAIL:  cannot create primary key using a non-unique index.
+CREATE UNIQUE INDEX rpi_idx2 ON rpi_test(a , b);
+-- should fail; WITH INDEX option specified more than once.
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 'rpi_idx2', INDEX = 'rpi_idx2' );
+ERROR:  only one WITH INDEX option can be specified for a primary key
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx2');
+ERROR:  multiple primary keys for table "rpi_test" are not allowed
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey;
+ALTER TABLE rpi_test ADD primary key(a, b);
+NOTICE:  ALTER TABLE / ADD PRIMARY KEY will create implicit index "rpi_test_pkey" for table "rpi_test"
+CREATE UNIQUE INDEX rpi_idx3 ON rpi_test(a);
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD PRIMARY KEY(a, b) WITH (INDEX = 'rpi_idx3'); -- should fail
+ERROR:  primary key definition does not match the index
+DROP INDEX rpi_idx3;
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%' ORDER BY relkind DESC, relname ASC;
+    relname     | relkind 
+----------------+---------
+ rpi_test       | r
+ rpi_idx1       | i
+ rpi_idx2       | i
+ rpi_test_a_key | i
+ rpi_test_pkey  | i
+(5 rows)
+
+DROP TABLE rpi_test;
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%';
+ relname | relkind 
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3b99e86..83a1987 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -91,7 +91,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml pkey_with_index
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index b348f0e..74ed275 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -125,3 +125,4 @@ test: largeobject
 test: with
 test: xml
 test: stats
+test: pkey_with_index
diff --git a/src/test/regress/sql/pkey_with_index.sql b/src/test/regress/sql/pkey_with_index.sql
new file mode 100644
index 0000000..ec21b21
--- /dev/null
+++ b/src/test/regress/sql/pkey_with_index.sql
@@ -0,0 +1,54 @@
+
+CREATE TABLE rpi_test( a int , b varchar(10), c char);
+
+-- add some data so that all tests have something to work with.
+
+INSERT INTO rpi_test VALUES( 1, 2 );
+INSERT INTO rpi_test VALUES( 3, 4 );
+INSERT INTO rpi_test VALUES( 5, 6 );
+
+CREATE UNIQUE INDEX rpi_uniq_idx ON rpi_test(a , b);
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_uniq_idx');
+
+CREATE UNIQUE INDEX rpi_uniq2_idx ON rpi_test(b , a);
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD primary key(b, a) WITH (INDEX = 'rpi_uniq2_idx');
+
+DROP INDEX rpi_uniq_idx;
+DROP INDEX rpi_uniq2_idx;
+
+
+
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 3 );
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = del );
+ALTER TABLE rpi_test ADD PRIMARY KEY(b, a) WITH (INDEX = 'rpi_idx_doesnt_exist');
+
+ALTER TABLE rpi_test ADD UNIQUE (a);
+ALTER TABLE rpi_test ADD PRIMARY KEY(a) WITH (INDEX = 'rpi_test_a_key');
+
+CREATE INDEX rpi_idx1 ON rpi_test(a , b);
+
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx1'); -- should fail; non-unique
+
+CREATE UNIQUE INDEX rpi_idx2 ON rpi_test(a , b);
+
+-- should fail; WITH INDEX option specified more than once.
+ALTER TABLE rpi_test ADD PRIMARY KEY (a, b) WITH ( INDEX = 'rpi_idx2', INDEX = 'rpi_idx2' );
+
+ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx2');
+
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey;
+
+ALTER TABLE rpi_test ADD primary key(a, b);
+
+CREATE UNIQUE INDEX rpi_idx3 ON rpi_test(a);
+
+ALTER TABLE rpi_test DROP CONSTRAINT rpi_test_pkey, ADD PRIMARY KEY(a, b) WITH (INDEX = 'rpi_idx3'); -- should fail
+
+DROP INDEX rpi_idx3;
+
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%' ORDER BY relkind DESC, relname ASC;
+
+DROP TABLE rpi_test;
+
+SELECT relname, relkind FROM pg_class WHERE relname like E'rpi\\_%';
+
#2Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Gurjeet Singh (#1)
2 attachment(s)
Re: Patch to add a primary key using an existing index

On Sat, Oct 9, 2010 at 2:07 PM, Gurjeet Singh <singh.gurjeet@gmail.com>wrote:

This is a continuation from this thread:
http://archives.postgresql.org/pgsql-hackers/2010-09/msg02153.php

The attached patch allows creating a primary key using an existing index.

I have attached two versions of the patch: one is context diff, and the
other is the same except ignoring whitespace changes.

Attached are gzip'd patches for archives. Archive shows the previous mail
attachments all inline... horrible.
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Attachments:

add_pkey_with_index.patch.gzapplication/x-gzip; name=add_pkey_with_index.patch.gzDownload
add_pkey_with_index.ignore_ws.patch.gzapplication/x-gzip; name=add_pkey_with_index.ignore_ws.patch.gzDownload
#3Andrew Dunstan
andrew@dunslane.net
In reply to: Gurjeet Singh (#2)
Re: Patch to add a primary key using an existing index

On 10/09/2010 02:19 PM, Gurjeet Singh wrote:

On Sat, Oct 9, 2010 at 2:07 PM, Gurjeet Singh <singh.gurjeet@gmail.com
<mailto:singh.gurjeet@gmail.com>> wrote:

This is a continuation from this thread:
http://archives.postgresql.org/pgsql-hackers/2010-09/msg02153.php

The attached patch allows creating a primary key using an existing
index.

I have attached two versions of the patch: one is context diff,
and the other is the same except ignoring whitespace changes.

Attached are gzip'd patches for archives. Archive shows the previous
mail attachments all inline... horrible.

I wish we could get the archive processor to provide access to the
attachments even if they have a MIME type of text/whatever. That's a
horrid inefficiency. Maybe we could restrict it to text attachments that
have a Content-Type with a name attribute that contains the string
'patch', or a similar Content-Disposition filename attribute.

cheers

andrew

#4Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Andrew Dunstan (#3)
archives, attachments, etc (was: Patch to add a primary key using an existing index)

Andrew Dunstan <andrew@dunslane.net> writes:

I wish we could get the archive processor to provide access to the
attachments even if they have a MIME type of text/whatever. That's a
horrid inefficiency. Maybe we could restrict it to text attachments
that have a Content-Type with a name attribute that contains the
string 'patch', or a similar Content-Disposition filename attribute.

I wish our super admins would have some time to resume the work on the
new archives infrastructure, that was about ready for integration if not
prime time:

http://archives.beccati.org/pgsql-hackers/message/276290

As you see it doesn't suffer from this problem, the threading is not
split arbitrarily, and less obvious but it runs from a PostgreSQL
database. Yes, that means the threading code is exercising our recursive
querying facility, as far as I understand it.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#5Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Dimitri Fontaine (#4)
Re: archives, attachments, etc (was: Patch to add a primary key using an existing index)

On Sat, Oct 9, 2010 at 3:30 PM, Dimitri Fontaine <dimitri@2ndquadrant.fr>wrote:

Andrew Dunstan <andrew@dunslane.net> writes:

I wish we could get the archive processor to provide access to the
attachments even if they have a MIME type of text/whatever. That's a
horrid inefficiency. Maybe we could restrict it to text attachments
that have a Content-Type with a name attribute that contains the
string 'patch', or a similar Content-Disposition filename attribute.

I wish our super admins would have some time to resume the work on the
new archives infrastructure, that was about ready for integration if not
prime time:

http://archives.beccati.org/pgsql-hackers/message/276290

As you see it doesn't suffer from this problem, the threading is not
split arbitrarily, and less obvious but it runs from a PostgreSQL
database. Yes, that means the threading code is exercising our recursive
querying facility, as far as I understand it.

Something looks wrong with that thread. The message text in my mails is
missing. Perhaps that is contained in the .bin files but I can't tell as the
link leads to 404 Not Found.

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

#6Matteo Beccati
php@beccati.com
In reply to: Gurjeet Singh (#5)
Re: archives, attachments, etc

Hi Gurjeet,

On 09/10/2010 22:54, Gurjeet Singh wrote:

On Sat, Oct 9, 2010 at 3:30 PM, Dimitri Fontaine <dimitri@2ndquadrant.fr
<mailto:dimitri@2ndquadrant.fr>> wrote:
I wish our super admins would have some time to resume the work on the
new archives infrastructure, that was about ready for integration if not
prime time:

http://archives.beccati.org/pgsql-hackers/message/276290

As you see it doesn't suffer from this problem, the threading is not
split arbitrarily, and less obvious but it runs from a PostgreSQL
database. Yes, that means the threading code is exercising our recursive
querying facility, as far as I understand it.

Something looks wrong with that thread. The message text in my mails is
missing. Perhaps that is contained in the .bin files but I can't tell as
the link leads to 404 Not Found.

Thanks for the private email to point this thread out. I've been overly
busy lately and missed it.

I'll try to debug what happens with your message formatting as soon as I
can find some time.

Cheers
--
Matteo Beccati

Development & Consulting - http://www.beccati.com/

#7Jim Nasby
jim@nasby.net
In reply to: Gurjeet Singh (#1)
Re: Patch to add a primary key using an existing index

UNIQUE constraints suffer from the same behavior; feel like fixing that too? :)

On Oct 9, 2010, at 1:07 PM, Gurjeet Singh wrote:

This is a continuation from this thread: http://archives.postgresql.org/pgsql-hackers/2010-09/msg02153.php

The attached patch allows creating a primary key using an existing index.

This capability would be helpful in situations where one wishes to rebuild/reindex the primary key, but associated downtime is not desirable. It also allows one to create a table and start using it, while creating a unique index 'concurrently' and later adding the primary key using the concurrently built index. Maybe pg_dump can also use it.

The command syntax is:

ALTER TABLE sometable ADD PRIMARY KEY( col1, col2 ) WITH ( INDEX = 'indexname' );

A typical use case:

CREATE INDEX CONCURRENTLY new_pkey_idx ON sometable( a, b );

ALTER TABLE sometable ADD PRIMARY KEY ( a, b ) WITH (INDEX = 'new_pkey_idx' );

- OR -

ALTER TABLE sometable DROP CONSTRAINT sometable_pkey,
ADD PRIMARY KEY ( a, b ) WITH (INDEX = 'new_pkey_idx' );

Notes for the reviewers:
------------------------

Don't be scared by the size of changes to index.c :) These are mostly indentation diffs. I have attached two versions of the patch: one is context diff, and the other is the same except ignoring whitespace changes.

The pseudocode is as follows:

In ATExecAddIndex()
If this ALTER command specifies a PRIMARY KEY
Call get_pkey_index_oid() to perform checks.

In get_pkey_index_oid()
Look for the WITH INDEX option
Reject
if more than one WITH INDEX clause specified
if the index doesn't exist or not found in table's schema
if the index is associated with any CONSTRAINT
if index is not ready or not valid (CONCURRENT buiild? Canceled CONCURRENT?)
if index is on some other table
if index is not unique
if index is an expression index
if index is a partial index
if index columns do not match the PRIMARY KEY clause in the command
if index is not B-tree
If PRIMARY KEY clause doesn't have a constraint name, assign it one. (code comments explain why)
Rename the index to match constraint name in the PRIMARY KEY clause

Back in ATExecAddIndex()
Use the index OID returned by get_pkey_index_oid() to tell DefineIndex() to not create index.
Now mark the index as having 'indisprimary' flag.

In DefineIndex() and index_create() APIs
pass an additional flag: index_exists
Skip various actions based on this flag.

The patch contains a few tests, and doesn't yet have a docs patch.

The development branch is at http://github.com/gurjeet/postgres/tree/replace_pkey_index

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device
<add_pkey_with_index.patch><add_pkey_with_index.ignore_ws.patch>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Jim C. Nasby, Database Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

#8Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Jim Nasby (#7)
Re: Patch to add a primary key using an existing index

Depesz brought that to my attention a few days after the initial submission,
and adding support for UNIQUE was not much pain. I implemented it almost
immediately, but didn't announce it as I was hoping I could submit some doc
changes too with that.

If you are the adventurous kind, you can follow the Git branch here:
https://github.com/gurjeet/postgres/tree/replace_pkey_index

Regards,

On Mon, Nov 1, 2010 at 10:29 PM, Jim Nasby <jim@nasby.net> wrote:

UNIQUE constraints suffer from the same behavior; feel like fixing that
too? :)

On Oct 9, 2010, at 1:07 PM, Gurjeet Singh wrote:

This is a continuation from this thread:

http://archives.postgresql.org/pgsql-hackers/2010-09/msg02153.php

The attached patch allows creating a primary key using an existing index.

This capability would be helpful in situations where one wishes to

rebuild/reindex the primary key, but associated downtime is not desirable.
It also allows one to create a table and start using it, while creating a
unique index 'concurrently' and later adding the primary key using the
concurrently built index. Maybe pg_dump can also use it.

The command syntax is:

ALTER TABLE sometable ADD PRIMARY KEY( col1, col2 ) WITH ( INDEX =

'indexname' );

A typical use case:

CREATE INDEX CONCURRENTLY new_pkey_idx ON sometable( a, b );

ALTER TABLE sometable ADD PRIMARY KEY ( a, b ) WITH (INDEX =

'new_pkey_idx' );

- OR -

ALTER TABLE sometable DROP CONSTRAINT sometable_pkey,
ADD PRIMARY KEY ( a, b ) WITH (INDEX = 'new_pkey_idx' );

Notes for the reviewers:
------------------------

Don't be scared by the size of changes to index.c :) These are mostly

indentation diffs. I have attached two versions of the patch: one is context
diff, and the other is the same except ignoring whitespace changes.

The pseudocode is as follows:

In ATExecAddIndex()
If this ALTER command specifies a PRIMARY KEY
Call get_pkey_index_oid() to perform checks.

In get_pkey_index_oid()
Look for the WITH INDEX option
Reject
if more than one WITH INDEX clause specified
if the index doesn't exist or not found in table's schema
if the index is associated with any CONSTRAINT
if index is not ready or not valid (CONCURRENT buiild? Canceled

CONCURRENT?)

if index is on some other table
if index is not unique
if index is an expression index
if index is a partial index
if index columns do not match the PRIMARY KEY clause in the

command

if index is not B-tree
If PRIMARY KEY clause doesn't have a constraint name, assign it one.

(code comments explain why)

Rename the index to match constraint name in the PRIMARY KEY clause

Back in ATExecAddIndex()
Use the index OID returned by get_pkey_index_oid() to tell

DefineIndex() to not create index.

Now mark the index as having 'indisprimary' flag.

In DefineIndex() and index_create() APIs
pass an additional flag: index_exists
Skip various actions based on this flag.

The patch contains a few tests, and doesn't yet have a docs patch.

The development branch is at

http://github.com/gurjeet/postgres/tree/replace_pkey_index

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device
<add_pkey_with_index.patch><add_pkey_with_index.ignore_ws.patch>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Jim C. Nasby, Database Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

#9Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Gurjeet Singh (#8)
1 attachment(s)
Re: Patch to add a primary key using an existing index

Attached is the patch that extends the same feature for UNIQUE indexes.

It also includes some doc changes for the ALTER TABLE command, but I could
not verify the resulting changes since I don't have the doc-building
infrastructure installed.

Regards,

On Mon, Nov 8, 2010 at 1:39 AM, Gurjeet Singh <singh.gurjeet@gmail.com>wrote:

Depesz brought that to my attention a few days after the initial
submission, and adding support for UNIQUE was not much pain. I implemented
it almost immediately, but didn't announce it as I was hoping I could submit
some doc changes too with that.

If you are the adventurous kind, you can follow the Git branch here:
https://github.com/gurjeet/postgres/tree/replace_pkey_index

Regards,

On Mon, Nov 1, 2010 at 10:29 PM, Jim Nasby <jim@nasby.net> wrote:

UNIQUE constraints suffer from the same behavior; feel like fixing that
too? :)

On Oct 9, 2010, at 1:07 PM, Gurjeet Singh wrote:

This is a continuation from this thread:

http://archives.postgresql.org/pgsql-hackers/2010-09/msg02153.php

The attached patch allows creating a primary key using an existing

index.

This capability would be helpful in situations where one wishes to

rebuild/reindex the primary key, but associated downtime is not desirable.
It also allows one to create a table and start using it, while creating a
unique index 'concurrently' and later adding the primary key using the
concurrently built index. Maybe pg_dump can also use it.

The command syntax is:

ALTER TABLE sometable ADD PRIMARY KEY( col1, col2 ) WITH ( INDEX =

'indexname' );

A typical use case:

CREATE INDEX CONCURRENTLY new_pkey_idx ON sometable( a, b );

ALTER TABLE sometable ADD PRIMARY KEY ( a, b ) WITH (INDEX =

'new_pkey_idx' );

- OR -

ALTER TABLE sometable DROP CONSTRAINT sometable_pkey,
ADD PRIMARY KEY ( a, b ) WITH (INDEX = 'new_pkey_idx' );

Notes for the reviewers:
------------------------

Don't be scared by the size of changes to index.c :) These are mostly

indentation diffs. I have attached two versions of the patch: one is context
diff, and the other is the same except ignoring whitespace changes.

The pseudocode is as follows:

In ATExecAddIndex()
If this ALTER command specifies a PRIMARY KEY
Call get_pkey_index_oid() to perform checks.

In get_pkey_index_oid()
Look for the WITH INDEX option
Reject
if more than one WITH INDEX clause specified
if the index doesn't exist or not found in table's schema
if the index is associated with any CONSTRAINT
if index is not ready or not valid (CONCURRENT buiild? Canceled

CONCURRENT?)

if index is on some other table
if index is not unique
if index is an expression index
if index is a partial index
if index columns do not match the PRIMARY KEY clause in the

command

if index is not B-tree
If PRIMARY KEY clause doesn't have a constraint name, assign it one.

(code comments explain why)

Rename the index to match constraint name in the PRIMARY KEY clause

Back in ATExecAddIndex()
Use the index OID returned by get_pkey_index_oid() to tell

DefineIndex() to not create index.

Now mark the index as having 'indisprimary' flag.

In DefineIndex() and index_create() APIs
pass an additional flag: index_exists
Skip various actions based on this flag.

The patch contains a few tests, and doesn't yet have a docs patch.

The development branch is at

http://github.com/gurjeet/postgres/tree/replace_pkey_index

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device
<add_pkey_with_index.patch><add_pkey_with_index.ignore_ws.patch>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Jim C. Nasby, Database Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Attachments:

replace_pkey_index.uniq+doc.patch.gzapplication/x-gzip; name=replace_pkey_index.uniq+doc.patch.gzDownload
#10Steve Singer
ssinger_pg@sympatico.ca
In reply to: Gurjeet Singh (#9)
Re: Patch to add a primary key using an existing index

On 10-11-07 01:54 PM, Gurjeet Singh wrote:

Attached is the patch that extends the same feature for UNIQUE indexes.

It also includes some doc changes for the ALTER TABLE command, but I
could not verify the resulting changes since I don't have the
doc-building infrastructure installed.

Regards,

Gurjeet,

I've taken a stab at reviewing this.

Submission Review:
========================

Tests
--------
The expected output for the regression tests you added don't match
what I'm getting when I run the tests with your patch applied.
I think you just need to regenerate the expected results they seem
to be from a previous version of the patch (different error messages etc..).

Documentation
---------------

I was able to generate the docs.

The ALTER TABLE page under the synopsis has

ADD table_constraint

where table_constraint is defined on the CREATE TABLE page.
On the CREATE TABLE page table_constraint isn't defined as having the WITH
, the WITH is part of index_parameters.

I propose the alter table page instead have

ADD table_constraint [index_parameters]

where index_parameters also references the CREATE TABLE page like
table_constraint.

Usability Review
====================

Behaviour
-------------
I feel that if the ALTER TABLE ... renames the the index
a NOTICE should be generated. We generate notices about creating an
index for a new pkey. We should give them a notice that we are renaming
an index on them.

Coding Review:
======================

Error Messages
-----------------
in tablecmds your errdetail messages often don't start with a capital
letter. I belive the preference is to have the errdetail strings start
with a capital letter and end with a period.

tablecmds.c - get_constraint_index_oid

contains the check

/* Currently only B-tree indexes are suupported for primary keys */
if (index_rel->rd_rel->relam != BTREE_AM_OID)
elog(ERROR, "\"%s\" is not a B-Tree index", index_name);

but above we already validate that the index is a unique index with
another check. Today only B-tree indexes support unique constraints.
If this changed at some point and we could have a unique index of some
other type, would something in this patch need to be changed to support
them? If we are only depending on the uniqueness property then I think
this check is covered by the uniquness one higher in the function.

Also note the typo in your comment above (suupported)

Comments
-----------------

index.c: Line 671 and 694. Your indentation changes make the comments
run over 80 characters. If you end up submitting a new version
of the patch I'd reformat those two comments.

Other than those issues the patch looks good to me.

Steve

#11Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Steve Singer (#10)
1 attachment(s)
Re: Patch to add a primary key using an existing index

On Sat, Nov 20, 2010 at 9:00 AM, Steve Singer <ssinger_pg@sympatico.ca>wrote:

Submission Review:
========================

Tests
--------
The expected output for the regression tests you added don't match
what I'm getting when I run the tests with your patch applied.
I think you just need to regenerate the expected results they seem
to be from a previous version of the patch (different error messages
etc..).

Fixed. Also modified one test to cover the case where constraint name is
provided.

Documentation
---------------

I was able to generate the docs.

The ALTER TABLE page under the synopsis has

ADD table_constraint

where table_constraint is defined on the CREATE TABLE page.
On the CREATE TABLE page table_constraint isn't defined as having the WITH
, the WITH is part of index_parameters.

I propose the alter table page instead have

ADD table_constraint [index_parameters]

where index_parameters also references the CREATE TABLE page like
table_constraint.

IMHO index_parameters is an optional component of table_constraint, and
hence can't be mentioned here, at least not the way shown above.

I have made slight improvements to the doc which might help the user
understand that this WITH(INDEX=) option is exclusive to ALTER TABLE and not
provided by CREATE TABLE.

Usability Review
====================

Behaviour
-------------
I feel that if the ALTER TABLE ... renames the the index
a NOTICE should be generated. We generate notices about creating an index
for a new pkey. We should give them a notice that we are renaming an index
on them.

Done.

Coding Review:
======================

Error Messages
-----------------
in tablecmds your errdetail messages often don't start with a capital
letter. I belive the preference is to have the errdetail strings start with
a capital letter and end with a period.

Fixed.

tablecmds.c - get_constraint_index_oid

contains the check

/* Currently only B-tree indexes are suupported for primary keys */
if (index_rel->rd_rel->relam != BTREE_AM_OID)
elog(ERROR, "\"%s\" is not a B-Tree index",
index_name);

but above we already validate that the index is a unique index with another
check. Today only B-tree indexes support unique constraints. If this
changed at some point and we could have a unique index of some other type,
would something in this patch need to be changed to support them? If we are
only depending on the uniqueness property then I think this check is covered
by the uniquness one higher in the function.

Also note the typo in your comment above (suupported)

I agree; code removed.

Comments
-----------------

index.c: Line 671 and 694. Your indentation changes make the comments
run over 80 characters. If you end up submitting a new version
of the patch I'd reformat those two comments.

Fixed.

Other than those issues the patch looks good to me.

Thanks for your time Steve.

Regards,

PS: I will be mostly unavailable between 11/25 and 12/6, so wouldn't mind if
somebody took ownership of this patch for that duration.
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Attachments:

replace_pkey_index.revised.patch.gzapplication/x-gzip; name=replace_pkey_index.revised.patch.gzDownload
#12Steve Singer
ssinger_pg@sympatico.ca
In reply to: Gurjeet Singh (#11)
Re: Patch to add a primary key using an existing index

On 10-11-22 09:37 AM, Gurjeet Singh wrote:

On Sat, Nov 20, 2010 at 9:00 AM, Steve Singer <ssinger_pg@sympatico.ca
<mailto:ssinger_pg@sympatico.ca>> wrote:

Submission Review:
========================

Tests
--------
The expected output for the regression tests you added don't match
what I'm getting when I run the tests with your patch applied.
I think you just need to regenerate the expected results they seem
to be from a previous version of the patch (different error
messages etc..).

Fixed. Also modified one test to cover the case where constraint name
is provided.

Almost fixed.
I still get an unexpected difference.

! DETAIL:  cannot create PRIMARY KEY/UNIQUE constraint with a non-unique 
index.
   CREATE UNIQUE INDEX rpi_idx2 ON rpi_test(a , b);
   -- should fail; WITH INDEX option specified more than once.
   ALTER TABLE rpi_test ADD PRIMARY KEY (a, b)
--- 35,41 ----
   -- should fail; non-unique
   ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx1');
   ERROR:  "rpi_idx1" is not a unique index
! DETAIL:  Cannot create PRIMARY KEY/UNIQUE constraint using a 
non-unique index.

Documentation
---------------

I was able to generate the docs.

The ALTER TABLE page under the synopsis has

ADD table_constraint

where table_constraint is defined on the CREATE TABLE page.
On the CREATE TABLE page table_constraint isn't defined as having
the WITH
, the WITH is part of index_parameters.

I propose the alter table page instead have

ADD table_constraint [index_parameters]

where index_parameters also references the CREATE TABLE page like
table_constraint.

IMHO index_parameters is an optional component of table_constraint,
and hence can't be mentioned here, at least not the way shown above.

My reading of CREATE TABLE is that index_parameters is an optional
parameter that comes after table_constraint and isn't part of
table_constraint. Any other opinions?

Everything else I mentioned seems fixed in this version

Show quoted text

gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

#13Steve Singer
ssinger@ca.afilias.info
In reply to: Steve Singer (#12)
1 attachment(s)
Re: Patch to add a primary key using an existing index

On 10-11-22 03:24 PM, Steve Singer wrote:

On 10-11-22 09:37 AM, Gurjeet Singh wrote:

On Sat, Nov 20, 2010 at 9:00 AM, Steve Singer <ssinger_pg@sympatico.ca

Almost fixed.
I still get an unexpected difference.

! DETAIL: cannot create PRIMARY KEY/UNIQUE constraint with a non-unique
index.
CREATE UNIQUE INDEX rpi_idx2 ON rpi_test(a , b);
-- should fail; WITH INDEX option specified more than once.
ALTER TABLE rpi_test ADD PRIMARY KEY (a, b)
--- 35,41 ----
-- should fail; non-unique
ALTER TABLE rpi_test ADD primary key(a, b) WITH (INDEX = 'rpi_idx1');
ERROR: "rpi_idx1" is not a unique index
! DETAIL: Cannot create PRIMARY KEY/UNIQUE constraint using a non-unique
index.

The attached version of the patch gets your regression tests to pass.

I'm going to mark this as ready for a committer.

Attachments:

replace_pkey_index.revised2.patch.gzapplication/x-gzip; name=replace_pkey_index.revised2.patch.gzDownload
#14Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Steve Singer (#13)
Re: Patch to add a primary key using an existing index

On Fri, Nov 26, 2010 at 05:58, Steve Singer <ssinger@ca.afilias.info> wrote:

The attached version of the patch gets your regression tests to pass.
I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax:
ALTER TABLE table_name ADD PRIMARY KEY (...) WITH (INDEX='index_name')

Issues:
* WITH (...) is designed for storage parameters. I think treating "INDEX"
as a special keyword in the way might be confusable.
* 'index_name' needs to be single-quoted, but object identifiers should
be double-quoted literals in normal cases.
* The key specifier is a duplicated option because the index has own keys.
Do we need it? It might be for safety, but redundant.
Note that the patch raises a reasonable error on conflict:
ERROR: PRIMARY KEY/UNIQUE constraint definition does not match the index

And, I found a bug:
* USING INDEX TABLESPACE clause is silently ignored, even if the index
uses another tablespace.

After all, do we need a special syntax for the functionality?
Reusing WITH (...) syntax seems to be a trouble for me.
"ADD PRIMARY KEY USING index_name" might be a candidate, but we'd
better reserve USING for non-btree PRIMARY KEY/UNIQUE indexes.
Ideas and suggestions?

--
Itagaki Takahiro

#15Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#14)
Re: Patch to add a primary key using an existing index

On Sun, Nov 28, 2010 at 8:06 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Fri, Nov 26, 2010 at 05:58, Steve Singer <ssinger@ca.afilias.info> wrote:

The attached version of the patch gets your regression tests to pass.
I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax:
 ALTER TABLE table_name ADD PRIMARY KEY (...) WITH (INDEX='index_name')

Why not:

ALTER TABLE table_name ADD PRIMARY KEY (...) INDEX index_name;

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16David Fetter
david@fetter.org
In reply to: Robert Haas (#15)
Re: Patch to add a primary key using an existing index

On Sun, Nov 28, 2010 at 08:40:08PM -0500, Robert Haas wrote:

On Sun, Nov 28, 2010 at 8:06 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Fri, Nov 26, 2010 at 05:58, Steve Singer <ssinger@ca.afilias.info> wrote:

The attached version of the patch gets your regression tests to
pass. I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax: �ALTER TABLE
table_name ADD PRIMARY KEY (...) WITH (INDEX='index_name')

Why not:

ALTER TABLE table_name ADD PRIMARY KEY (...) INDEX index_name;

+1 :)

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#17Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#15)
Re: Patch to add a primary key using an existing index

On sön, 2010-11-28 at 20:40 -0500, Robert Haas wrote:

On Sun, Nov 28, 2010 at 8:06 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Fri, Nov 26, 2010 at 05:58, Steve Singer <ssinger@ca.afilias.info> wrote:

The attached version of the patch gets your regression tests to pass.
I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax:
ALTER TABLE table_name ADD PRIMARY KEY (...) WITH (INDEX='index_name')

Why not:

ALTER TABLE table_name ADD PRIMARY KEY (...) INDEX index_name;

I would think that that determines that name of the index that the
command creates. It does not convey that an existing index is to be
used.

#18Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#17)
Re: Patch to add a primary key using an existing index

On Fri, Dec 3, 2010 at 2:23 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On sön, 2010-11-28 at 20:40 -0500, Robert Haas wrote:

On Sun, Nov 28, 2010 at 8:06 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Fri, Nov 26, 2010 at 05:58, Steve Singer <ssinger@ca.afilias.info> wrote:

The attached version of the patch gets your regression tests to pass.
I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax:
 ALTER TABLE table_name ADD PRIMARY KEY (...) WITH (INDEX='index_name')

Why not:

ALTER TABLE table_name ADD PRIMARY KEY (...) INDEX index_name;

I would think that that determines that name of the index that the
command creates.  It does not convey that an existing index is to be
used.

Well, that'll become clear pretty quickly if you try to use it that
way, but I'm certainly open to other ideas.

Random thoughts:

ALTER TABLE table_name SET PRIMARY KEY INDEX index_name
ALTER INDEX index_name PRIMARY KEY

Other suggestions?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#19Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Robert Haas (#18)
Re: Patch to add a primary key using an existing index

On 03.12.2010 21:43, Robert Haas wrote:

On Fri, Dec 3, 2010 at 2:23 PM, Peter Eisentraut<peter_e@gmx.net> wrote:

On s�n, 2010-11-28 at 20:40 -0500, Robert Haas wrote:

On Sun, Nov 28, 2010 at 8:06 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Fri, Nov 26, 2010 at 05:58, Steve Singer<ssinger@ca.afilias.info> wrote:

The attached version of the patch gets your regression tests to pass.
I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax:
ALTER TABLE table_name ADD PRIMARY KEY (...) WITH (INDEX='index_name')

Why not:

ALTER TABLE table_name ADD PRIMARY KEY (...) INDEX index_name;

I would think that that determines that name of the index that the
command creates. It does not convey that an existing index is to be
used.

Well, that'll become clear pretty quickly if you try to use it that
way, but I'm certainly open to other ideas.

Random thoughts:

ALTER TABLE table_name SET PRIMARY KEY INDEX index_name
ALTER INDEX index_name PRIMARY KEY

ALTER TABLE table_name SET PRIMARY KEY USING INDEX index_name. Quite
verbose, but imho USING makes it much more clear that it's an existing
index.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#20r t
pgsql@xzilla.net
In reply to: Robert Haas (#18)
Re: Patch to add a primary key using an existing index

On Fri, Dec 3, 2010 at 2:43 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Dec 3, 2010 at 2:23 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On sön, 2010-11-28 at 20:40 -0500, Robert Haas wrote:

On Sun, Nov 28, 2010 at 8:06 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Fri, Nov 26, 2010 at 05:58, Steve Singer <ssinger@ca.afilias.info>

wrote:

The attached version of the patch gets your regression tests to pass.
I'm going to mark this as ready for a committer.

I think we need more discussions about the syntax:
ALTER TABLE table_name ADD PRIMARY KEY (...) WITH

(INDEX='index_name')

Why not:

ALTER TABLE table_name ADD PRIMARY KEY (...) INDEX index_name;

I would think that that determines that name of the index that the
command creates. It does not convey that an existing index is to be
used.

+1 on this being confusing

Well, that'll become clear pretty quickly if you try to use it that
way, but I'm certainly open to other ideas.

Random thoughts:

ALTER TABLE table_name SET PRIMARY KEY INDEX index_name
ALTER INDEX index_name PRIMARY KEY

Other suggestions?

What exactly was the objection to the following -->

ALTER TABLE table_name ADD PRIMARY KEY (column_list) USING index_name;

Is the objection that you might have been trying to specify a constraint
named "using" ? I'm willing to make that option more difficult. :-)

Robert Treat
play: http://www.xzilla.net
work: http://www.omniti.com/is/hiring

#21Alvaro Herrera
alvherre@commandprompt.com
In reply to: Heikki Linnakangas (#19)
Re: Patch to add a primary key using an existing index

Excerpts from Heikki Linnakangas's message of vie dic 03 16:45:59 -0300 2010:

ALTER TABLE table_name SET PRIMARY KEY USING INDEX index_name. Quite
verbose, but imho USING makes it much more clear that it's an existing
index.

I was going to post the same thing (well except I was still thinking in
ADD PRIMARY KEY rather than SET PRIMARY KEY). I think SET is better
than ADD in that it is a bit different from the syntax that makes it
create a new index. On the other hand, it could also be pointlessly
annoying.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#22Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Alvaro Herrera (#21)
Re: Patch to add a primary key using an existing index

On 03.12.2010 21:58, Alvaro Herrera wrote:

Excerpts from Heikki Linnakangas's message of vie dic 03 16:45:59 -0300 2010:

ALTER TABLE table_name SET PRIMARY KEY USING INDEX index_name. Quite
verbose, but imho USING makes it much more clear that it's an existing
index.

I was going to post the same thing (well except I was still thinking in
ADD PRIMARY KEY rather than SET PRIMARY KEY). I think SET is better
than ADD in that it is a bit different from the syntax that makes it
create a new index. On the other hand, it could also be pointlessly
annoying.

I think I'd prefer ADD too. I didn't pay attention to that when I posted.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#23Robert Haas
robertmhaas@gmail.com
In reply to: r t (#20)
Re: Patch to add a primary key using an existing index

On Fri, Dec 3, 2010 at 2:56 PM, r t <pgsql@xzilla.net> wrote:

What exactly was the objection to the following -->
ALTER TABLE table_name ADD PRIMARY KEY (column_list) USING index_name;
Is the objection that you might have been trying to specify a constraint
named "using" ? I'm willing to make that option more difficult. :-)

I think it's that someone might expect the word after USING to be the
name of an index AM.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#24Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#23)
Re: Patch to add a primary key using an existing index

On 12/3/10 12:27 PM, Robert Haas wrote:

On Fri, Dec 3, 2010 at 2:56 PM, r t <pgsql@xzilla.net> wrote:

What exactly was the objection to the following -->
ALTER TABLE table_name ADD PRIMARY KEY (column_list) USING index_name;
Is the objection that you might have been trying to specify a constraint
named "using" ? I'm willing to make that option more difficult. :-)

I think it's that someone might expect the word after USING to be the
name of an index AM.

Seems unlikely to cause confusion to me.

However, I don't see why we need (column_list). Surely the index has a
column list already?

ALTER TABLE table_name ADD CONSTRAINT pk_name PRIMARY KEY USING index_name

... seems like the syntax most consistent with the existing commands.
Anything else would be confusingly inconsistent with the way you add a
brand-new PK.

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#25Robert Treat
rob@xzilla.net
In reply to: Josh Berkus (#24)
Re: Patch to add a primary key using an existing index

On Fri, Dec 3, 2010 at 4:41 PM, Josh Berkus <josh@agliodbs.com> wrote:

On 12/3/10 12:27 PM, Robert Haas wrote:

On Fri, Dec 3, 2010 at 2:56 PM, r t <pgsql@xzilla.net> wrote:

What exactly was the objection to the following -->
ALTER TABLE table_name ADD PRIMARY KEY (column_list) USING index_name;
Is the objection that you might have been trying to specify a constraint
named "using" ? I'm willing to make that option more difficult. :-)

I think it's that someone might expect the word after USING to be the
name of an index AM.

Seems unlikely to cause confusion to me.

+1. And were we ever to support that, I think that would be the case to use
WITH (storage_parameter) type syntax, where you would specify
access_method=hash (or whatever). Although, let's not debate that syntax
right now, at this point :-)

However, I don't see why we need (column_list). Surely the index has a
column list already?

ALTER TABLE table_name ADD CONSTRAINT pk_name PRIMARY KEY USING index_name

... seems like the syntax most consistent with the existing commands.
Anything else would be confusingly inconsistent with the way you add a
brand-new PK.

Uh, the syntax I posted was based on this currently valid syntax:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);

The constraint bit is optional, which is why I left it out, but I presume it
would be optional with the new syntax as well... Also, I'm not wedded to the
idea of keeping the column list, but if you are arguing to make it super
consistent, then I think you need to include it.

Robert Treat
play: http://www.xzilla.net
work: http://www.omniti.com/is/hiring

#26Ross J. Reedstrom
reedstrm@rice.edu
In reply to: Robert Treat (#25)
Re: Patch to add a primary key using an existing index

On Fri, Dec 03, 2010 at 05:16:04PM -0500, Robert Treat wrote:

On Fri, Dec 3, 2010 at 4:41 PM, Josh Berkus <josh@agliodbs.com> wrote:

However, I don't see why we need (column_list). Surely the index has a
column list already?

ALTER TABLE table_name ADD CONSTRAINT pk_name PRIMARY KEY USING index_name

... seems like the syntax most consistent with the existing commands.
Anything else would be confusingly inconsistent with the way you add a
brand-new PK.

Uh, the syntax I posted was based on this currently valid syntax:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);

The constraint bit is optional, which is why I left it out, but I presume it
would be optional with the new syntax as well... Also, I'm not wedded to the
idea of keeping the column list, but if you are arguing to make it super
consistent, then I think you need to include it.

If you consider that an index basically is, in some sense, a pre-canned
column list, then:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);
ALTER TABLE table_name ADD PRIMARY KEY USING index_name;

are parallel constructions. And it avoids the error case of the user
providing a column list that doesn't match the index.

Ross
--
Ross Reedstrom, Ph.D. reedstrm@rice.edu
Systems Engineer & Admin, Research Scientist phone: 713-348-6166
Connexions http://cnx.org fax: 713-348-3665
Rice University MS-375, Houston, TX 77005
GPG Key fingerprint = F023 82C8 9B0E 2CC6 0D8E F888 D3AE 810E 88F0 BEDE

#27Josh Berkus
josh@agliodbs.com
In reply to: Robert Treat (#25)
Re: Patch to add a primary key using an existing index

On 12/3/10 2:16 PM, Robert Treat wrote:

Uh, the syntax I posted was based on this currently valid syntax:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);

The constraint bit is optional, which is why I left it out, but I
presume it would be optional with the new syntax as well... Also, I'm
not wedded to the idea of keeping the column list, but if you are
arguing to make it super consistent, then I think you need to include it.

No, I'm not in that case. I'm suggesting we omit the column list and
skip directly to USING.

Why no column list?
1. The extra typing will annoy our users
2. The column list provides opportunities for users to fail to be
consistent with the index and get errors

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#28Tom Lane
tgl@sss.pgh.pa.us
In reply to: Ross J. Reedstrom (#26)
Re: Patch to add a primary key using an existing index

"Ross J. Reedstrom" <reedstrm@rice.edu> writes:

If you consider that an index basically is, in some sense, a pre-canned
column list, then:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);
ALTER TABLE table_name ADD PRIMARY KEY USING index_name;

are parallel constructions. And it avoids the error case of the user
providing a column list that doesn't match the index.

+1 for that approach. One other thought is that ordinarily, the
add-constraint syntax ensures that the constraint is named the same as
its underlying index; in fact we go so far as to keep them in sync if
you rename the index later. But after

ALTER TABLE table_name ADD CONSTRAINT con_name PRIMARY KEY USING index_name;

they'd be named differently, unless we (a) throw an error or (b)
forcibly rename the index. Neither of those ideas seems to satisfy the
principle of least surprise, but leaving it alone seems like it will
also lead to confusion later.

I wonder whether, in the same spirit as not letting the user write a
column name list that might not match, we should pick a syntax that
doesn't allow specifying a constraint name different from the index
name. In the case where you say CONSTRAINT it'd be a bit plausible
to write something like

ALTER TABLE table_name ADD CONSTRAINT con_name PRIMARY KEY USING EXISTING INDEX;

(implying that the index to use is named con_name) but I don't know
what to do if you want to leave off the "CONSTRAINT name" clause.

Thoughts?

regards, tom lane

#29Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#28)
Re: Patch to add a primary key using an existing index

On Dec 4, 2010, at 1:30 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

"Ross J. Reedstrom" <reedstrm@rice.edu> writes:

If you consider that an index basically is, in some sense, a pre-canned
column list, then:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);
ALTER TABLE table_name ADD PRIMARY KEY USING index_name;

are parallel constructions. And it avoids the error case of the user
providing a column list that doesn't match the index.

+1 for that approach. One other thought is that ordinarily, the
add-constraint syntax ensures that the constraint is named the same as
its underlying index; in fact we go so far as to keep them in sync if
you rename the index later. But after

ALTER TABLE table_name ADD CONSTRAINT con_name PRIMARY KEY USING index_name;

they'd be named differently, unless we (a) throw an error or (b)
forcibly rename the index. Neither of those ideas seems to satisfy the
principle of least surprise, but leaving it alone seems like it will
also lead to confusion later.

I think that might be the best way though.

I wonder whether, in the same spirit as not letting the user write a
column name list that might not match, we should pick a syntax that
doesn't allow specifying a constraint name different from the index
name. In the case where you say CONSTRAINT it'd be a bit plausible
to write something like

ALTER TABLE table_name ADD CONSTRAINT con_name PRIMARY KEY USING EXISTING INDEX;

(implying that the index to use is named con_name) but I don't know
what to do if you want to leave off the "CONSTRAINT name" clause.

Because this seems plain weird.

...Robert

Show quoted text
#30Robert Treat
rob@xzilla.net
In reply to: Robert Haas (#29)
Re: Patch to add a primary key using an existing index

On Sat, Dec 4, 2010 at 6:48 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Dec 4, 2010, at 1:30 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

"Ross J. Reedstrom" <reedstrm@rice.edu> writes:

If you consider that an index basically is, in some sense, a pre-canned
column list, then:

ALTER TABLE table_name ADD PRIMARY KEY (column_list);
ALTER TABLE table_name ADD PRIMARY KEY USING index_name;

are parallel constructions. And it avoids the error case of the user
providing a column list that doesn't match the index.

+1 for that approach. One other thought is that ordinarily, the
add-constraint syntax ensures that the constraint is named the same as
its underlying index; in fact we go so far as to keep them in sync if
you rename the index later. But after

ALTER TABLE table_name ADD CONSTRAINT con_name PRIMARY KEY USING

index_name;

they'd be named differently, unless we (a) throw an error or (b)
forcibly rename the index. Neither of those ideas seems to satisfy the
principle of least surprise, but leaving it alone seems like it will
also lead to confusion later.

I think that might be the best way though.

Haas, are you promoting to leave them different? I'd be comfortable with
that.

I'd also be comfortable with B (renaming with notice, similar to the notice
when creating a constraint). Given we rename the constraint when we rename
the index, I would not find the reverse behavior terribly surprising.

Actually I think I'd even be comfortable with A, either you must name the
constraint after the index, or you can leave the constraint name out, and
we'll use the index name.

I wonder whether, in the same spirit as not letting the user write a
column name list that might not match, we should pick a syntax that
doesn't allow specifying a constraint name different from the index
name. In the case where you say CONSTRAINT it'd be a bit plausible
to write something like

ALTER TABLE table_name ADD CONSTRAINT con_name PRIMARY KEY USING EXISTING

INDEX;

(implying that the index to use is named con_name) but I don't know
what to do if you want to leave off the "CONSTRAINT name" clause.

Because this seems plain weird.

+1

Robert Treat
play: http://www.xzilla.net
work: http://www.omniti.com/is/hiring

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Treat (#30)
Re: Patch to add a primary key using an existing index

Robert Treat <rob@xzilla.net> writes:

Actually I think I'd even be comfortable with A, either you must name the
constraint after the index, or you can leave the constraint name out, and
we'll use the index name.

Or we could omit the "CONSTRAINT name" clause from the syntax
altogether.

I think that allowing the names to be different is a bad idea. That
hasn't been possible in the past and there's no apparent reason why
this feature should suddenly make it possible. We will have problems
with it, for instance failures on name collisions because generated
names are only checked against one catalog or the other.

regards, tom lane

#32Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#31)
Re: Patch to add a primary key using an existing index

On Dec 4, 2010, at 11:46 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Treat <rob@xzilla.net> writes:

Actually I think I'd even be comfortable with A, either you must name the
constraint after the index, or you can leave the constraint name out, and
we'll use the index name.

Or we could omit the "CONSTRAINT name" clause from the syntax
altogether.

I think that allowing the names to be different is a bad idea. That
hasn't been possible in the past and there's no apparent reason why
this feature should suddenly make it possible. We will have problems
with it, for instance failures on name collisions because generated
names are only checked against one catalog or the other.

So maybe we should start by deciding what the semantics should be, and then decide what syntax would convey those semantics.

What would make sense to me is: create a pk constraint with the sane name as the existing unique index. If that constraint name already exists, error.

...Robert

#33Josh Berkus
josh@agliodbs.com
In reply to: Robert Haas (#32)
Re: Patch to add a primary key using an existing index

On 12/04/2010 12:37 PM, Robert Haas wrote:

What would make sense to me is: create a pk constraint with the sane name as the existing unique index. If that constraint name already exists, error.

+1, agreed. Based on this, the syntax should be obvious.

We'll need to doc what to do in the event of a name collision error,
though (rename the other constraint). Hmmm, can you rename a constraint?

--
-- Josh Berkus
PostgreSQL Experts Inc.
http://www.pgexperts.com

#34Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#23)
Re: Patch to add a primary key using an existing index

On fre, 2010-12-03 at 15:27 -0500, Robert Haas wrote:

On Fri, Dec 3, 2010 at 2:56 PM, r t <pgsql@xzilla.net> wrote:

What exactly was the objection to the following -->
ALTER TABLE table_name ADD PRIMARY KEY (column_list) USING index_name;
Is the objection that you might have been trying to specify a constraint
named "using" ? I'm willing to make that option more difficult. :-)

I think it's that someone might expect the word after USING to be the
name of an index AM.

That could be avoided by writing

USING INDEX <name>

#35Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Peter Eisentraut (#34)
Re: Patch to add a primary key using an existing index

On Sun, Dec 5, 2010 at 2:09 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On fre, 2010-12-03 at 15:27 -0500, Robert Haas wrote:

On Fri, Dec 3, 2010 at 2:56 PM, r t <pgsql@xzilla.net> wrote:

What exactly was the objection to the following -->
ALTER TABLE table_name ADD PRIMARY KEY (column_list) USING index_name;
Is the objection that you might have been trying to specify a

constraint

named "using" ? I'm willing to make that option more difficult. :-)

I think it's that someone might expect the word after USING to be the
name of an index AM.

That could be avoided by writing

USING INDEX <name>

Allowing USING INDEX along with USING INDEX TABLESPACE is causing
shift/reduce conflicts.

I liked the proposal upthread of providing alternate syntax where user does
not have to specify column-list and system picks up that list from the
index.

ALTER TABLE table_name ADD [CONSTRAINT cons_name] PRIMARY KEY (column_list)
[WITH (...)] [USING INDEX TABLESPACE tblspcname];
ALTER TABLE table_name ADD [CONSTRAINT cons_name] PRIMARY KEY [WITH (...)]
[USING INDEX index_name];

This would also help avoid the bug that Itagaki found, where the user wants
to use an existing index, and also specifies USING INDEX TABLESPACE.

But I still hold a bias towards renaming the index to match constraint name
(with a NOTICE), rather than require that the constraint name match the
index name, because the constraint name is optional and when it is not
provided system has to generate a name and we have to rename the index
anyway to maintain consistency.

Following are the gram.y changes that I am going to start with:

 %type <boolean> constraints_set_mode
-%type <str>        OptTableSpace OptConsTableSpace OptTableSpaceOwner
+%type <str>        OptTableSpace OptConsTableSpace OptConsIndex
OptTableSpaceOwner
 %type <list>   opt_check_option
[...]
            | UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
                ConstraintAttributeSpec
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_UNIQUE;
                    n->location = @1;
                    n->keys = $3;
                    n->options = $5;
                    n->indexspace = $6;
                    n->deferrable = ($7 & 1) != 0;
                    n->initdeferred = ($7 & 2) != 0;
                    $$ = (Node *)n;
                }
+           | UNIQUE opt_definition OptConsIndex ConstraintAttributeSpec
+               {
+                   Constraint *n = makeNode(Constraint);
+                   n->contype = CONSTR_UNIQUE;
+                   n->location = @1;
+                   n->options = $2;
+                   n->indexname = $3;
+                   n->deferrable = ($4 & 1) != 0;
+                   n->initdeferred = ($4 & 2) != 0;
+                   $$ = (Node *)n;
+               }
            | PRIMARY KEY '(' columnList ')' opt_definition
OptConsTableSpace
                ConstraintAttributeSpec
                {
                    Constraint *n = makeNode(Constraint);
                    n->contype = CONSTR_PRIMARY;
                    n->location = @1;
                    n->keys = $4;
                    n->options = $6;
                    n->indexspace = $7;
                    n->deferrable = ($8 & 1) != 0;
                    n->initdeferred = ($8 & 2) != 0;
                    $$ = (Node *)n;
                }
+           | PRIMARY KEY opt_definition OptConsIndex
ConstraintAttributeSpec
+               {
+                   Constraint *n = makeNode(Constraint);
+                   n->contype = CONSTR_PRIMARY;
+                   n->location = @1;
+                   n->options = $3;
+                   n->indexname = $4;
+                   n->deferrable = ($5 & 1) != 0;
+                   n->initdeferred = ($5 & 2) != 0;
+                   $$ = (Node *)n;
+               }
            | EXCLUDE access_method_clause '(' ExclusionConstraintList ')'

[...]
OptConsTableSpace: USING INDEX TABLESPACE name { $$ = $4; }
| /*EMPTY*/ { $$ = NULL; }
;

+OptConsIndex:   USING INDEX name   { $$ = $3; }
+           | /*EMPTY*/             { $$ = NULL; }
+       ;
+

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

#36Tom Lane
tgl@sss.pgh.pa.us
In reply to: Gurjeet Singh (#35)
Re: Patch to add a primary key using an existing index

Gurjeet Singh <singh.gurjeet@gmail.com> writes:

But I still hold a bias towards renaming the index to match constraint name
(with a NOTICE), rather than require that the constraint name match the
index name, because the constraint name is optional and when it is not
provided system has to generate a name and we have to rename the index
anyway to maintain consistency.

No. If the constraint name is not specified, we should certainly use
the existing index name, not randomly rename it.

regards, tom lane

#37Kevin Grittner
Kevin.Grittner@wicourts.gov
In reply to: Tom Lane (#36)
Re: Patch to add a primary key using an existing index

Tom Lane <tgl@sss.pgh.pa.us> wrote:

If the constraint name is not specified, we should certainly use
the existing index name, not randomly rename it.

+1

-Kevin

#38Robert Haas
robertmhaas@gmail.com
In reply to: Kevin Grittner (#37)
Re: Patch to add a primary key using an existing index

On Thu, Dec 9, 2010 at 2:51 PM, Kevin Grittner
<Kevin.Grittner@wicourts.gov> wrote:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

If the constraint name is not specified, we should certainly use
the existing index name, not randomly rename it.

+1

+1

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#39Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Tom Lane (#36)
1 attachment(s)
Re: Patch to add a primary key using an existing index

On Thu, Dec 9, 2010 at 2:48 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Gurjeet Singh <singh.gurjeet@gmail.com> writes:

But I still hold a bias towards renaming the index to match constraint

name

(with a NOTICE), rather than require that the constraint name match the
index name, because the constraint name is optional and when it is not
provided system has to generate a name and we have to rename the index
anyway to maintain consistency.

No. If the constraint name is not specified, we should certainly use
the existing index name, not randomly rename it.

Attached is the updated patch with doc changes and test cases. An overview
of the patch is in order:

The new command syntax is

ALTER TABLE table_name
ADD [CONSTRAINT constraint_name]
PRIMARY KEY USING INDEX index_name
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE
];

ALTER TABLE table_name
ADD [CONSTRAINT constraint_name]
UNIQUE USING INDEX index_name
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE
];

The index should be a unique index, and it should not be an expressional or
partial index. The included test cases exercise a few other cases.

If the constraint name is provided, then index is renamed to that with a
NOTICE, else the index name is used as the constraint name.

I have consciously disallowed the ability to specify storage_parameters
using the WITH clause, if somebody thinks it is wise to allow that and is
needed, I can do that.

Git branch: https://github.com/gurjeet/postgres/tree/constraint_with_index

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

Attachments:

constraint_using_index.patch.gzapplication/x-gzip; name=constraint_using_index.patch.gzDownload
#40Steve Singer
ssinger_pg@sympatico.ca
In reply to: Gurjeet Singh (#39)
1 attachment(s)
Re: Patch to add a primary key using an existing index

I've taken a look at this version of the patch.

Submission Review
----------------
This version of the patch applies cleanly to master. It matches your git
repo and includes test + docs.

Usability Review
---------------

The command syntax now matches what was discussed during the last cf.

The text of the notice:

test=# alter table a add constraint acons unique using index aind2;
NOTICE: ALTER TABLE / ADD UNIQUE USING INDEX will rename index "aind2"
to "acons"

Documentation
----------

I've attached a patch (to be applied on top of your latest patch) with
some editorial changes I'd recommend to your documentation. I feel it
reads a bit clearer (but others should chime in if they disagree or have
better wordings)

A git tree with changes rebased to master + this change is available
at https://github.com/ssinger/postgres/tree/ssinger/constraint_with_index

Code Review
-----------

src/backend/parser/parse_utilcmd.c: 1452
Your calling strdup on the attribute name. I don't have a good enough
grasp on the code to be able to trace this through to where the memory
gets free'd. Does it get freed? Should/could this be a call to pstrdup

Feature Test
-------------

I wasn't able to find any issues in my testing

I'm marking this as returned with feedback pending your answer on the
possible memory leak above but I think the patch is very close to being
ready.

Steve Singer

Attachments:

existing_index_doc_patch.difftext/x-patch; name=existing_index_doc_patch.diffDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 83d2fbb..0b486ab 100644
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
*************** ALTER TABLE <replaceable class="PARAMETE
*** 242,268 ****
      <term><literal>ADD <replaceable class="PARAMETER">table_constraint_using_index</replaceable></literal></term>
      <listitem>
       <para>
!       This form adds a new <literal>PRIMARY KEY</> or <literal>UNIQUE</>
        constraint to the table using an existing index. All the columns of the
        index will be included in the constraint.
       </para>
  
       <para>
!       The index should be UNIQUE, and should not be a <firstterm>partial index</>
!       or an <firstterm>expressional index</>.
       </para>
  
       <para>
!       This can be helpful in situations where one wishes to recreate or
!       <literal>REINDEX</> the index of a <literal>PRIMARY KEY</> or a
!       <literal>UNIQUE</> constraint, but dropping and recreating the constraint
!       to acheive the effect is not desirable. See the illustrative example below.
       </para>
  
       <note>
       <para>
        If a constraint name is provided then the index will be renamed to that
!       name, else the constraint will be named to match the index name.
       </para>
      </note>
  
--- 242,270 ----
      <term><literal>ADD <replaceable class="PARAMETER">table_constraint_using_index</replaceable></literal></term>
      <listitem>
       <para>
!       This form adds a new <literal>PRIMARY KEY</> or <literal>UNIQUE</literal>
        constraint to the table using an existing index. All the columns of the
        index will be included in the constraint.
       </para>
  
       <para>
!       The index should be a UNIQUE index. A <firstterm>partial index</firstterm>
! 	  or an <firstterm>expressional index</firstterm> is not allowed.
       </para>
  
       <para>
!       Adding a constraint using an existing index can be helpful in situations 
! 	  where you wishes to rebuild an index used for a  
! 	  <literal>PRIMARY KEY</literal> or a <literal>UNIQUE</literal> constraint,
! 	  but dropping and recreating the constraint
!       is not desirable. See the illustrative example below.
       </para>
  
       <note>
       <para>
        If a constraint name is provided then the index will be renamed to that
!       name of the constraint. Otherwise the constraint will be named to match 
! 	  the index name.
       </para>
      </note>
  
#41Robert Haas
robertmhaas@gmail.com
In reply to: Steve Singer (#40)
Re: Patch to add a primary key using an existing index

On Sun, Jan 16, 2011 at 5:34 PM, Steve Singer <ssinger_pg@sympatico.ca> wrote:

I'm marking this as returned with feedback pending your answer on the
possible memory leak above but I think the patch is very close to being
ready.

Please use "Waiting on Author" if the patch is to be considered
further for this CommitFest, and "Returned with Feedback" only if it
will not be further considered for this CommitFest.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#42Tom Lane
tgl@sss.pgh.pa.us
In reply to: Steve Singer (#40)
Re: Patch to add a primary key using an existing index

Steve Singer <ssinger_pg@sympatico.ca> writes:

src/backend/parser/parse_utilcmd.c: 1452
Your calling strdup on the attribute name. I don't have a good enough
grasp on the code to be able to trace this through to where the memory
gets free'd. Does it get freed? Should/could this be a call to pstrdup

strdup() is pretty much automatically wrong in the parser, not to
mention most of the rest of the backend. pstrdup is likely what was
meant. If that's the only issue then I don't see any need to wait on
the author, so will take this one.

regards, tom lane

#43Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#42)
Re: Patch to add a primary key using an existing index

I wrote:

... If that's the only issue then I don't see any need to wait on
the author, so will take this one.

I find myself quite dissatisfied with the way that this patch adds yet
another bool flag to index_create (which has too many of those already),
with the effect of causing it to exactly *not* do an index creation.
That's a clear violation of the principle of least astonishment IMNSHO.
I think what's needed here is to refactor things a bit so that the
constraint-creation code is pulled out of index_create and called
separately where needed. Hacking on that now.

One other issue that might be worthy of discussion is that as things
stand, execution of the ADD CONSTRAINT USING INDEX syntax will cause
the constraint to absorb the index as an INTERNAL dependency. That
means dropping the constraint would make the index go away silently ---
it no longer has any separate life. If the intent is just to provide a
way to get the effect of ALTER ADD PRIMARY KEY CONCURRENTLY, then this
behavior is probably fine. But someone who believes DROP CONSTRAINT
exactly reverses the effects of ADD CONSTRAINT might be surprised.
Comments?

regards, tom lane

#44Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#43)
Re: Patch to add a primary key using an existing index

On Mon, Jan 24, 2011 at 7:01 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

One other issue that might be worthy of discussion is that as things
stand, execution of the ADD CONSTRAINT USING INDEX syntax will cause
the constraint to absorb the index as an INTERNAL dependency.  That
means dropping the constraint would make the index go away silently ---
it no longer has any separate life. If the intent is just to provide a
way to get the effect of ALTER ADD PRIMARY KEY CONCURRENTLY, then this
behavior is probably fine.  But someone who believes DROP CONSTRAINT
exactly reverses the effects of ADD CONSTRAINT might be surprised.
Comments?

Well, I think the behavior as described is what we want. If the
syntax associated with that behavior is going to lead to confusion,
I'd view that as a deficiency of the syntax, rather than a deficiency
of the behavior. (I make this comment with some reluctance
considering the amount of bikeshedding we've already done on this
topic, but... that's what I think.)

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#45Joshua Tolley
eggyknap@gmail.com
In reply to: Tom Lane (#43)
Re: Patch to add a primary key using an existing index

On Mon, Jan 24, 2011 at 07:01:13PM -0500, Tom Lane wrote:

One other issue that might be worthy of discussion is that as things
stand, execution of the ADD CONSTRAINT USING INDEX syntax will cause
the constraint to absorb the index as an INTERNAL dependency. That
means dropping the constraint would make the index go away silently ---
it no longer has any separate life. If the intent is just to provide a
way to get the effect of ALTER ADD PRIMARY KEY CONCURRENTLY, then this
behavior is probably fine. But someone who believes DROP CONSTRAINT
exactly reverses the effects of ADD CONSTRAINT might be surprised.
Comments?

So you'd manually create an index, attach it to a constraint, drop the
constraint, and find that the index had disappeared? ISTM since you created
the index explicitly, you should have to drop it explicitly as well.

--
Joshua Tolley / eggyknap
End Point Corporation
http://www.endpoint.com

#46Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Tom Lane (#43)
Re: Patch to add a primary key using an existing index

Sorry for not being on top of this.

On Tue, Jan 25, 2011 at 9:01 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

... If that's the only issue then I don't see any need to wait on
the author, so will take this one.

I find myself quite dissatisfied with the way that this patch adds yet
another bool flag to index_create (which has too many of those already),
with the effect of causing it to exactly *not* do an index creation.
That's a clear violation of the principle of least astonishment IMNSHO.
I think what's needed here is to refactor things a bit so that the
constraint-creation code is pulled out of index_create and called
separately where needed. Hacking on that now.

Thanks.

One other issue that might be worthy of discussion is that as things

stand, execution of the ADD CONSTRAINT USING INDEX syntax will cause
the constraint to absorb the index as an INTERNAL dependency. That
means dropping the constraint would make the index go away silently ---
it no longer has any separate life. If the intent is just to provide a
way to get the effect of ALTER ADD PRIMARY KEY CONCURRENTLY, then this
behavior is probably fine. But someone who believes DROP CONSTRAINT
exactly reverses the effects of ADD CONSTRAINT might be surprised.
Comments?

Since we rename the index automatically to match the constraint name,
implying that the index now belongs to the system, I think the user should
expect the index to go away with the constraint; else we have to remember
index's original name and restore that name on DROP CONSTRAINT, which IMHO
will be even more unintuitive.

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

#47Tom Lane
tgl@sss.pgh.pa.us
In reply to: Gurjeet Singh (#46)
Re: Patch to add a primary key using an existing index

Gurjeet Singh <singh.gurjeet@gmail.com> writes:

On Tue, Jan 25, 2011 at 9:01 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

One other issue that might be worthy of discussion is that as things
stand, execution of the ADD CONSTRAINT USING INDEX syntax will cause
the constraint to absorb the index as an INTERNAL dependency. That
means dropping the constraint would make the index go away silently ---
it no longer has any separate life.

Since we rename the index automatically to match the constraint name,
implying that the index now belongs to the system, I think the user should
expect the index to go away with the constraint; else we have to remember
index's original name and restore that name on DROP CONSTRAINT, which IMHO
will be even more unintuitive.

Yeah, that's a good point. Also, the documented example usage of this
feature is

To recreate a primary key constraint, without blocking updates while the
index is rebuilt:
<programlisting>
CREATE UNIQUE INDEX CONCURRENTLY dist_id_temp_idx on distributors (dist_id);
ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
</programlisting>

with the implication that after you do that, the installed index is
exactly like you would have gotten from straight ADD PRIMARY KEY.
If there's something funny about it, then it's not just a replacement.

In the end I think this is mainly an issue of setting appropriate
expectations in the documentation. I've added the following text to
the ALTER TABLE manual page:

<para>
After this command is executed, the index is <quote>owned</> by the
constraint, in the same way as if the index had been built by
a regular <literal>ADD PRIMARY KEY</> or <literal>ADD UNIQUE</>
command. In particular, dropping the constraint will make the index
disappear too.
</para>

regards, tom lane

#48Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Tom Lane (#47)
Re: Patch to add a primary key using an existing index

On Wed, Jan 26, 2011 at 5:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

In the end I think this is mainly an issue of setting appropriate
expectations in the documentation. I've added the following text to
the ALTER TABLE manual page:

<para>
After this command is executed, the index is <quote>owned</> by the
constraint, in the same way as if the index had been built by
a regular <literal>ADD PRIMARY KEY</> or <literal>ADD UNIQUE</>
command. In particular, dropping the constraint will make the index
disappear too.
</para>

I'd change that last sentence to:

... dropping the constraint will drop the index too.

'disappear' doesn't seem accurate in the context.

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device

#49Tom Lane
tgl@sss.pgh.pa.us
In reply to: Gurjeet Singh (#39)
Re: Patch to add a primary key using an existing index

Gurjeet Singh <singh.gurjeet@gmail.com> writes:

Attached is the updated patch with doc changes and test cases.

Applied with assorted corrections. Aside from the refactoring I wanted,
there were various oversights.

I have consciously disallowed the ability to specify storage_parameters
using the WITH clause, if somebody thinks it is wise to allow that and is
needed, I can do that.

AFAICS, WITH would be supplied at the time of index creation; it's not
appropriate to include it here, any more than INDEX TABLESPACE.

A point that may or may not have gotten discussed back when is that it's
important that the result of this process be dumpable by pg_dump, ie
there not be any hidden discrepancies between the state after ADD
CONSTRAINT USING INDEX and the state you'd get from straight ADD
CONSTRAINT, because the latter is the syntax pg_dump is going to emit.
ADD CONSTRAINT can handle WITH and INDEX TABLESPACE, so carrying those
over from the original index specification is no problem, but
non-default index opclasses or sort ordering options would be a big
problem. That would in particular completely break pg_upgrade, because
the on-disk index wouldn't match the catalog entries created by running
pg_dump. I added some code to check and disallow non-default opclass
and options.

regards, tom lane

#50Gurjeet Singh
singh.gurjeet@gmail.com
In reply to: Tom Lane (#49)
Re: Patch to add a primary key using an existing index

On Wed, Jan 26, 2011 at 6:14 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Gurjeet Singh <singh.gurjeet@gmail.com> writes:

Attached is the updated patch with doc changes and test cases.

Applied with assorted corrections. Aside from the refactoring I wanted,
there were various oversights.

Looking at the commit, the committed patch resembles the submitted patch by
only about 20% :) .

I agree there were quite serious oversights. Thanks for taking care of
those.

Regards,
--
gurjeet.singh
@ EnterpriseDB - The Enterprise Postgres Company
http://www.EnterpriseDB.com

singh.gurjeet@{ gmail | yahoo }.com
Twitter/Skype: singh_gurjeet

Mail sent from my BlackLaptop device