Patch to reindex primary keys

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

Attached is a patch that implements replacing a primary key with another
index. This would help overcome the limitation that primary keys cannot be
reindexed
without taking exclusive locks.

The use case is to create an identical index, concurrenlty, with the
same
structure as the primary key, and then use this feature to atomically
replace
the primary key's underlying index.

Before I dive into the internals, here's what this patch enables
Postgres to do:

</snip>
postgres=# create table mytable( a int primary key );
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index
"mytable_pkey" for table "mytable"
CREATE TABLE
postgres=# insert into mytable select s from generate_series( 1, 100 ) as s;
INSERT 0 100
postgres=# create unique index concurrently mysecond_key on mytable( a );
CREATE INDEX
postgres=#
postgres=# \d mytable
Table "public.mytable"
Column | Type | Modifiers
--------+---------+-----------
a | integer | not null
Indexes:
"mytable_pkey" PRIMARY KEY, btree (a)
"mysecond_key" UNIQUE, btree (a)

postgres=#
postgres=# begin;
BEGIN
postgres=# alter table mytable drop constraint mytable_pkey;
ALTER TABLE
postgres=# alter table mytable add primary key (a) with (index =
'mysecond_key' );
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index
"mysecond_key" for table "mytable"
ALTER TABLE
postgres=# commit
postgres-# ;
COMMIT
postgres=# \d mytable
Table "public.mytable"
Column | Type | Modifiers
--------+---------+-----------
a | integer | not null
Indexes:
"mysecond_key" PRIMARY KEY, btree (a)

</snip>

Internally, this patch this patch drops current primary key constraintm,
if any,
(currently not working, but rest of the feature is still usable), and then
creates a new constraint with the given index.

Here's the pseudocode I started with:

Check if cxt->pkey->options has a 'WITH INDEX' element
take an exclusive lock on that index

Does this table have a primary key
check if index mentioned in cxt->pkey matches that PKEY
definition,
Does column list match
Do the opclasses match
Does index type match (BTree for now)
Do they have the same owner

Append a new command to newcmds to drop the PKey constraint
use 'rel' variable to get primary key's OID ( modify and reuse
relationHasPrimaryKey() )
use relation_open() to get pkey's relation
use the returned Relation->rd_rel->relname to build DROP
CONSTRAINT command
set missingok member of the command so that this would work even
if there was already a DROP CONSTRAINT for the PKey.
push this command to newcmds

Chenge the 'WITH INDEX' element, and replace index name in Value* to
have decimal representation of index's OID.
This will be used by ATExecAddIndex().

The patch is based on REl9_0_STABLE from a few days ago. It is a bit
hackish,
and modifies the couple of internal APIs to get the work done. I still have
a few
TODO items in there, but wanted to throw this patch out there to get a few
eyeballs on it while I traveled.

PS: I am (going to bed and then traveling) for the next 20 hours or so, so
will
not be able to respond to emails until then.

I dedicate this work to

my dear brother-in-law, Sandeep Singh
and my dear friend, Mandeep Singh Sethi

Good men... you will be always in our hearts. RIP.
--
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.patchapplication/octet-stream; name=replace_pkey_index.patchDownload
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 387d43e..fed79ff 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -287,7 +287,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();
 				}
 		;
@@ -305,7 +305,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 69946fe..0ab523e 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,6 +617,7 @@ index_create(Oid heapRelationId,
 	if (shared_relation && tableSpaceId != GLOBALTABLESPACE_OID)
 		elog(ERROR, "shared relations must be placed in pg_global tablespace");
 
+	if (!index_exists)
 	if (get_relname_relid(indexRelationName, namespaceId))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_TABLE),
@@ -648,6 +651,17 @@ index_create(Oid heapRelationId,
 			indexRelationId = GetNewRelFileNode(tableSpaceId, pg_class);
 	}
 
+	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
@@ -720,6 +734,7 @@ index_create(Oid heapRelationId,
 						classObjectId, coloptions, isprimary,
 						!deferrable,
 						!concurrent);
+	}
 
 	/*
 	 * Register constraint and dependencies for the index.
@@ -833,7 +848,7 @@ index_create(Oid heapRelationId,
 									 true);
 			}
 		}
-		else
+		else if(!index_exists)
 		{
 			bool		have_simple_col = false;
 
@@ -876,6 +891,8 @@ index_create(Oid heapRelationId,
 			Assert(!initdeferred);
 		}
 
+		if (!index_exists)
+		{
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
 		{
@@ -905,6 +922,7 @@ index_create(Oid heapRelationId,
 											DEPENDENCY_NORMAL,
 											DEPENDENCY_AUTO);
 		}
+		}
 	}
 	else
 	{
@@ -945,6 +963,8 @@ index_create(Oid heapRelationId,
 	}
 	else if (skip_build)
 	{
+		if (!index_exists)
+		{
 		/*
 		 * Caller is responsible for filling the index later on.  However,
 		 * we'd better make sure that the heap relation is correctly marked as
@@ -958,6 +978,7 @@ index_create(Oid heapRelationId,
 						   heapRelation->rd_rel->reltuples);
 		/* Make the above update visible */
 		CommandCounterIncrement();
