Subject: Confused comment about drop replica identity index

From: "[email-masked]" <[email-masked]>
Date: Tue, 14 Dec 2021 12:38:28 +0000

Hi hackers,

When I doing development based by PG, I found the following comment have a
little problem in file src/include/catalog/pg_class.h.

/*
 * an explicitly chosen candidate key's columns are used as replica identity.
 * Note this will still be set if the index has been dropped; in that case it
 * has the same meaning as 'd'.
 */
#define		  REPLICA_IDENTITY_INDEX	'i'

The last sentence makes me a little confused :
[......in that case it as the same meaning as 'd'.]

Now, pg-doc didn't have a clear style to describe this.

But if I drop relation's replica identity index like the comment, the action
is not as same as default.

For example:
Execute the following SQL:
create table tbl (col1 int  primary key, col2 int not null);
create unique INDEX ON tbl(col2);
alter table tbl replica identity using INDEX tbl_col2_idx;
drop index tbl_col2_idx;
create publication pub for table tbl;
delete from tbl;

Actual result:
ERROR:  cannot delete from table "tbl" because it does not have a replica identity and publishes deletes
HINT:  To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.

Expected result in comment:
DELETE 0

I found that in the function CheckCmdReplicaIdentity, the operation described
in the comment is not considered,
When relation's replica identity index is found to be InvalidOid, an error is
reported.

Are the comment here not accurate enough?
Or we need to adjust the code according to the comments?

Regards,
Wang wei

---

From: Ashutosh Bapat <[email-masked]>
Date: Tue, 14 Dec 2021 19:10:49 +0530

> HINT:  To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.

I think I see where's the confusion. The table has a primary key and
so when the replica identity index is dropped, per the comment in
code, you expect that primary key will be used as replica identity
since that's what 'd' or default means.

> When relation's replica identity index is found to be InvalidOid, an error is

This code in RelationGetIndexList() is not according to that comment.

   if (replident == REPLICA_IDENTITY_DEFAULT && OidIsValid(pkeyIndex))
        relation->rd_replidindex = pkeyIndex;
    else if (replident == REPLICA_IDENTITY_INDEX && OidIsValid(candidateIndex))
        relation->rd_replidindex = candidateIndex;
    else
        relation->rd_replidindex = InvalidOid;

> Or we need to adjust the code according to the comments?

Comment in code is one thing, but I think PG documentation is not
covering the use case you tried. What happens when a replica identity
index is dropped has not been covered either in ALTER TABLE
https://www.postgresql.org/docs/13/sql-altertable.html or DROP INDEX
https://www.postgresql.org/docs/14/sql-dropindex.html documentation.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Michael Paquier <[email-masked]>
Date: Wed, 15 Dec 2021 12:24:45 +0900

>         relation->rd_replidindex = InvalidOid;

Yeah, the comment is wrong.  If the index of a REPLICA_IDENTITY_INDEX
is dropped, I recall that the behavior is the same as
REPLICA_IDENTITY_NOTHING.

> https://www.postgresql.org/docs/14/sql-dropindex.html documentation.

Not sure about the DROP INDEX page, but I'd be fine with mentioning
that in the ALTER TABLE page in the paragraph related to REPLICA
IDENTITY.  While on it, I would be tempted to switch this stuff to use
a list of <variablelist> for all the option values.  That would be
much easier to read.

[ ... thinks a bit ... ]

FWIW, this brings back some memories, as of this thread:
https://www.postgresql.org/message-id/[email-masked]

See also commit fe7fd4e from August 2020, where some tests have been
added.  I recall seeing this incorrect comment from last year's
thread and it may have been mentioned in one of the surrounding
threads..  Maybe I just let it go back then.  I don't know.
--
Michael

---

From: "[email-masked]" <[email-masked]>
Date: Wed, 15 Dec 2021 09:18:26 +0000

On Tue, Dec 15, 2021 at 11:25AM, Michael Paquier wrote:
> Yeah, the comment is wrong.  If the index of a REPLICA_IDENTITY_INDEX is
> dropped, I recall that the behavior is the same as REPLICA_IDENTITY_NOTHING.

Thank you for your response.
I agreed that the comment is wrong.

