On Tue, 20 Jan 2026 at 09:47, Amit Kapila <[email protected]> wrote:
>
> On Mon, Jan 19, 2026 at 6:36 PM vignesh C <[email protected]> wrote:
> >
> > pg_get_sequence_data() internally uses try_relation_open() rather than
> > relation_open(). As a result, if the target sequence no longer exists
> > at the time of access, the function does not raise an error and
> > instead returns NULLs for the sequence state columns. The sequence
> > sync worker code previously assumed these columns to be non NULL and
> > asserted accordingly. This assumption does not hold in the presence of
> > concurrent DDL. The patch updates the sequence sync logic to
> > explicitly check for NULL values returned from pg_get_sequence_data().
> > If any of the required sequence state fields are NULL, the sequence
> > sync worker skips processing that sequence to identify and report the
> > missing sequences. The attached patch has the changes for the same.
> >
>
> - seqinfo_local->last_value = DatumGetInt64(slot_getattr(slot, ++col, 
> &isnull));
> - Assert(!isnull);
> + /*
> + * If the sequence was dropped concurrently, pg_get_sequence_data() can
> + * return NULLs.
> + */
> + datum = slot_getattr(slot, ++col, &isnull);
> + if (isnull)
> + return COPYSEQ_SKIPPED;
> + seqinfo_local->last_value = DatumGetInt64(datum);
>
> - seqinfo_local->is_called = DatumGetBool(slot_getattr(slot, ++col, &isnull));
> - Assert(!isnull);
> + datum = slot_getattr(slot, ++col, &isnull);
> + if (isnull)
> + return COPYSEQ_SKIPPED;
> + seqinfo_local->is_called = DatumGetBool(datum);
>
> Is there a case where the first one (last_value) is non-null but later
> can be null? If not, then I think it is better to retain assert for
> other cases.

That is not possible. Updated accordingly with slight change to
comment. The attached patch has the changes for the same.

Regards,
Vignesh
From 16d1f24d30371da2a87025c98e2dae5d4954f482 Mon Sep 17 00:00:00 2001
From: Vignesh C <[email protected]>
Date: Mon, 19 Jan 2026 18:25:23 +0530
Subject: [PATCH v2] Handle concurrent sequence drop during sequence sync

pg_get_sequence_data() may return NULL values when a sequence is dropped concurrently,
as it uses try_relation_open() internally. The sequence sync worker previously assumed
non-NULL values for sequence state fields, leading to assertion failures when concurrent
DDL occurred.

Fix it by checking NULL sequence state values and skip the affected sequence gracefully.
---
 src/backend/replication/logical/sequencesync.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 509388d1bf4..2a18c24586c 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -233,6 +233,7 @@ get_and_validate_seq_info(TupleTableSlot *slot, Relation *sequence_rel,
 {
 	bool		isnull;
 	int			col = 0;
+	Datum		datum;
 	Oid			remote_typid;
 	int64		remote_start;
 	int64		remote_increment;
@@ -251,8 +252,14 @@ get_and_validate_seq_info(TupleTableSlot *slot, Relation *sequence_rel,
 	*seqinfo = seqinfo_local =
 		(LogicalRepSequenceInfo *) list_nth(seqinfos, *seqidx);
 
-	seqinfo_local->last_value = DatumGetInt64(slot_getattr(slot, ++col, &isnull));
-	Assert(!isnull);
+	/*
+	 * last_value can be NULL if the sequence was dropped concurrently (see
+	 * pg_get_sequence_data()).
+	 */
+	datum = slot_getattr(slot, ++col, &isnull);
+	if (isnull)
+		return COPYSEQ_SKIPPED;
+	seqinfo_local->last_value = DatumGetInt64(datum);
 
 	seqinfo_local->is_called = DatumGetBool(slot_getattr(slot, ++col, &isnull));
 	Assert(!isnull);
@@ -400,7 +407,7 @@ copy_sequences(WalReceiverConn *conn)
 		int			batch_skipped_count = 0;
 		int			batch_insuffperm_count = 0;
 		int			batch_missing_count;
-		Relation	sequence_rel;
+		Relation	sequence_rel = NULL;
 
 		WalRcvExecResult *res;
 		TupleTableSlot *slot;
-- 
2.43.0

Reply via email to