+		}
 	}
 	else
 	{
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 435dfdd..96cbce2 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -269,7 +269,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 2da1c86..11ba888 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,7 +69,9 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
 static Oid GetIndexOpClass(List *opclass, Oid attrType,
 				char *accessMethodName, Oid accessMethodId);
 static char *ChooseIndexNameAddition(List *colnames);
-static bool relationHasPrimaryKey(Relation rel);
+
+/* TODO: Unable to figure out which header to declare this in */
+Oid relationHasPrimaryKey(Relation rel);
 
 
 /*
@@ -122,6 +124,7 @@ DefineIndex(RangeVar *heapRelation,
 			bool is_alter_table,
 			bool check_rights,
 			bool skip_build,
+			bool index_exists,
 			bool quiet,
 			bool concurrent)
 {
@@ -340,7 +343,7 @@ DefineIndex(RangeVar *heapRelation,
 		 * it's no problem either.
 		 */
 		if (is_alter_table &&
-			relationHasPrimaryKey(rel))
+			relationHasPrimaryKey(rel) != InvalidOid)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
@@ -488,6 +491,7 @@ DefineIndex(RangeVar *heapRelation,
 					 isconstraint, deferrable, initdeferred,
 					 allowSystemTableMods,
 					 skip_build || concurrent,
+					 index_exists,
 					 concurrent);
 
 	if (!concurrent)
@@ -1541,10 +1545,11 @@ ChooseIndexColumnNames(List *indexElems)
  *
  *	See whether an existing relation has a primary key.
  */