> Not sure about the DROP INDEX page, but I'd be fine with mentioning that in the
> ALTER TABLE page in the paragraph related to REPLICA IDENTITY.  While on it, I
> would be tempted to switch this stuff to use a list of <variablelist> for all the option
> values.  That would be much easier to read.

Yeah, if we can add some details to pg-doc and code comments, I think it will
be more friendly to PG users and developers.

Regards,
Wang wei

---

From: Michael Paquier <[email-masked]>
Date: Thu, 16 Dec 2021 07:39:52 +0900

> be more friendly to PG users and developers.

Would you like to write a patch to address all that?
Thanks,
--
Michael

---

From: "[email-masked]" <[email-masked]>
Date: Thu, 16 Dec 2021 02:27:06 +0000

> Would you like to write a patch to address all that?

OK, I will push it soon.

Regards,
Wang wei

---

From: Alvaro Herrera <[email-masked]>
Date: Thu, 16 Dec 2021 15:08:46 -0300

> REPLICA_IDENTITY_NOTHING.

Hmm, so if a table has REPLICA IDENTITY INDEX and there is a publication
with an explicit column list, then we need to forbid the DROP INDEX for
that index.

I wonder why don't we just forbid DROP INDEX of an index that's been
defined as replica identity.  It seems quite silly an operation to
allow.

-- 
Álvaro Herrera           39°49'30"S 73°17'W  —  https://www.EnterpriseDB.com/
"Ed is the standard text editor."
      http://groups.google.com/group/alt.religion.emacs/msg/8d94ddab6a9b0ad3

---

From: Michael Paquier <[email-masked]>
Date: Fri, 17 Dec 2021 08:55:23 +0900

> with an explicit column list, then we need to forbid the DROP INDEX for

Hmm.  I have not followed this thread very closely.

> defined as replica identity.  It seems quite silly an operation to

The commit logs talk about b23b0f55 here for this code, to ease the
handling of relcache entries for rd_replidindex.  07cacba is the
origin of the logic (see RelationGetIndexList).  Andres?

I don't think that this is really an argument against putting more
restrictions as anything that deals with an index drop, including the
internal ones related to constraints, would need to go through
index_drop(), and new features may want more restrictions in place as
you say.

Now, I don't see a strong argument in changing this behavior either
(aka I have not looked at what this implies for the new publication
types), and we still need to do something for the comment/docs in
existing branches, anyway.  So I would still fix this gap as a first
step, then deal with the rest on HEAD as necessary.
--
Michael

---

From: "Euler Taveira" <[email-masked]>
Date: Thu, 16 Dec 2021 21:31:15 -0300

> > defined as replica identity.  It seems quite silly an operation to
It would avoid pilot errors.

> step, then deal with the rest on HEAD as necessary.
I've never understand the weak dependency between the REPLICA IDENTITY and the
index used by it. I'm afraid we will receive complaints about this unexpected
behavior (my logical replication setup is broken because I dropped an index) as
far as new logical replication features are added.  Row filtering imposes some
restrictions in UPDATEs and DELETEs (an error message is returned and the
replication stops) if a column used in the expression isn't part of the REPLICA
IDENTITY anymore.

It seems we already have some code in RangeVarCallbackForDropRelation() that
deals with a system index error condition. We could save a syscall and provide
a test for indisreplident there.

If this restriction is undesirable, we should at least document this choice and
probably emit a WARNING for DROP INDEX.

--
Euler Taveira
EDB   https://www.enterprisedb.com/

---

From: "[email-masked]" <[email-masked]>
Date: Mon, 20 Dec 2021 03:46:13 +0000

> > Would you like to write a patch to address all that?

Here is a patch to correct wrong comment about REPLICA_IDENTITY_INDEX, And improve the pg-doc.

Regards,
Wang wei

---

From: Michael Paquier <[email-masked]>
Date: Mon, 20 Dec 2021 20:11:04 +0900

On Mon, Dec 20, 2021 at 03:46:13AM +0000, [email-masked] wrote:
> Here is a patch to correct wrong comment about
> REPLICA_IDENTITY_INDEX, And improve the pg-doc.

That's mostly fine.  I have made some adjustments as per the
attached.

