From fe498c31c2163a69728ad749e365112c65739fe1 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Wed, 20 Dec 2023 10:54:53 +0530
Subject: [PATCH v2] Function to get invalidation cause of a replication slot.

This patch implements a new system function
pg_get_slot_invalidation_cause('slot_name')
to get invalidation cause of a replication slot.

This function returns 0 if the replication slot is not invalidated;
else returns invalidation cause. Possible invalidation causes are:
    1 = required WAL has been removed.
    2 = required rows have been removed.
    3 = wal_level insufficient on the primary server
---
 doc/src/sgml/func.sgml                    | 32 +++++++++++++++++++++++
 src/backend/replication/slotfuncs.c       | 27 +++++++++++++++++++
 src/include/catalog/pg_proc.dat           |  4 +++
 src/test/recovery/t/019_replslot_limit.pl |  6 +++++
 4 files changed, 69 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..2ec0c411ca 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27684,6 +27684,38 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_slot_invalidation_cause</primary>
+        </indexterm>
+        <function>pg_get_slot_invalidation_cause</function> ( <parameter>slot_name</parameter> <type>name</type> )
+        <returnvalue>integer</returnvalue>
+       </para>
+       <para>
+        Returns invalidation cause of the replication slot named
+        <parameter>slot_name</parameter>. Returns 0 if the given replication
+        slot is not invalidated. Possible invalidation causes are:
+        <itemizedlist spacing="compact">
+         <listitem>
+          <para>
+           <literal>1</literal> = required WAL has been removed.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>2</literal> = required rows have been removed.
+          </para>
+         </listitem>
+         <listitem>
+          <para>
+           <literal>3</literal> = wal_level insufficient on the primary server
+          </para>
+         </listitem>
+        </itemizedlist>
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 4b694a03d0..3ac6c45170 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -225,6 +225,33 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for getting invalidation cause of a slot.
+ *
+ * Returns ReplicationSlotInvalidationCause enum value for valid slot_name;
+ * Returns RS_INVAL_NONE if the given slot is not invalidated.
+ */
+Datum
+pg_get_slot_invalidation_cause(PG_FUNCTION_ARGS)
+{
+	Name		name = PG_GETARG_NAME(0);
+	ReplicationSlot *s;
+	ReplicationSlotInvalidationCause cause;
+
+	s = SearchNamedReplicationSlot(NameStr(*name), true);
+	if (s == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("replication slot \"%s\" does not exist",
+						NameStr(*name))));
+
+	SpinLockAcquire(&s->mutex);
+	cause = s->data.invalidated;
+	SpinLockRelease(&s->mutex);
+
+	PG_RETURN_INT16(cause);
+}
+
 /*
  * pg_get_replication_slots - SQL SRF showing all replication slots
  * that currently exist on the database cluster.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 77e8b13764..ce001dacb9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11095,6 +11095,10 @@
   proname => 'pg_drop_replication_slot', provolatile => 'v', proparallel => 'u',
   prorettype => 'void', proargtypes => 'name',
   prosrc => 'pg_drop_replication_slot' },
+{ oid => '8484', descr => 'what caused the replication slot to become invalid',
+  proname => 'pg_get_slot_invalidation_cause', provolatile => 's', proisstrict => 't',
+  prorettype => 'int2', proargtypes => 'name',
+  prosrc => 'pg_get_slot_invalidation_cause' },
 { oid => '3781',
   descr => 'information about replication slots currently in use',
   proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f',
diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index 7d94f15778..61ac19e974 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -201,6 +201,12 @@ $result = $node_primary->safe_psql(
 is($result, "rep1|f|t|lost|",
 	'check that the slot became inactive and the state "lost" persists');
 
+$result = $node_primary->safe_psql(
+	'postgres',
+	qq[SELECT pg_get_slot_invalidation_cause('rep1')]);
+is($result, "1",
+	'check that the invalidation cause of the slot is obtained correctly');
+
 # Wait until current checkpoint ends
 my $checkpoint_ended = 0;
 for (my $i = 0; $i < 10 * $PostgreSQL::Test::Utils::timeout_default; $i++)
-- 
2.34.1