-static bool
+Oid 
 relationHasPrimaryKey(Relation rel)
 {
-	bool		result = false;
+	Oid			indexoid = InvalidOid;
+	bool		isprimary = false;
 	List	   *indexoidlist;
 	ListCell   *indexoidscan;
 
@@ -1557,21 +1562,22 @@ relationHasPrimaryKey(Relation rel)
 
 	foreach(indexoidscan, indexoidlist)
 	{
-		Oid			indexoid = lfirst_oid(indexoidscan);
 		HeapTuple	indexTuple;
 
+		indexoid = lfirst_oid(indexoidscan);
+
 		indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
 		if (!HeapTupleIsValid(indexTuple))		/* should not happen */
 			elog(ERROR, "cache lookup failed for index %u", indexoid);
-		result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary;
+		isprimary = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary;
 		ReleaseSysCache(indexTuple);
-		if (result)
+		if (isprimary)
 			break;
 	}
 
 	list_free(indexoidlist);
 
-	return result;
+	return isprimary ? indexoid : InvalidOid;
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2383112..68e916f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4559,7 +4559,10 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 {
 	bool		check_rights;
 	bool		skip_build;
+	bool		index_exists = false;
 	bool		quiet;
+	Oid			index_oid = InvalidOid;
+	ListCell	*l, *prev = NULL;
 
 	Assert(IsA(stmt, IndexStmt));
 
@@ -4570,11 +4573,55 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 	/* suppress notices when rebuilding existing index */
 	quiet = is_rebuild;
 
+	/* TODO:
+		Do stmt->options have 'WITH INDEX' option set
+			Get OID from the Value* (represented as string)
+				strspn(opr_name_or_oid, "0123456789") == strlen(opr_name_or_oid))
+				result = DatumGetObjectId(DirectFunctionCall1(oidin,
+												CStringGetDatum(opr_name_or_oid)));
+			replace InvalidOid, below, with this OID
+			set 'primary' to on;
+			set skip_build to on
+				TODO Check with -hackers that this is the right thing to do even when tab->newvals is non-null?
+	 */
+
+	foreach(l, stmt->options)
+	{
+		DefElem	*def = (DefElem*)lfirst(l);
+
+		if (def->defnamespace == NULL && 0 == strcmp(def->defname, "index"))
+		{
+			if (!(def->defaction == DEFELEM_UNSPEC || def->defaction == DEFELEM_SET))
+				elog(ERROR, "index option for a primary key has a syntax error." );
+		}
+		else
+		{
+			prev = l;
+			continue;
+		}
+
+		Assert(strspn(strVal(def->arg), "0123456789") == strlen(strVal(def->arg)));
+		Assert(stmt->primary);
+
+		index_oid = DatumGetObjectId(DirectFunctionCall1(oidin,
+										CStringGetDatum(strVal(def->arg))));
+
+		/* We override the skip_build set above */
+		skip_build = true;
+
+		index_exists = true;
+
+		break;
+	}
+
+	if (l) /* unecessary check, but good for readability */
+		stmt->options = list_delete_cell(stmt->options, l, prev);
+
 	/* The IndexStmt has already been through transformIndexStmt */
 
 	DefineIndex(stmt->relation, /* relation */
 				stmt->idxname,	/* index name */
-				InvalidOid,		/* no predefined OID */
+				index_oid,		/* no predefined OID */
 				stmt->accessMethod,		/* am name */
 				stmt->tableSpace,
 				stmt->indexParams,		/* parameters */
@@ -4589,6 +4636,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 				true,			/* is_alter_table */
 				check_rights,
 				skip_build,
+				index_exists,
 				quiet,
 				false);
 }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 322e689..ad98928 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1931,6 +1931,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
 	List	   *newcmds = NIL;
 	bool		skipValidation = true;
 	AlterTableCmd *newcmd;
+	Oid	index_oid = InvalidOid;
 
 	/*
 	 * We must not scribble on the passed-in AlterTableStmt, so copy it. (This
@@ -2047,6 +2048,192 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
 
 	transformFKConstraints(pstate, &cxt, skipValidation, true);
 
+	/* TODO: look at cxt->pkey->options and see if it has 'WITH INDEX'; if yes
+			then cook-up an ALTER TABLE DROP CONSTRAINT for table's primary key,
+			and push into newcmds
+
+	Check if cxt->pkey->options has a 'WITH INDEX' element
+		take an exclusive lock on that index
+
+		Does this table have a primary key
+			check if index mentioned in cxt->pkey matches that PKEY definition,
+				Does column list match
+				Do the opclasses match
+				Does index type match (BTree for now)
+				Do they have the same owner
+
+		Append a new command to newcmds to drop the PKey constraint
+			use 'rel' variable to get primary key's OID ( modify and reuse relationHasPrimaryKey() )
+			use relation_open() to get pkey's relation
+			use the returned Relation->rd_rel->relname to build DROP CONSTRAINT command
+			set missingok member of the command so that this would work even if there was already a DROP CONSTRAINT for the PKey.
+			push this command to newcmds
+
+		Chenge the 'WITH INDEX' element, and replace index name in Value* to have decimal representation of index's OID.
+			This will be used by ATExecAddIndex().
+
+		BIG TODO: convert all elog() calls into ereport() and proper errcode()
+					calls and write better messages.
+	  */
+
+	if (cxt.pkey)
+	foreach(l, cxt.pkey->options)
+	{
+		DefElem *def = (DefElem*)lfirst(l);
+		Oid pkey_oid;
+		char *index_name;
+		char *oid_string;
+		List *index_name_list;
+		RangeVar *index_rv;
+		Relation index_rel;
+
+		if (def->defnamespace == NULL && 0 == strcmp(def->defname, "index"))
+		{
+			if (!(def->defaction == DEFELEM_UNSPEC || def->defaction == DEFELEM_SET))
+				elog(ERROR, "index option for a primary key has a syntax error." );
+		}
+		else
+			continue;
+
+		/*
+		 * If we don't do this, then this will get to DefineIndex(), and it will
+		 * throw a fit.
+		 */
+		if (index_oid != InvalidOid)
+			elog(ERROR, "only one WITH INDEX option can be specified for a primary key.");
+
+		if (!IsA(def->arg, String))
+			elog(ERROR, "WITH INDEX option for primary key should be a string value");
+
+		index_name = strVal(def->arg);
+
+		index_name_list = stringToQualifiedNameList(index_name);
+
+		/* TODO: Should we assert that this is a non-qualified name? */
+		index_rv = makeRangeVarFromNameList(index_name_list);
+
+		index_rel = relation_openrv(index_rv, AccessExclusiveLock);
+
+		index_oid = RelationGetRelid(index_rel);
+
+		oid_string = DatumGetCString(DirectFunctionCall1(oidout,
+												ObjectIdGetDatum(index_oid)));
+
+		/* replace index name with its Oid::cstring */
+		def->arg = (Node*)makeString(oid_string);
+
+		/* TODO: set index name in the statement, to affect the constraint name */
+		cxt.pkey->idxname = pstrdup(strVal(llast(index_name_list)));
+
+		/* TODO: make sure the index is in the same schema/namespace as the table */
+
+		pkey_oid = relationHasPrimaryKey(rel);
+
+		if (pkey_oid != InvalidOid)
+		{
+			HeapTuple		pkey_tuple;
+			HeapTuple		index_tuple;
+			Form_pg_index	pkey_form;
+			Form_pg_index	index_form;
+			AlterTableCmd	*at_cmd;
+			int2			i;
+			Relation		pkey_rel;
+
+			/*
+			 * Check pg_class->relam to see if the index type match. As of now, only
+			 * BTree indexes are used to implement primary keys, but it doesn't hurt
+			 * to be future proof.
+			 */
+			pkey_rel = relation_open(pkey_oid, AccessExclusiveLock);
+
+			if (pkey_rel->rd_rel->relam != index_rel->rd_rel->relam)
+				elog(ERROR, "index type of WITH INDEX argument and that of the primary key are not the same.");
+
+			if (pkey_rel->rd_rel->relowner != index_rel->rd_rel->relowner)
+				elog(ERROR, "owner of WITH INDEX argument and that of the primary key are not same.");
+
+			pkey_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(pkey_oid));
+			if (!HeapTupleIsValid(pkey_tuple))
+				elog(ERROR, "cache lookup failed for index %u", pkey_oid);
+			pkey_form = (Form_pg_index) GETSTRUCT(pkey_tuple);
+
+			index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+			if (!HeapTupleIsValid(index_tuple))
+				elog(ERROR, "cache lookup failed for index %u", index_oid);
+			index_form = (Form_pg_index) GETSTRUCT(index_tuple);
+
+			if (!index_form->indisvalid)
+				elog(ERROR, "index mentioned in the WITH INDEX clause of primary key is not valid.");
+
+			if (!index_form->indisunique)
+				elog(ERROR, "index mentioned in the WITH INDEX clause of primary key is not a unique index.");
+
+			/* TODO: Should we check for indisready, indcheckxmin ?*/
+
+			if (index_form->indrelid != pkey_form->indrelid)
+				elog(ERROR, "index mentioned in the WITH INDEX clause of primary key is not on the same table.");
+
+			if (index_form->indnatts != pkey_form->indnatts)
+				elog(ERROR, "idefinition of index mentioned in the WITH INDEX clause does not match primary key.");
+
+			/*
+			 * Loop over each attribute in the primary key and see if it
+			 * matches the attributes of the index we are replacing it with.
+			 */
+			for (i = 0; i < pkey_form->indnatts; i++)
+			{
+				/* We do not expect primary keys on expressions */
+				Assert(pkey_form->indkey.values[i] != 0);
+
+				if (pkey_form->indkey.values[i] != index_form->indkey.values[i])
+					elog(ERROR, "idefinition of index mentioned in the WITH INDEX clause does not match primary key.");
+
+				if (pkey_form->indclass.values[i] != index_form->indclass.values[i])
+					elog(ERROR, "operator classes of index mentioned in the WITH INDEX clause does not match those of primary key.");
+			}
+
+			ReleaseSysCache(pkey_tuple);
+			ReleaseSysCache(index_tuple);
+
+			at_cmd = makeNode(AlterTableCmd);
+			at_cmd->subtype = AT_DropConstraint;
+			at_cmd->name = pstrdup(NameStr(pkey_rel->rd_rel->relname));
+			at_cmd->behavior = DROP_CASCADE;;
+			at_cmd->missing_ok = true;
+
+			newcmds = lappend(newcmds, at_cmd);
+
+			/* Release the lock so that it can be dropped from cache. */
+			relation_close(pkey_rel, AccessExclusiveLock);
+		}
+
+		/* NOw update pg_index tuple to mark this index as indisprimary */
+		{
+			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);
+		}
+
+		relation_close(index_rel, NoLock);
+
+		/* do not break; at the end of the loop. Use this opprtunity to catch multiple 'WITH INDEX' clauses*/
+	}
+
 	/*
 	 * Push any index-creation commands into the ALTER, so that they can be
 	 * scheduled nicely by tablecmds.c.  Note that tablecmds.c assumes that
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b9be06f..adae7c6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -890,6 +890,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 d336576..3b019cb 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 7edda97..c774710 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);
#2Robert Haas
robertmhaas@gmail.com
In reply to: Gurjeet Singh (#1)
Re: Patch to reindex primary keys

On Wed, Sep 29, 2010 at 8:42 PM, Gurjeet Singh <singh.gurjeet@gmail.com> wrote:

    Attached is a patch that implements replacing a primary key with another
index. This would help overcome the limitation that primary keys cannot be
reindexed
without taking exclusive locks.

Sounds useful. You should add this patch here so it gets reviewed
during the next CommitFest.

https://commitfest.postgresql.org/action/commitfest_view/open

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