+         The default for non-system tables. Records the old values of the columns
+         of the primary key, if any. The default for non-system tables. 
The same sentence is repeated twice.

+         Records no information about the old row.(This is the
default for system tables.)
For consistency with the rest, this could drop the parenthesis for the
second sentence.

+       <term><literal>USING INDEX index_name</literal></term>
This should use <replaceable> as markup for index_name.

Pondering more about this thread, I don't think we should change the
existing behavior in the back-branches, but I don't have any arguments
about doing such changes on HEAD to help the features being worked
on, either.  So I'd like to apply and back-patch the attached, as a
first step, to fix the inconsistency.
--
Michael
From 05fe07224a5f31f3d28da088f302772724c00dd4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email-masked]>
Date: Mon, 20 Dec 2021 20:09:59 +0900
Subject: [PATCH v2] Correct comment and documentation about
 REPLICA_IDENTITY_INDEX

---
 src/include/catalog/pg_class.h    |  2 +-
 doc/src/sgml/ref/alter_table.sgml | 51 ++++++++++++++++++++++++++-----
 2 files changed, 44 insertions(+), 9 deletions(-)

diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 93338d267c..b46299048e 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -182,7 +182,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd
 /*
  * an explicitly chosen candidate key's columns are used as replica identity.
  * Note this will still be set if the index has been dropped; in that case it
- * has the same meaning as 'd'.
+ * has the same meaning as 'n'.
  */
 #define		  REPLICA_IDENTITY_INDEX	'i'
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 8f14e4a5c4..a76e2e7322 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -872,16 +872,51 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
      <para>
       This form changes the information which is written to the write-ahead log
       to identify rows which are updated or deleted.  This option has no effect
-      except when logical replication is in use.  <literal>DEFAULT</literal>
-      (the default for non-system tables) records the
-      old values of the columns of the primary key, if any.  <literal>USING INDEX</literal>
-      records the old values of the columns covered by the named index, which
-      must be unique, not partial, not deferrable, and include only columns marked
-      <literal>NOT NULL</literal>.  <literal>FULL</literal> records the old values of all columns
-      in the row.  <literal>NOTHING</literal> records no information about the old row.
-      (This is the default for system tables.)
+      except when logical replication is in use.
       In all cases, no old values are logged unless at least one of the columns
       that would be logged differs between the old and new versions of the row.
+     <variablelist>
+      <varlistentry>
+       <term><literal>DEFAULT</literal></term>
+       <listitem>
+        <para>
+         Records the old values of the columns of the primary key, if any.
+         This is the default for non-system tables. 
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>USING INDEX <replaceable class="parameter">index_name</replaceable></literal></term>
+       <listitem>
+        <para>
+         Records the old values of the columns covered by the named index,
+         that must be unique, not partial, not deferrable, and include only
+         columns marked <literal>NOT NULL</literal>. If this index is
+         dropped, the behavior is the same as <literal>NOTHING</literal>.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>FULL</literal></term>
+       <listitem>
+        <para>
+         Records the old values of all columns in the row.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>NOTHING</literal></term>
+       <listitem>
+        <para>
+         Records no information about the old row. This is the default for
+         system tables.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
      </para>
     </listitem>
    </varlistentry>
-- 
2.34.1

---

From: "Euler Taveira" <[email-masked]>
Date: Mon, 20 Dec 2021 11:57:32 -0300

> That's mostly fine.  I have made some adjustments as per the
Your patch looks good to me.

> first step, to fix the inconsistency.
What do you think about the attached patch? It forbids the DROP INDEX. We might
add a detail message but I didn't in this patch.

--
Euler Taveira
EDB   https://www.enterprisedb.com/
From 2ba00335746cf8348e019582a253721047f1a0ac Mon Sep 17 00:00:00 2001
From: Euler Taveira <[email-masked]>
Date: Thu, 16 Dec 2021 16:54:47 -0300
Subject: [PATCH v1] Disallow dropping an index that is used by replica
 identity

