Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wbwsC-002yC8-18 for pgsql-hackers@arkaria.postgresql.org; Tue, 23 Jun 2026 08:52:48 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wbws9-00AeFG-0P for pgsql-hackers@arkaria.postgresql.org; Tue, 23 Jun 2026 08:52:45 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wbws8-00AeF8-2C for pgsql-hackers@lists.postgresql.org; Tue, 23 Jun 2026 08:52:44 +0000 Received: from mail-pg1-x533.google.com ([2607:f8b0:4864:20::533]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wbws6-00000001id4-3RP0 for pgsql-hackers@postgresql.org; Tue, 23 Jun 2026 08:52:43 +0000 Received: by mail-pg1-x533.google.com with SMTP id 41be03b00d2f7-c88d4606ec8so1992831a12.0 for ; Tue, 23 Jun 2026 01:52:42 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782204761; cv=none; d=google.com; s=arc-20240605; b=gMnrq6omGzAXIbUFBHcyZHFGtmpzI7uWnA/HhgAkq/3ofUOii3Quh/TEkDjeoDOhkE ocCIchW1SwNHjdY190OhDCtCVg0ozIz8WbV6AWMnBIa3CUhy14lJr0Nj11kKG4nH8WL6 X5s4zJmz62tZtENGYHpl0KmSLknmzsydANPgKI+bo/XoR9y3hGFCjq2vS1408lrXIDiv spw8lVRGsMX625cTZytU6X4VumvCNtv6ZtEeHPa6ovmfQeyz4ynQe5Y7o5b1orP0CJVk lCvbVJw3QsjPJDa7H39qyBOxHTzGiqkzbZL/iZvZazbSFoSzjzV0SJPfQaZNSJ+XlQgD vYRw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=Smy6hEmHNGQU0ozrW0f6xYH3IJojRxnKfCF56jLnc+E=; fh=TOKVOBtFbC9J/HB2ZrsVbYtS8Z177wswe3e9x3Q9DGI=; b=KfGBI90cDTf5Es15xA34COuU66DfQaZKgz0LZ/KmbExUsnBAo0CWxrnWD0YY015M8V A+tiF3yJD+kcNE/Hsz6tjOeXyup8NOQjCCQMbCJg0Y7erFDqaOabkBzOXsrvqv7Hr8kx +UlvyKVECCUHj4LjpKbf2ffyOyHac+seMM+UDDlSUAl/7UYLeRsRvhxoPsEtHOL7afPV tPPKrm9WPErOoshRm+q7TPYXwzpjZfVlmsEYhgitN7a5RyFhMsAx0/xprAje+7Suezm3 Kx+U9NeQKBD2wM/0MkhMqvXvVTB98diaqnanLwysvGG3OBpxBTqT2HMiox/cpSozW98i TJvw==; darn=postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782204761; x=1782809561; darn=postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=Smy6hEmHNGQU0ozrW0f6xYH3IJojRxnKfCF56jLnc+E=; b=Q/hC7bUXDi2sIwJDNRDEwozOvx3vDBS71445zjSivr/rmpaYAB8K42psHdoNTbWZ0n aInYV4cTvI+e0CZLsi3Nb1Wi2T5qlmp2Sg6UnWR/G8hOpFSCkEon25Fwz4z3qfIQxFxI YaK5HL9vk284YVLv8mHatfr7L0l0nznPyMbhd9A0f7LcLPe7H7p5+YpRn8eAua6ByOxl 3qOgx75NrgPPPxkB5Qzbi7D0q5vGq525DYjuob0Vg5CNJ/n4xZa0B2B8zSE2I206RJzL kOVQXoHEaPPObw6s4I/TcyvXL0bu46u3w3at/6zqGNY7FeuXWoFo2LLxhyZ3CjiiGuGn MVaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782204761; x=1782809561; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Smy6hEmHNGQU0ozrW0f6xYH3IJojRxnKfCF56jLnc+E=; b=e4DCbSBZtec5Ck5GuD2mKUGqcpHMDeWPttlK2QTuaZUQhSeri5K23rz22bbbBVsxtI CAx1mr9PqyN05w1Zzuu4E1Lq2fc7awahcf5t8aQAL5f0AOffSRuUKLNKJnEgzVAPpLeB glItOcJoOw2l1VSXvRIbzxJbqZLcLiaHOWPWUtZ6tPZMeasXWaSXNYUUx6bdVPz5bsHQ dk3no+9ZYCLD8Gcgal4U/JTQiSah8N2LtQnO0x7EWuWMCIJUhklypNmOBautN4PULWpr EHP0eJjFP5u++moqEwKymAfzfDWAYE/YHyqlXe2vEqFwJ00kr9+DzFd4MiDyvqs80bs+ sl1Q== X-Forwarded-Encrypted: i=1; AFNElJ8e09apJyKnKxOYmLmXcDW2cd6xdtN5uvwRZ58X2Yh5+jsLL4QK7z8KR+UjH67SdDyVXHYgGrmDyzXATOLR@postgresql.org X-Gm-Message-State: AOJu0Yzdhh8Vslqq6cdaHgeRSC6zpoNIliU5+e7+HOs77qbwnPn/OuW8 7e6jfDXfwntg4SRDs57uz7MLqcGA/O7Mws+ZaNsPW+40QsE4zU6wH8wO+IwDtKePccOrH9sUJA4 PgoCEFbouLJRbvOhdKajZzzrxryAo3T4= X-Gm-Gg: AfdE7cmmFtWJwjG0remCmWtY2HpY0+Qg5PQnhdqjO5fssUpe7m9SoYjPzxGVCz0rQaA LFCKMjzuvLUITZfSeZcAkgB4KgmQySVk6AUzCXGDkCG/zWkAlXi8vr5qXXeK7AS8399dlUTSFMe kTEUlZkYlUPl9k8Q78lOOqfjxA9pIPDJ1IUhD07sxMaxPRlFgL4KVdOaLGlBphyWK3mTlOvoqBk LIU1AXjyUP+0/bQdLbOFqP/RiA2Z7PvXPtSV64ZKvmqwe4ceN9hfzPKB91Jsz5HqYe2zkR6Jvwn s1iH1ayho5TdR/jsAzJMBIV54GMMS9oQxkP1cLWWB23WXmyrW7Ns0gKQotAWcEBptfeP0f2y89e 0l1Q8zvstzHZyGAxq5I7SLWr1EEPO1g== X-Received: by 2002:a05:6a20:3d06:b0:3a3:17f8:bedd with SMTP id adf61e73a8af0-3bd18fb704amr1950933637.17.1782204761118; Tue, 23 Jun 2026 01:52:41 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Amit Kapila Date: Tue, 23 Jun 2026 14:22:29 +0530 X-Gm-Features: AVVi8CfYKp-dAIzuledlMrch4HunFhwXietV3TRq0_FhIg1EvqJkOWlJ-6W8_FU Message-ID: Subject: Re: Add a hook for handling logical decoding messages on subscribers. To: Bharath Rupireddy Cc: Masahiko Sawada , PostgreSQL-development Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Tue, Jun 23, 2026 at 4:09=E2=80=AFAM Bharath Rupireddy wrote: > > On Fri, Jun 19, 2026 at 3:34=E2=80=AFPM Masahiko Sawada wrote: > > > > Hi all, > > > > Commit ac4645c015 allows pgoutput to send logical decoding messages, > > but it's limited to applications that use the pgoutput plugin -- the > > built-in logical replication doesn't use it. I'd like to propose > > introducing a hook to the logical replication message handling so that > > extensions can plug in their own handling routine. This feature can be > > used for extensions to implement DDL replication, function > > replication, or trigger user-specific routines on the subscriber side. > > Thanks for working on this! > > > I've attached the PoC patch; it adds a hook function, and adds a new > > 'message' subscription option that allows the user to request the > > publisher to send logical decoding messages. Therefore, users need to > > enable the 'message' option and set up the hook function at server > > startup in order to receive the messages and trigger the hook > > function. > > I understand the intent of the proposal, but I'd like to get the > bigger picture first. > > Do we have any external modules that actually implement DDL > replication (or any of the listed use-cases) with a similar hook? Or > any existing discussion? I could be missing something because I > haven't looked at all the DDL replication related threads. > > Another thing I'm curious about - why a hook? Is the plan to implement > DDL replication as an external module rather than in core? If DDL > replication eventually gets into core, I'd expect it to be apply-side > logic executing the decoded DDL messages directly, not something going > through a hook. > I think it is important to have some example extension implementation to see how the hook could be utilized. One more use of such a hook could be to use for audit of DDLs replayed on subscribers. BTW, can we also consider it as a solution implementing basic DDL replication for tables? The key question is what if someday we have in-core DDL replication. I think extensions can still be used to implement filtering or transformation of DDL. We can implement capture of DDL using JSON format [1] so that it is forward compatible with in-core DDL replication. So considering that, the extension handlers will look like: _PG_init(void) { /* Publisher side */ prev_ProcessUtility =3D ProcessUtility_hook; ProcessUtility_hook =3D ddlrep_ProcessUtility; ..... /* Subscriber side */ prev_message_handler =3D logical_message_handler; logical_message_handler =3D ddlrep_message_handler; static void ddlrep_ProcessUtility(PlannedStmt *pstmt, const char *queryString, bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc) { bool replicate =3D should_replicate(pstmt); /* * Execute the DDL first. */ if (prev_ProcessUtility) prev_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc); else standard_ProcessUtility(pstmt, queryString, readOnlyTree, context= , params, queryEnv, dest, qc); if (replicate) { const char *tag =3D GetCommandTagName(CreateCommandTag(pstmt->utilityStmt)); char *msg =3D build_ddl_message(tag, queryString); /* * transactional =3D true =E2=86=92 message held in ReorderBuffe= r, emitted * to subscribers at COMMIT in WAL order relative to any DML in * the same transaction. * * omit_lsn =3D false =E2=86=92 include the LSN so the subscriber= can log * exactly which WAL position a given DDL came from. */ LogLogicalMessage("pg_ddl", msg, strlen(msg), true /* transactional */, false /* omit_lsn */); ... static void ddlrep_message_handler(const char *prefix, Size sz, const char *message, bool transactional, XLogRecPtr lsn) { char *payload; char *ddl; char *search_path; StringInfoData cmd; int spi_rc; /* * Always pass through to the previous handler first. This ensures * correct behaviour when chained with other extensions. */ if (prev_message_handler) prev_message_handler(prefix, sz, message, transactional, lsn); if (strcmp(prefix, "pg_ddl") !=3D 0) return; /* Write code to execute/perform DDL. */ When we have a built-in handler then the apply worker carefully registers the same and gives an ERROR if the extension one is already registered. void ApplyWorkerMain(Datum main_arg) { /* ... existing initialisation ... */ /* * If the subscription requests built-in DDL replication and an * extension has also registered a logical message hook, both would * process the same "pg_ddl" messages and execute DDL twice. * Refuse to start rather than silently corrupt. */ if (MySubscription->ddloption !=3D DDL_OPTION_NONE && logical_message_hook !=3D NULL) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), .... /* * No conflict =E2=80=94 register the built-in DDL handler when the * subscription requests DDL replication and no extension owns * the hook. */ if (MySubscription->ddloption !=3D DDL_OPTION_NONE) logical_message_hook =3D builtin_ddl_message_handler; [1] The JSON format for WAL could be of form to keep it extendable: { "version": 1, "command_tag": "CREATE TABLE", "object_type": "table", "schema": "public", "identity": "public.foo", "ddl_text": "CREATE TABLE public.foo (id int PRIMARY KEY)", "search_path": "public" } > > Why not a hook at apply_dispatch to give external modules more freedom > with the pgoutput plugin? > What advantage do you see with the same? --=20 With Regards, Amit Kapila.