Drop an index that is used by a replica identity can break the
replication for UPDATE and DELETE operations. This may be undesirable
from the usability standpoint. It introduces a behaviour change.
---
 src/backend/commands/tablecmds.c               | 14 +++++++++++++-
 src/test/regress/expected/replica_identity.out |  3 +++
 src/test/regress/sql/replica_identity.sql      |  3 +++
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bf42587e38..727ad6625b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1530,15 +1530,21 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 					   rel->relname);
 
 	/*
+	 * An index used by a replica identity cannot be dropped because some
+	 * operations (UPDATE and DELETE) relies on the index properties
+	 * (uniqueness and NOT NULL) to record old values. These old values are
+	 * essential for identifying the row on the subscriber.
+	 *
 	 * Check the case of a system index that might have been invalidated by a
 	 * failed concurrent process and allow its drop. For the time being, this
 	 * only concerns indexes of toast relations that became invalid during a
 	 * REINDEX CONCURRENTLY process.
 	 */
-	if (IsSystemClass(relOid, classform) && relkind == RELKIND_INDEX)
+	if (relkind == RELKIND_INDEX)
 	{
 		HeapTuple	locTuple;
 		Form_pg_index indexform;
+		bool		indisreplident;
 		bool		indisvalid;
 
 		locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid));
@@ -1550,8 +1556,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
 
 		indexform = (Form_pg_index) GETSTRUCT(locTuple);
 		indisvalid = indexform->indisvalid;
+		indisreplident = indexform->indisreplident;
 		ReleaseSysCache(locTuple);
 
+		if (indisreplident)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("could not drop index \"%s\" because it is required by the replica identity", rel->relname)));
+
 		/* Mark object as being an invalid index of system catalogs */
 		if (!indisvalid)
 			invalid_system_index = true;
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index e25ec06a84..56900451f7 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -227,6 +227,9 @@ Indexes:
 -- used as replica identity.
 ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
 ERROR:  column "id" is in index used as replica identity
+-- DROP INDEX is not allowed if the index is used as REPLICA IDENTITY
+DROP INDEX test_replica_identity3_id_key;
+ERROR:  could not drop index "test_replica_identity3_id_key" because it is required by the replica identity
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity2;
 DROP TABLE test_replica_identity3;
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index 33da829713..f46769e965 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -98,6 +98,9 @@ ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
 -- used as replica identity.
 ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
 
+-- DROP INDEX is not allowed if the index is used as REPLICA IDENTITY
+DROP INDEX test_replica_identity3_id_key;
+
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity2;
 DROP TABLE test_replica_identity3;
-- 
2.20.1

---

From: Michael Paquier <[email-masked]>
Date: Tue, 21 Dec 2021 09:46:37 +0900

> add a detail message but I didn't in this patch.

Yeah.  I'd agree about doing something like that on HEAD, and that
would help with some of the logirep-related patch currently being
worked on, as far as I understood.
--
Michael

---

From: "[email-masked]" <[email-masked]>
Date: Tue, 21 Dec 2021 01:31:42 +0000

On Tue, Dec 20, 2021 at 19:11PM, Michael Paquier wrote:
> That's mostly fine.  I have made some adjustments as per the attached.

Thanks for reviewing.

> For consistency with the rest, this could drop the parenthesis for the second
> sentence.
> 

The change looks good to me.

Regards,
Wang wei

---

From: Michael Paquier <[email-masked]>
Date: Wed, 22 Dec 2021 16:40:16 +0900

> Your patch looks good to me.

Thanks.  I have done this part for now.
--
Michael

---

From: "[email-masked]" <[email-masked]>
Date: Thu, 30 Dec 2021 06:45:30 +0000

On Tues, Dec 21, 2021 8:47 AM Michael Paquier <[email-masked]> wrote:
> > What do you think about the attached patch? It forbids the DROP INDEX.
> > We might add a detail message but I didn't in this patch.
> 
> Yeah.  I'd agree about doing something like that on HEAD, and that would help
> with some of the logirep-related patch currently being worked on, as far as I
> understood.

Hi,

I think forbids DROP INDEX might not completely solve this problem. Because
user could still use other command to delete the index, for example: ALTER
TABLE DROP COLUMN. After dropping the column, the index on it will also be
dropped.

Besides, user can also ALTER REPLICA IDENTITY USING INDEX "primary key", and in
this case, when they ALTER TABLE DROP CONSTR "PRIMARY KEY", the replica
identity index will also be dropped.

Best regards,
Hou zj