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 1vnXD1-008UF4-1c for pgsql-hackers@arkaria.postgresql.org; Wed, 04 Feb 2026 07:21:55 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vnXD0-009i7S-1V for pgsql-hackers@arkaria.postgresql.org; Wed, 04 Feb 2026 07:21:54 +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 1vnXCz-009i7K-2T for pgsql-hackers@lists.postgresql.org; Wed, 04 Feb 2026 07:21:53 +0000 Received: from mail-qk1-x731.google.com ([2607:f8b0:4864:20::731]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1vnXCw-00000000UB0-2gA5 for pgsql-hackers@lists.postgresql.org; Wed, 04 Feb 2026 07:21:52 +0000 Received: by mail-qk1-x731.google.com with SMTP id af79cd13be357-8c6af798a83so744495785a.0 for ; Tue, 03 Feb 2026 23:21:50 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1770189709; x=1770794509; darn=lists.postgresql.org; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:from:to:cc:subject:date:message-id:reply-to; bh=DpKP2Pbdo0d9iGqZTAq2cbpVbIMMucYQBPCmtZXAaZc=; b=WQigzv5+tAQT8SZMowxcs7dKOpJHdirGYoGrsZotn5Jh7BgdHuD3tTsDaNIHhgeuBD zoQZwTu3cdZTQrLTBNDvNnkNGa9aL69xMAdpT8qO8MV+74cN3RpcmpopHBI+41s9bPe+ uOjxDHfuS4CmsKRjftaS4i8rO8q8hqwwCT8RFcSZOfPXLp69uYwmI0RgD5zT+F5y6MFl XhDyZUywCs6R9dw6XbQH4hPyzIcwaWtSJ3qsW+/Hf3TxaFgvLycLQPSjLxgvTL+5ldF9 Y541ysFfWuKcjCrTFBSIrGx0nL/MjH5ufOrQeQJTjAkbxeHRU81KFzz6NEP5lUV81tU+ agZQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770189709; x=1770794509; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=DpKP2Pbdo0d9iGqZTAq2cbpVbIMMucYQBPCmtZXAaZc=; b=RncQWdvrku3FJ16MUICKz2LE2oz8z5dWQs5N9R/1DrgfGrmyy6QTm/GVFvssAj5fm6 WVocjc8Fapjgb/P0Sd1t70U9/GLRHUNJi8/CrSVEV2GkqxUkF8vd7J8QmTXsCI2EyZBh hQrlHBRNEftOS2KQS6WMDa2pOCyZXtD04ye0YEOV1dWA5UwKhYEkJC35yAdr/IEDyAO7 ccEkhH57yaN+/Rx8Cwp7HEdG8uMC/JLyjwQhjkQNKgROG1LpeCmOUHGMBpHz5yNk6jNt oL6wwI5GBgu7Geh5DCaw5byIulHALFMF4a304I5ZC/71E3flVAPUofroMYIe4aKozeBQ OGYg== X-Forwarded-Encrypted: i=1; AJvYcCWigaw9D3SovYyPU4s2CiTnOzIKtbgL3ni1wNvv3HyhlitFmQHoMEoLNs/mAWkSBvwlyqWZp5aDcP9p7j/4@lists.postgresql.org X-Gm-Message-State: AOJu0YzoDVVIAZptQFJDhh7IqhQItx1P/eMsKsEzN7BKGTo1HEmHUyLM mm87TG6pCfiKof9D6RajfqdtDphQv8r9PApAn7Wa1PBKblKp+f6p8JGD X-Gm-Gg: AZuq6aKzumnIhj5+HHhd4PfgCA8CvHsT3p0ERxb0sTmbU6CQKHu9NblzP3RPCL0tUi0 iKMYAAEYFWWH18fLkPys4VBjBFQf/0PdfY4m/Ugya74NG3BMTMY4yysLo3anXa55z3AblWM5tci SYGfS6LPjTB3Vs4DJf9d9kWYSjmmcW/JxKSBCYaDBJN3FHjFgC0OpB1C8xgHDs/6IiPF3plZj69 o5x3J7rmr5JgFqFrWHfeMjHDD1FI5w6Dt4txjir3Q3Tlemj6G4+eFl/N18ZDerFsNY2jl5DSKcE 7JlxWLNyfx6Gw78yHMZfSHDxUKhUPjMO08nndKrghSw1UgdRNQBg8xjqkMsbO5RF1yb7eLTeXEf Zm+eTw9R15NE7U8ZWxkJ/BzmC1CYcnrqRhu9YuErqGauGN44F+XjtMCuNNpIRzxQeitnIurn1Ll 6Y3ufMdeyc0/hs4xsUcJRKdp6BtmoFOe8= X-Received: by 2002:a05:620a:414d:b0:8c7:177f:cc1e with SMTP id af79cd13be357-8ca2f9fb623mr276677885a.67.1770189709128; Tue, 03 Feb 2026 23:21:49 -0800 (PST) Received: from smtpclient.apple ([209.127.78.222]) by smtp.gmail.com with ESMTPSA id af79cd13be357-8ca2fd52b2fsm133118485a.47.2026.02.03.23.21.43 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 03 Feb 2026 23:21:47 -0800 (PST) From: Chao Li Message-Id: Content-Type: multipart/mixed; boundary="Apple-Mail=_047BEB1B-0A05-4E04-A85C-14ED5C01A4B6" Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.300.41.1.7\)) Subject: Re: Improve logical replication usability when tables lack primary keys Date: Wed, 4 Feb 2026 15:21:32 +0800 In-Reply-To: <75C23B7E-8923-4CD1-83A1-F2E6E9B0D5D3@gmail.com> Cc: Euler Taveira , GRANT ZHOU , "houzj.fnst@fujitsu.com" , Dilip Kumar , Postgres hackers To: Amit Kapila References: <5ABD7727-CD22-4112-A186-0E788EE78109@gmail.com> <23A24BFF-18A7-4FE9-AAFA-13E1AA207DD0@gmail.com> <19dac243-1f46-4720-bdec-bf0f851d03b9@app.fastmail.com> <875BBCC0-CF08-4136-8E9E-F03DF75C3A11@gmail.com> <75C23B7E-8923-4CD1-83A1-F2E6E9B0D5D3@gmail.com> X-Mailer: Apple Mail (2.3864.300.41.1.7) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Apple-Mail=_047BEB1B-0A05-4E04-A85C-14ED5C01A4B6 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On Dec 30, 2025, at 16:07, Chao Li wrote: >=20 >=20 >=20 >> On Dec 22, 2025, at 19:48, Amit Kapila = wrote: >>=20 >> On Fri, Dec 19, 2025 at 1:39=E2=80=AFPM Chao Li = wrote: >>>=20 >>>> On Dec 18, 2025, at 22:49, Euler Taveira wrote: >>>>=20 >>>> On Wed, Dec 17, 2025, at 6:43 PM, GRANT ZHOU wrote: >>>>> On Wed, Dec 17, 2025 at 12:50=E2=80=AFPM Euler Taveira = wrote: >>>>>> Each table needs to say what's its row identifier. The user = created a table >>>>>> without primary key. Well, create a primary key. There are dozens = of thousands >>>>>> of objects. Use a script. >>>>> However, I=E2=80=99d like to share a user perspective regarding = the "use a >>>>> script" approach. The main value of `FOR TABLES IN SCHEMA` is >>>>> *in-database automation*. If users still need to maintain external >>>>> scripts to monitor and `ALTER` new tables to prevent replication >>>>> errors, it significantly diminishes the value of that automation. >>>>>=20 >>>>=20 >>>> As I tried to explain in the previous email, the problem with FOR = ALL TABLES >>>> and FOR TABLES IN SCHEMA syntax is that the is no catalog = information about the >>>> relations; the list of relations is collected at runtime. >>>>=20 >>>> When I suggested "use a script" I was referring to fix the logical = replication >>>> setup regarding the lack of primary key. There is no need to have = an automation >>>> outside the database, use an event trigger. If your lazy user = doesn't create >>>> the primary key, assign REPLICA IDENTITY FULL. Something like >>>>=20 >>>> -- This example is far from being a complete solution for fixing = the lack of >>>> -- primary key in a logical replication scenario. >>>> -- ALTER TABLE should be supported too >>>> CREATE OR REPLACE FUNCTION event_trigger_for_replica_identity() >>>> RETURNS event_trigger LANGUAGE plpgsql AS $$ >>>> DECLARE >>>> obj record; >>>> rec record; >>>> ricnt integer :=3D 0; >>>> BEGIN >>>> FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() >>>> LOOP >>>> IF obj.command_tag =3D 'CREATE TABLE' THEN >>>> SELECT COUNT(*) INTO ricnt FROM pg_index WHERE indrelid =3D = obj.objid AND indisprimary; >>>> RAISE NOTICE 'ricnt: %', ricnt; >>>> IF ricnt =3D 0 THEN >>>> EXECUTE 'ALTER TABLE ' || obj.object_identity || ' = REPLICA IDENTITY FULL'; >>>> END IF; >>>> END IF; >>>> END LOOP; >>>> END; >>>> $$; >>>>=20 >>>> CREATE EVENT TRIGGER event_trigger_for_replica_identity >>>> ON ddl_command_end >>>> EXECUTE FUNCTION event_trigger_for_replica_identity(); >>>>=20 >>>> CREATE TABLE event_trigger_test_1 (a int); >>>> \d+ event_trigger_test_1 >>>> CREATE TABLE event_trigger_test_2 (a int primary key); >>>> \d+ event_trigger_test_2 >>>> CREATE TABLE event_trigger_test_3 (a int, b text not null, primary = key(b)); >>>> \d+ event_trigger_test_3 >>>> --ALTER TABLE event_trigger_test_3 DROP CONSTRAINT = event_trigger_test_3_pkey; >>>> --\d+ event_trigger_test_3 >>>>=20 >>>> DROP EVENT TRIGGER event_trigger_for_replica_identity; >>>> DROP FUNCTION event_trigger_for_replica_identity; >>>> DROP TABLE event_trigger_test_1, event_trigger_test_2, = event_trigger_test_3; >>>>=20 >>>> = 8<------------------------------------------------------------------------= ----8< >>>>=20 >>>>> Additionally, tables without Primary Keys are valid SQL and = extremely >>>>> common in enterprise environments (e.g., audit logs, data = warehousing). >>>>> In large-scale deployments, enforcing PKs on every single table = isn't >>>>> always practical. >>>>>=20 >>>>=20 >>>> I'm not saying users shouldn't create tables without a primary key. = I'm arguing >>>> that this decision should take into account what adjustments need = to be made to >>>> use these tables in logical replication. >>>>=20 >>>>>=20 >>>>> I think the goal of this proposal is not to change the underlying = table >>>>> property design, but rather to seek a mechanism (like a = Publication >>>>> option) to ensure this automation functions safely without = external >>>>> intervention. It is simply about allowing the database to handle = these >>>>> valid, common scenarios gracefully when automation is enabled. >>>>>=20 >>>>=20 >>>> You didn't get it. You already have one property to handle it and = you are >>>> proposing to add a second property to handle it. >>>>=20 >>>> I think you are pursuing the wrong solution. IMO we need a solution = to enforce >>>> that the logical replication contract is valid. If you create or = modify a table >>>> that is part of a publication, there is no validation that that = table complies >>>> with the publication properties (update and delete properties = should require an >>>> appropriate replica identity). We should close the gaps in both = publication and >>>> table. >>>>=20 >>=20 >> If we want, we can ensure that any table added to that specific >> publication (that has an option replica_identy=3D'full') would >> automatically override the default to FULL, if PK is not available. >> This information can be cached to avoid overhead. >>=20 >>>=20 >>> If I summarize Euler=E2=80=99s position in short words: discipline = over convenience. I actually strongly agree with that. In PG we = generally prefer explicit over implicit behavior, and predictability = over magic. >>>=20 >>=20 >> You haven't told why we can't consider a custom event trigger as >> suggested by Euler for customers who are not willing to change the RI >> default explicitly for each table. I think it is worth considering >> providing a custom solution outside core-postgres for your customers >> for this specific case. >=20 > Thanks for raising this. Let me clarify why we don=E2=80=99t consider = a custom event trigger a satisfactory solution in practice, even though = it is technically possible. >=20 > I discussed this with our field teams, and some customers have indeed = experimented with event-trigger-based solutions before. However, they = generally don=E2=80=99t prefer them for this use case. >=20 > First, the required logic is non-trivial and fragile. The trigger = would need to track table creation, primary key creation and removal, = and distinguish between cases where REPLICA IDENTITY FULL was set = implicitly versus explicitly by the user. Handling all these cases = correctly makes the solution feel like a workaround rather than a robust = enforcement mechanism. >=20 > Second, event triggers introduce operational risk. They need to be = installed, monitored, and maintained separately from the core system. If = a trigger is accidentally dropped, disabled, or modified, the behavior = silently changes, which is particularly risky for replication semantics. >=20 > Third, customers place much higher trust in core PostgreSQL behavior = than in custom scripts layered on top. Issues caused by core behavior = are seen as something that can be understood, worked around, or fixed by = upgrading, whereas failures caused by custom triggers are harder to = diagnose and are often attributed to the overall solution quality. >=20 > For these reasons, while event triggers can work as a stopgap, our = customers strongly prefer a solution where the replication contract is = enforced by core PostgreSQL rather than external mechanisms. >=20 >>=20 >>> Based on the discussion so far, I think we share the following = design goals: >>>=20 >>> 1) Keep replica identity as a table property. >>> 2) Avoid silent runtime failures when FOR TABLES IN SCHEMA pulls in = tables without primary keys. >>> 3) Avoid global or implicit behavior changes. >>> 4) Preserve explicit opt-in for higher WAL cost. >>> 5) Keep the logical replication contract explicit and enforceable. >>>=20 >>> I=E2=80=99ve been thinking about whether adding a new replica = identity could meet these goals. >>>=20 >>> Today we have four replica identities: DEFAULT (PK, fallback to = NONE), INDEX, FULL, and NONE. >>>=20 >>> What if we introduce a new replica identity, tentatively called = =E2=80=9CFORCE=E2=80=9D: PK with fallback to FULL. (Let=E2=80=99s keep = our focus on the design, not argue the name for now.) >>>=20 >>> With this approach: >>>=20 >>> 1) Replica identity remains a table property. >>> 2) Publication membership is still evaluated at runtime, so FOR = TABLES IN SCHEMA is not special-cased. >>> 3) No new GUCs are required. >>> 4) The user must explicitly opt in by setting the replica identity. = Once FORCE is chosen, adding or dropping a primary key later does not = silently break UPDATE/DELETE replication. >>>=20 >>> 5) The logical replication contract remains explicit; the table = declares that it is safe for UPDATE/DELETE replication even without a = PK, at the cost of higher WAL volume. >>>=20 >>> This feels like a small, explicit extension of the existing RI = semantics. Notably, REPLICA IDENTITY DEFAULT already has conditional = behavior (PK fallback >>> to NONE), so conditional RI behavior is not new, this would just = make a different fallback explicit and user-chosen. >>>=20 >>> After that, we could consider a database-level = default_replica_identity setting, applied at table creation time, for = environments that want this behavior consistently. But that would only = make sense if we first agree on the table-level mechanism. >>>=20 >>=20 >> I don't much like the database-level option as it expects a new >> default to be introduced. I think the internal working will almost be >> same as the option at publication-level. >=20 > That=E2=80=99s fair. I agree that a database-level option wouldn=E2=80=99= t be fundamentally different from a publication-level solution and would = likely share most of the same internal mechanics. >=20 > At this point nothing is decided yet; we=E2=80=99re still exploring = different approaches and trying to understand the trade-offs. >=20 > I have a question to better understand how a publication-level = approach would behave in edge cases. >=20 > Since replica identity is defined on tables and a table can belong to = multiple publications, how should UPDATE/DELETE be handled if the same = table is added to two publications with different expectations? >=20 > For example, suppose a table without a PK is added to: > - pub_a, which does not require FULL (or effectively falls back to = NONE) > - pub_b, which requires FULL for UPDATE/DELETE >=20 > In this case, should UPDATE/DELETE on the table be allowed at all, and = if so, based on which publication=E2=80=99s semantics? What do you = think? >=20 > Best regards, > -- > Chao Li (Evan) > HighGo Software Co., Ltd. > https://www.highgo.com/ Hi Amit, Following your suggestion, I implemented a PoC that adds a new = publication parameter (tentatively named fallbackfull) to make the = DEFAULT =E2=86=92 FULL fallback behavior per-publication. I=E2=80=99m = not attached to the parameter name =E2=80=94 if we decide to go with the = publication approach, I=E2=80=99m happy to adjust naming based on = feedback. After playing with this implementation for a couple of days, I ran into = a few concerns: 1. Protocol extension required If the DEFAULT =E2=86=92 FULL fallback is triggered, the subscriber = needs to know whether the corresponding publication has fallbackfull = enabled in order to decide how to apply UPDATE/DELETE. That means we=E2=80= =99d need to extend the logical replication protocol, e.g., by adding a = new field to the RELATION message to carry the fallbackfull flag. 2. Impact on decoding plugins Decoding plugins would need to understand this new flag. In my PoC, I = updated pgoutput, but there may be third-party plugins that would also = need changes. That feels like a compatibility risk. 3. Potential data-integrity issues This is the most concerning part to me. Consider a table t1 with REPLICA IDENTITY DEFAULT and no primary key, = included in publication p1. By design, UPDATE/DELETE on t1 are not = allowed. However, a user could work around this by creating a dummy publication, = adding t1 to it, and setting fallbackfull =3D true on that publication. = This would effectively enable UPDATE/DELETE on t1. Later, if the owner of p1 decides to enable fallbackfull on p1 to = replicate t1, the subscriber of p1 may already be out of sync due to the = earlier updates/deletes performed via the dummy publication. At that = point, subsequent UPDATE/DELETE replication may fail or behave = incorrectly. =46rom this perspective, allowing fallbackfull at the publication level = seems to open the door to cross-publication interference and data = divergence. Given these concerns, I=E2=80=99m leaning toward keeping fallbackfull as = a per-table option rather than a per-publication one. Curious to hear = your thoughts. I=E2=80=99ve attached v2 of the PoC implementing the publication-level = approach for reference. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/ --Apple-Mail=_047BEB1B-0A05-4E04-A85C-14ED5C01A4B6 Content-Disposition: attachment; filename=v2-0001-Add-fallbackfull-option-to-publication.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="v2-0001-Add-fallbackfull-option-to-publication.patch" Content-Transfer-Encoding: quoted-printable =46rom=20a128e79d5da7b70386fe0063db09812255827eb2=20Mon=20Sep=2017=20= 00:00:00=202001=0AFrom:=20"Chao=20Li=20(Evan)"=20=0A= Date:=20Mon,=202=20Feb=202026=2015:50:57=20+0800=0ASubject:=20[PATCH=20= v2]=20Add=20fallbackfull=20option=20to=20publication=0A=0AWhen=20a=20= publication's=20fallbackfull=20option=20is=20true,=20then=20tables=20in=0A= the=20publication=20get=20an=20ability=20of=20fallback=20replica=20to=20= FULL=20if=20a=0Atable's=20replica=20identity=20is=20DEFAULT=20but=20has=20= no=20PK.=0A=0AThe=20code=20change=20in=20currently=20at=20PoC=20quality=20= level,=20dirty=20logs=0Aare=20included,=20so=20please=20reviewers=20only=20= focus=20on=20the=20design=20part.=0A=0AAuthor:=20Chao=20Li=20= =0A---=0A=20src/backend/access/heap/heapam.c=20=20=20=20=20= =20=20=20=20=20=20=20|=2061=20++++++++++++---------=0A=20= src/backend/catalog/pg_publication.c=20=20=20=20=20=20=20=20|=20=201=20+=0A= =20src/backend/commands/publicationcmds.c=20=20=20=20=20=20|=2058=20= +++++++++++++++++---=0A=20src/backend/executor/execReplication.c=20=20=20= =20=20=20|=20=202=20+-=0A=20src/backend/replication/logical/proto.c=20=20= =20=20=20|=2027=20++++++++-=0A=20= src/backend/replication/logical/relation.c=20=20|=2050=20= ++++++++++++++++-=0A=20src/backend/replication/logical/worker.c=20=20=20=20= |=2028=20+++++++++-=0A=20src/backend/replication/pgoutput/pgoutput.c=20|=20= 33=20++++++++++-=0A=20src/include/catalog/pg_publication.h=20=20=20=20=20= =20=20=20|=20=204=20++=0A=20src/include/replication/logicalproto.h=20=20=20= =20=20=20|=2013=20++++-=0A=20src/include/replication/logicalrelation.h=20= =20=20|=20=201=20+=0A=2011=20files=20changed,=20233=20insertions(+),=20= 45=20deletions(-)=0A=0Adiff=20--git=20a/src/backend/access/heap/heapam.c=20= b/src/backend/access/heap/heapam.c=0Aindex=203004964ab7f..d4f78b9807e=20= 100644=0A---=20a/src/backend/access/heap/heapam.c=0A+++=20= b/src/backend/access/heap/heapam.c=0A@@=20-60,7=20+60,8=20@@=20static=20= HeapTuple=20heap_prepare_insert(Relation=20relation,=20HeapTuple=20tup,=0A= =20static=20XLogRecPtr=20log_heap_update(Relation=20reln,=20Buffer=20= oldbuf,=0A=20=09=09=09=09=09=09=09=09=20=20Buffer=20newbuf,=20HeapTuple=20= oldtup,=0A=20=09=09=09=09=09=09=09=09=20=20HeapTuple=20newtup,=20= HeapTuple=20old_key_tuple,=0A-=09=09=09=09=09=09=09=09=20=20bool=20= all_visible_cleared,=20bool=20new_all_visible_cleared);=0A+=09=09=09=09=09= =09=09=09=20=20bool=20all_visible_cleared,=20bool=20= new_all_visible_cleared,=0A+=09=09=09=09=09=09=09=09=20=20bool=20= logical_identity_is_full);=0A=20#ifdef=20USE_ASSERT_CHECKING=0A=20static=20= void=20check_lock_if_inplace_updateable_rel(Relation=20relation,=0A=20=09= =09=09=09=09=09=09=09=09=09=09=09=20const=20ItemPointerData=20*otid,=0A= @@=20-107,7=20+108,7=20@@=20static=20void=20= index_delete_sort(TM_IndexDeleteOp=20*delstate);=0A=20static=20int=09= bottomup_sort_and_shrink(TM_IndexDeleteOp=20*delstate);=0A=20static=20= XLogRecPtr=20log_heap_new_cid(Relation=20relation,=20HeapTuple=20tup);=0A= =20static=20HeapTuple=20ExtractReplicaIdentity(Relation=20relation,=20= HeapTuple=20tp,=20bool=20key_required,=0A-=09=09=09=09=09=09=09=09=09=09= bool=20*copy);=0A+=09=09=09=09=09=09=09=09=09=09bool=20*copy,=20bool=20= *ri_is_full);=0A=20=0A=20=0A=20/*=0A@@=20-2859,6=20+2860,7=20@@=20= heap_delete(Relation=20relation,=20const=20ItemPointerData=20*tid,=0A=20=09= bool=09=09all_visible_cleared=20=3D=20false;=0A=20=09HeapTuple=09= old_key_tuple=20=3D=20NULL;=09/*=20replica=20identity=20of=20the=20tuple=20= */=0A=20=09bool=09=09old_key_copied=20=3D=20false;=0A+=09bool=09=09= logical_identity_is_full=20=3D=20false;=0A=20=0A=20=09= Assert(ItemPointerIsValid(tid));=0A=20=0A@@=20-3088,7=20+3090,7=20@@=20= l1:=0A=20=09=20*=20Compute=20replica=20identity=20tuple=20before=20= entering=20the=20critical=20section=20so=0A=20=09=20*=20we=20don't=20= PANIC=20upon=20a=20memory=20allocation=20failure.=0A=20=09=20*/=0A-=09= old_key_tuple=20=3D=20ExtractReplicaIdentity(relation,=20&tp,=20true,=20= &old_key_copied);=0A+=09old_key_tuple=20=3D=20= ExtractReplicaIdentity(relation,=20&tp,=20true,=20&old_key_copied,=20= &logical_identity_is_full);=0A=20=0A=20=09/*=0A=20=09=20*=20If=20this=20= is=20the=20first=20possibly-multixact-able=20operation=20in=20the=20= current=0A@@=20-3170,13=20+3172,13=20@@=20l1:=0A=20=09=09xlrec.offnum=20= =3D=20ItemPointerGetOffsetNumber(&tp.t_self);=0A=20=09=09xlrec.xmax=20=3D=20= new_xmax;=0A=20=0A-=09=09if=20(old_key_tuple=20!=3D=20NULL)=0A-=09=09{=0A= -=09=09=09if=20(relation->rd_rel->relreplident=20=3D=3D=20= REPLICA_IDENTITY_FULL)=0A-=09=09=09=09xlrec.flags=20|=3D=20= XLH_DELETE_CONTAINS_OLD_TUPLE;=0A-=09=09=09else=0A-=09=09=09=09= xlrec.flags=20|=3D=20XLH_DELETE_CONTAINS_OLD_KEY;=0A-=09=09}=0A+=09=09=09= if=20(old_key_tuple=20!=3D=20NULL)=0A+=09=09=09{=0A+=09=09=09=09if=20= (logical_identity_is_full)=0A+=09=09=09=09=09xlrec.flags=20|=3D=20= XLH_DELETE_CONTAINS_OLD_TUPLE;=0A+=09=09=09=09else=0A+=09=09=09=09=09= xlrec.flags=20|=3D=20XLH_DELETE_CONTAINS_OLD_KEY;=0A+=09=09=09}=0A=20=0A=20= =09=09XLogBeginInsert();=0A=20=09=09XLogRegisterData(&xlrec,=20= SizeOfHeapDelete);=0A@@=20-3326,6=20+3328,7=20@@=20heap_update(Relation=20= relation,=20const=20ItemPointerData=20*otid,=20HeapTuple=20newtup,=0A=20=09= HeapTuple=09heaptup;=0A=20=09HeapTuple=09old_key_tuple=20=3D=20NULL;=0A=20= =09bool=09=09old_key_copied=20=3D=20false;=0A+=09bool=09=09= logical_identity_is_full=20=3D=20false;=0A=20=09Page=09=09page;=0A=20=09= BlockNumber=20block;=0A=20=09MultiXactStatus=20mxact_status;=0A@@=20= -4113,7=20+4116,7=20@@=20l2:=0A=20=09old_key_tuple=20=3D=20= ExtractReplicaIdentity(relation,=20&oldtup,=0A=20=09=09=09=09=09=09=09=09= =09=09=20=20=20bms_overlap(modified_attrs,=20id_attrs)=20||=0A=20=09=09=09= =09=09=09=09=09=09=09=20=20=20id_has_external,=0A-=09=09=09=09=09=09=09=09= =09=09=20=20=20&old_key_copied);=0A+=09=09=09=09=09=09=09=09=09=09=20=20=20= &old_key_copied,=20&logical_identity_is_full);=0A=20=0A=20=09/*=20NO=20= EREPORT(ERROR)=20from=20here=20till=20changes=20are=20logged=20*/=0A=20=09= START_CRIT_SECTION();=0A@@=20-4200,11=20+4203,12=20@@=20l2:=0A=20=09=09=09= log_heap_new_cid(relation,=20heaptup);=0A=20=09=09}=0A=20=0A-=09=09= recptr=20=3D=20log_heap_update(relation,=20buffer,=0A-=09=09=09=09=09=09=09= =09=20newbuf,=20&oldtup,=20heaptup,=0A-=09=09=09=09=09=09=09=09=20= old_key_tuple,=0A-=09=09=09=09=09=09=09=09=20all_visible_cleared,=0A-=09=09= =09=09=09=09=09=09=20all_visible_cleared_new);=0A+=09=09=09recptr=20=3D=20= log_heap_update(relation,=20buffer,=0A+=09=09=09=09=09=09=09=09=09=20= newbuf,=20&oldtup,=20heaptup,=0A+=09=09=09=09=09=09=09=09=09=20= old_key_tuple,=0A+=09=09=09=09=09=09=09=09=09=20all_visible_cleared,=0A+=09= =09=09=09=09=09=09=09=09=20all_visible_cleared_new,=0A+=09=09=09=09=09=09= =09=09=09=20logical_identity_is_full);=0A=20=09=09if=20(newbuf=20!=3D=20= buffer)=0A=20=09=09{=0A=20=09=09=09PageSetLSN(BufferGetPage(newbuf),=20= recptr);=0A@@=20-8918,7=20+8922,8=20@@=20static=20XLogRecPtr=0A=20= log_heap_update(Relation=20reln,=20Buffer=20oldbuf,=0A=20=09=09=09=09= Buffer=20newbuf,=20HeapTuple=20oldtup,=20HeapTuple=20newtup,=0A=20=09=09=09= =09HeapTuple=20old_key_tuple,=0A-=09=09=09=09bool=20all_visible_cleared,=20= bool=20new_all_visible_cleared)=0A+=09=09=09=09bool=20= all_visible_cleared,=20bool=20new_all_visible_cleared,=0A+=09=09=09=09= bool=20logical_identity_is_full)=0A=20{=0A=20=09xl_heap_update=20xlrec;=0A= =20=09xl_heap_header=20xlhdr;=0A@@=20-9007,14=20+9012,14=20@@=20= log_heap_update(Relation=20reln,=20Buffer=20oldbuf,=0A=20=09=09= xlrec.flags=20|=3D=20XLH_UPDATE_SUFFIX_FROM_OLD;=0A=20=09if=20= (need_tuple_data)=0A=20=09{=0A-=09=09xlrec.flags=20|=3D=20= XLH_UPDATE_CONTAINS_NEW_TUPLE;=0A-=09=09if=20(old_key_tuple)=0A-=09=09{=0A= -=09=09=09if=20(reln->rd_rel->relreplident=20=3D=3D=20= REPLICA_IDENTITY_FULL)=0A-=09=09=09=09xlrec.flags=20|=3D=20= XLH_UPDATE_CONTAINS_OLD_TUPLE;=0A-=09=09=09else=0A-=09=09=09=09= xlrec.flags=20|=3D=20XLH_UPDATE_CONTAINS_OLD_KEY;=0A-=09=09}=0A+=09=09=09= xlrec.flags=20|=3D=20XLH_UPDATE_CONTAINS_NEW_TUPLE;=0A+=09=09=09if=20= (old_key_tuple)=0A+=09=09=09{=0A+=09=09=09=09if=20= (logical_identity_is_full)=0A+=09=09=09=09=09xlrec.flags=20|=3D=20= XLH_UPDATE_CONTAINS_OLD_TUPLE;=0A+=09=09=09=09else=0A+=09=09=09=09=09= xlrec.flags=20|=3D=20XLH_UPDATE_CONTAINS_OLD_KEY;=0A+=09=09=09}=0A=20=09= }=0A=20=0A=20=09/*=20If=20new=20tuple=20is=20the=20single=20and=20first=20= tuple=20on=20page...=20*/=0A@@=20-9219,7=20+9224,7=20@@=20= log_heap_new_cid(Relation=20relation,=20HeapTuple=20tup)=0A=20=20*/=0A=20= static=20HeapTuple=0A=20ExtractReplicaIdentity(Relation=20relation,=20= HeapTuple=20tp,=20bool=20key_required,=0A-=09=09=09=09=09=20=20=20bool=20= *copy)=0A+=09=09=09=09=09=20=20=20bool=20*copy,=20bool=20*ri_is_full)=0A=20= {=0A=20=09TupleDesc=09desc=20=3D=20RelationGetDescr(relation);=0A=20=09= char=09=09replident=20=3D=20relation->rd_rel->relreplident;=0A@@=20= -9229,6=20+9234,7=20@@=20ExtractReplicaIdentity(Relation=20relation,=20= HeapTuple=20tp,=20bool=20key_required,=0A=20=09Datum=09=09= values[MaxHeapAttributeNumber];=0A=20=0A=20=09*copy=20=3D=20false;=0A+=09= *ri_is_full=20=3D=20false;=0A=20=0A=20=09if=20= (!RelationIsLogicallyLogged(relation))=0A=20=09=09return=20NULL;=0A@@=20= -9236,7=20+9242,7=20@@=20ExtractReplicaIdentity(Relation=20relation,=20= HeapTuple=20tp,=20bool=20key_required,=0A=20=09if=20(replident=20=3D=3D=20= REPLICA_IDENTITY_NOTHING)=0A=20=09=09return=20NULL;=0A=20=0A-=09if=20= (replident=20=3D=3D=20REPLICA_IDENTITY_FULL)=0A+=09if=20= (logicalrep_identity_is_full(relation))=0A=20=09{=0A=20=09=09/*=0A=20=09=09= =20*=20When=20logging=20the=20entire=20old=20tuple,=20it=20very=20well=20= could=20contain=0A@@=20-9247,6=20+9253,7=20@@=20= ExtractReplicaIdentity(Relation=20relation,=20HeapTuple=20tp,=20bool=20= key_required,=0A=20=09=09=09*copy=20=3D=20true;=0A=20=09=09=09tp=20=3D=20= toast_flatten_tuple(tp,=20desc);=0A=20=09=09}=0A+=09=09*ri_is_full=20=3D=20= true;=0A=20=09=09return=20tp;=0A=20=09}=0A=20=0Adiff=20--git=20= a/src/backend/catalog/pg_publication.c=20= b/src/backend/catalog/pg_publication.c=0Aindex=20= 9a4791c573e..8e17ead4f84=20100644=0A---=20= a/src/backend/catalog/pg_publication.c=0A+++=20= b/src/backend/catalog/pg_publication.c=0A@@=20-1097,6=20+1097,7=20@@=20= GetPublication(Oid=20pubid)=0A=20=09pub->pubactions.pubtruncate=20=3D=20= pubform->pubtruncate;=0A=20=09pub->pubviaroot=20=3D=20= pubform->pubviaroot;=0A=20=09pub->pubgencols_type=20=3D=20= pubform->pubgencols;=0A+=09pub->pubfallbackfull=20=3D=20= pubform->pubfallbackfull;=0A=20=0A=20=09ReleaseSysCache(tup);=0A=20=0A= diff=20--git=20a/src/backend/commands/publicationcmds.c=20= b/src/backend/commands/publicationcmds.c=0Aindex=20= fc3a4c19e65..de0245530fe=20100644=0A---=20= a/src/backend/commands/publicationcmds.c=0A+++=20= b/src/backend/commands/publicationcmds.c=0A@@=20-81,13=20+81,16=20@@=20= parse_publication_options(ParseState=20*pstate,=0A=20=09=09=09=09=09=09=20= =20bool=20*publish_via_partition_root_given,=0A=20=09=09=09=09=09=09=20=20= bool=20*publish_via_partition_root,=0A=20=09=09=09=09=09=09=20=20bool=20= *publish_generated_columns_given,=0A-=09=09=09=09=09=09=20=20char=20= *publish_generated_columns)=0A+=09=09=09=09=09=09=20=20char=20= *publish_generated_columns,=0A+=09=09=09=09=09=09=20=20bool=20= *fallbackfull_given,=0A+=09=09=09=09=09=09=20=20bool=20*fallbackfull)=0A=20= {=0A=20=09ListCell=20=20=20*lc;=0A=20=0A=20=09*publish_given=20=3D=20= false;=0A=20=09*publish_via_partition_root_given=20=3D=20false;=0A=20=09= *publish_generated_columns_given=20=3D=20false;=0A+=09= *fallbackfull_given=20=3D=20false;=0A=20=0A=20=09/*=20defaults=20*/=0A=20= =09pubactions->pubinsert=20=3D=20true;=0A@@=20-96,6=20+99,7=20@@=20= parse_publication_options(ParseState=20*pstate,=0A=20=09= pubactions->pubtruncate=20=3D=20true;=0A=20=09= *publish_via_partition_root=20=3D=20false;=0A=20=09= *publish_generated_columns=20=3D=20PUBLISH_GENCOLS_NONE;=0A+=09= *fallbackfull=20=3D=20false;=0A=20=0A=20=09/*=20Parse=20options=20*/=0A=20= =09foreach(lc,=20options)=0A@@=20-168,6=20+172,13=20@@=20= parse_publication_options(ParseState=20*pstate,=0A=20=09=09=09= *publish_generated_columns_given=20=3D=20true;=0A=20=09=09=09= *publish_generated_columns=20=3D=20defGetGeneratedColsOption(defel);=0A=20= =09=09}=0A+=09=09else=20if=20(strcmp(defel->defname,=20"fallbackfull")=20= =3D=3D=200)=0A+=09=09{=0A+=09=09=09if=20(*fallbackfull_given)=0A+=09=09=09= =09errorConflictingDefElem(defel,=20pstate);=0A+=09=09=09= *fallbackfull_given=20=3D=20true;=0A+=09=09=09*fallbackfull=20=3D=20= defGetBoolean(defel);=0A+=09=09}=0A=20=09=09else=0A=20=09=09=09= ereport(ERROR,=0A=20=09=09=09=09=09(errcode(ERRCODE_SYNTAX_ERROR),=0A@@=20= -281,6=20+292,7=20@@=20pub_rf_contains_invalid_column(Oid=20pubid,=20= Relation=20relation,=20List=20*ancestors,=0A=20=09bool=09=09result=20=3D=20= false;=0A=20=09Datum=09=09rfdatum;=0A=20=09bool=09=09rfisnull;=0A+=09= Publication=20*pub;=0A=20=0A=20=09/*=0A=20=09=20*=20FULL=20means=20all=20= columns=20are=20in=20the=20REPLICA=20IDENTITY,=20so=20all=20columns=20= are=0A@@=20-289,6=20+301,20=20@@=20pub_rf_contains_invalid_column(Oid=20= pubid,=20Relation=20relation,=20List=20*ancestors,=0A=20=09if=20= (relation->rd_rel->relreplident=20=3D=3D=20REPLICA_IDENTITY_FULL)=0A=20=09= =09return=20false;=0A=20=0A+=09/*=20=0A+=09=20*=20If=20REPLICA=20= INDENTITY=20is=20DEFAULT=20and=20no=20replica=20index=20exists,=20see=20= if=20we=20=0A+=09=20*=20should=20fallback=20to=20FULL.=0A+=09=20*/=0A+=09= if=20(relation->rd_rel->relreplident=20=3D=3D=20REPLICA_IDENTITY_DEFAULT=20= &&=20=0A+=09=09!OidIsValid(RelationGetReplicaIndex(relation)))=0A+=09{=0A= +=09=09Publication=20*=20pub=20=3D=20GetPublication(pubid);=0A+=09=09if=20= (pub->pubfallbackfull)=0A+=09=09{=0A+=09=09=09return=20false;=09=0A+=09=09= }=0A+=09}=0A+=0A=20=09/*=0A=20=09=20*=20For=20a=20partition,=20if=20= pubviaroot=20is=20true,=20find=20the=20topmost=20ancestor=20that=0A=20=09= =20*=20is=20published=20via=20this=20publication=20as=20we=20need=20to=20= use=20its=20row=20filter=0A@@=20-394,9=20+420,12=20@@=20= pub_contains_invalid_column(Oid=20pubid,=20Relation=20relation,=20List=20= *ancestors,=0A=20=09pub=20=3D=20GetPublication(pubid);=0A=20=09= check_and_fetch_column_list(pub,=20publish_as_relid,=20NULL,=20= &columns);=0A=20=0A-=09if=20(relation->rd_rel->relreplident=20=3D=3D=20= REPLICA_IDENTITY_FULL)=0A+=09if=20(relation->rd_rel->relreplident=20=3D=3D= =20REPLICA_IDENTITY_FULL=0A+=09=09||=20(relation->rd_rel->relreplident=20= =3D=3D=20REPLICA_IDENTITY_DEFAULT=20&&=0A+=09=09=09= !OidIsValid(RelationGetReplicaIndex(relation))=20&&=20=0A+=09=09=09= pub->pubfallbackfull))=0A=20=09{=0A-=09=09/*=20With=20REPLICA=20IDENTITY=20= FULL,=20no=20column=20list=20is=20allowed.=20*/=0A+=09=09/*=20With=20= REPLICA=20IDENTITY=20FULL=20or=20fallback=20to=20FULL,=20no=20column=20= list=20is=20allowed.=20*/=0A=20=09=09*invalid_column_list=20=3D=20= (columns=20!=3D=20NULL);=0A=20=0A=20=09=09/*=0A@@=20-842,6=20+871,8=20@@=20= CreatePublication(ParseState=20*pstate,=20CreatePublicationStmt=20*stmt)=0A= =20=09bool=09=09publish_via_partition_root;=0A=20=09bool=09=09= publish_generated_columns_given;=0A=20=09char=09=09= publish_generated_columns;=0A+=09bool=09=09fallbackfull_given;=0A+=09= bool=09=09fallbackfull;=0A=20=09AclResult=09aclresult;=0A=20=09List=09=20= =20=20*relations=20=3D=20NIL;=0A=20=09List=09=20=20=20*schemaidlist=20=3D=20= NIL;=0A@@=20-886,11=20+917,13=20@@=20CreatePublication(ParseState=20= *pstate,=20CreatePublicationStmt=20*stmt)=0A=20=09=09=09=09=09=09=09=20=20= &publish_via_partition_root_given,=0A=20=09=09=09=09=09=09=09=20=20= &publish_via_partition_root,=0A=20=09=09=09=09=09=09=09=20=20= &publish_generated_columns_given,=0A-=09=09=09=09=09=09=09=20=20= &publish_generated_columns);=0A+=09=09=09=09=09=09=09=20=20= &publish_generated_columns,=0A+=09=09=09=09=09=09=09=20=20= &fallbackfull_given,=0A+=09=09=09=09=09=09=09=20=20&fallbackfull);=0A=20=0A= =20=09if=20(stmt->for_all_sequences=20&&=0A=20=09=09(publish_given=20||=20= publish_via_partition_root_given=20||=0A-=09=09=20= publish_generated_columns_given))=0A+=09=09=20= publish_generated_columns_given=20||=20fallbackfull_given))=0A=20=09=09= ereport(NOTICE,=0A=20=09=09=09=09= errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),=0A=20=09=09=09=09= errmsg("publication=20parameters=20are=20not=20applicable=20to=20= sequence=20synchronization=20and=20will=20be=20ignored=20for=20= sequences"));=0A@@=20-914,6=20+947,8=20@@=20CreatePublication(ParseState=20= *pstate,=20CreatePublicationStmt=20*stmt)=0A=20=09=09= BoolGetDatum(publish_via_partition_root);=0A=20=09= values[Anum_pg_publication_pubgencols=20-=201]=20=3D=0A=20=09=09= CharGetDatum(publish_generated_columns);=0A+=09= values[Anum_pg_publication_pubfallbackfull=20-=201]=20=3D=0A+=09=09= BoolGetDatum(fallbackfull);=0A=20=0A=20=09tup=20=3D=20= heap_form_tuple(RelationGetDescr(rel),=20values,=20nulls);=0A=20=0A@@=20= -1010,6=20+1045,8=20@@=20AlterPublicationOptions(ParseState=20*pstate,=20= AlterPublicationStmt=20*stmt,=0A=20=09bool=09=09= publish_via_partition_root;=0A=20=09bool=09=09= publish_generated_columns_given;=0A=20=09char=09=09= publish_generated_columns;=0A+=09bool=09=09fallbackfull_given;=0A+=09= bool=09=09fallbackfull;=0A=20=09ObjectAddress=20obj;=0A=20=09= Form_pg_publication=20pubform;=0A=20=09List=09=20=20=20*root_relids=20=3D=20= NIL;=0A@@=20-1023,11=20+1060,13=20@@=20= AlterPublicationOptions(ParseState=20*pstate,=20AlterPublicationStmt=20= *stmt,=0A=20=09=09=09=09=09=09=09=20=20= &publish_via_partition_root_given,=0A=20=09=09=09=09=09=09=09=20=20= &publish_via_partition_root,=0A=20=09=09=09=09=09=09=09=20=20= &publish_generated_columns_given,=0A-=09=09=09=09=09=09=09=20=20= &publish_generated_columns);=0A+=09=09=09=09=09=09=09=20=20= &publish_generated_columns,=0A+=09=09=09=09=09=09=09=20=20= &fallbackfull_given,=0A+=09=09=09=09=09=09=09=20=20&fallbackfull);=0A=20=0A= =20=09if=20(pubform->puballsequences=20&&=0A=20=09=09(publish_given=20||=20= publish_via_partition_root_given=20||=0A-=09=09=20= publish_generated_columns_given))=0A+=09=09=20= publish_generated_columns_given=20||=20fallbackfull_given))=0A=20=09=09= ereport(NOTICE,=0A=20=09=09=09=09= errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),=0A=20=09=09=09=09= errmsg("publication=20parameters=20are=20not=20applicable=20to=20= sequence=20synchronization=20and=20will=20be=20ignored=20for=20= sequences"));=0A@@=20-1143,6=20+1182,11=20@@=20= AlterPublicationOptions(ParseState=20*pstate,=20AlterPublicationStmt=20= *stmt,=0A=20=09=09values[Anum_pg_publication_pubgencols=20-=201]=20=3D=20= CharGetDatum(publish_generated_columns);=0A=20=09=09= replaces[Anum_pg_publication_pubgencols=20-=201]=20=3D=20true;=0A=20=09}=0A= +=09if=20(fallbackfull_given)=0A+=09{=0A+=09=09= values[Anum_pg_publication_pubfallbackfull=20-=201]=20=3D=20= BoolGetDatum(fallbackfull);=0A+=09=09= replaces[Anum_pg_publication_pubfallbackfull=20-=201]=20=3D=20true;=0A+=09= }=0A=20=0A=20=09tup=20=3D=20heap_modify_tuple(tup,=20= RelationGetDescr(rel),=20values,=20nulls,=0A=20=09=09=09=09=09=09=09= replaces);=0Adiff=20--git=20a/src/backend/executor/execReplication.c=20= b/src/backend/executor/execReplication.c=0Aindex=20= 743b1ee2b28..0605f18e0e9=20100644=0A---=20= a/src/backend/executor/execReplication.c=0A+++=20= b/src/backend/executor/execReplication.c=0A@@=20-1089,7=20+1089,7=20@@=20= CheckCmdReplicaIdentity(Relation=20rel,=20CmdType=20cmd)=0A=20=09=09= return;=0A=20=0A=20=09/*=20REPLICA=20IDENTITY=20FULL=20is=20also=20good=20= for=20UPDATE/DELETE.=20*/=0A-=09if=20(rel->rd_rel->relreplident=20=3D=3D=20= REPLICA_IDENTITY_FULL)=0A+=09if=20(logicalrep_identity_is_full(rel))=0A=20= =09=09return;=0A=20=0A=20=09/*=0Adiff=20--git=20= a/src/backend/replication/logical/proto.c=20= b/src/backend/replication/logical/proto.c=0Aindex=20= 3950dd0cf46..36fed2c686b=20100644=0A---=20= a/src/backend/replication/logical/proto.c=0A+++=20= b/src/backend/replication/logical/proto.c=0A@@=20-452,6=20+452,10=20@@=20= logicalrep_write_update(StringInfo=20out,=20TransactionId=20xid,=20= Relation=20rel,=0A=20=09=09=09=09=09=09bool=20binary,=20Bitmapset=20= *columns,=0A=20=09=09=09=09=09=09PublishGencolsType=20= include_gencols_type)=0A=20{=0A+=09ereport(LOG,=0A+=09=09=09= (errmsg("EVAN:=20logical=20replication:=20send=20UPDATE=20for=20relation=20= \"%s\"=20(oid=20%u)",=0A+=09=09=09=09=09RelationGetRelationName(rel),=20= RelationGetRelid(rel))));=0A+=0A=20=09pq_sendbyte(out,=20= LOGICAL_REP_MSG_UPDATE);=0A=20=0A=20=09Assert(rel->rd_rel->relreplident=20= =3D=3D=20REPLICA_IDENTITY_DEFAULT=20||=0A@@=20-534,6=20+538,10=20@@=20= logicalrep_write_delete(StringInfo=20out,=20TransactionId=20xid,=20= Relation=20rel,=0A=20=09=09=20=20=20rel->rd_rel->relreplident=20=3D=3D=20= REPLICA_IDENTITY_FULL=20||=0A=20=09=09=20=20=20rel->rd_rel->relreplident=20= =3D=3D=20REPLICA_IDENTITY_INDEX);=0A=20=0A+=09ereport(LOG,=0A+=09=09=09= (errmsg("EVAN:=20logical=20replication:=20send=20DELETE=20for=20relation=20= \"%s\"=20(oid=20%u)",=0A+=09=09=09=09=09RelationGetRelationName(rel),=20= RelationGetRelid(rel))));=0A+=0A=20=09pq_sendbyte(out,=20= LOGICAL_REP_MSG_DELETE);=0A=20=0A=20=09/*=20transaction=20ID=20(if=20not=20= valid,=20we're=20not=20streaming)=20*/=0A@@=20-666,10=20+674,15=20@@=20= logicalrep_write_message(StringInfo=20out,=20TransactionId=20xid,=20= XLogRecPtr=20lsn,=0A=20void=0A=20logicalrep_write_rel(StringInfo=20out,=20= TransactionId=20xid,=20Relation=20rel,=0A=20=09=09=09=09=09=20Bitmapset=20= *columns,=0A-=09=09=09=09=09=20PublishGencolsType=20= include_gencols_type)=0A+=09=09=09=09=09=20PublishGencolsType=20= include_gencols_type,=0A+=09=09=09=09=09=20bool=20fallbackfull,=20uint32=20= proto_version)=0A=20{=0A=20=09char=09=20=20=20*relname;=0A=20=0A+=09= ereport(LOG,=0A+=09=09=09(errmsg("EVAN:=20logical=20replication:=20send=20= RELATION=20for=20\"%s\"=20(oid=20%u),=20fallbackfull=3D%s",=0A+=09=09=09=09= =09RelationGetRelationName(rel),=20RelationGetRelid(rel),=20fallbackfull=20= ?=20"true"=20:=20"false")));=0A+=0A=20=09pq_sendbyte(out,=20= LOGICAL_REP_MSG_RELATION);=0A=20=0A=20=09/*=20transaction=20ID=20(if=20= not=20valid,=20we're=20not=20streaming)=20*/=0A@@=20-687,6=20+700,10=20= @@=20logicalrep_write_rel(StringInfo=20out,=20TransactionId=20xid,=20= Relation=20rel,=0A=20=09/*=20send=20replica=20identity=20*/=0A=20=09= pq_sendbyte(out,=20rel->rd_rel->relreplident);=0A=20=0A+=09/*=20send=20= publication=20fallbackfull=20flag=20if=20supported=20*/=0A+=09if=20= (proto_version=20>=3D=20LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM)=0A+=09= =09pq_sendbyte(out,=20fallbackfull=20?=201=20:=200);=0A+=0A=20=09/*=20= send=20the=20attribute=20info=20*/=0A=20=09logicalrep_write_attrs(out,=20= rel,=20columns,=20include_gencols_type);=0A=20}=0A@@=20-695,7=20+712,7=20= @@=20logicalrep_write_rel(StringInfo=20out,=20TransactionId=20xid,=20= Relation=20rel,=0A=20=20*=20Read=20the=20relation=20info=20from=20stream=20= and=20return=20as=20LogicalRepRelation.=0A=20=20*/=0A=20= LogicalRepRelation=20*=0A-logicalrep_read_rel(StringInfo=20in)=0A= +logicalrep_read_rel(StringInfo=20in,=20uint32=20proto_version)=0A=20{=0A= =20=09LogicalRepRelation=20*rel=20=3D=20= palloc_object(LogicalRepRelation);=0A=20=0A@@=20-708,6=20+725,12=20@@=20= logicalrep_read_rel(StringInfo=20in)=0A=20=09/*=20Read=20the=20replica=20= identity.=20*/=0A=20=09rel->replident=20=3D=20pq_getmsgbyte(in);=0A=20=0A= +=09/*=20Read=20publication=20fallbackfull=20flag=20if=20present.=20*/=0A= +=09if=20(proto_version=20>=3D=20= LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM)=0A+=09=09rel->fallbackfull=20= =3D=20(pq_getmsgbyte(in)=20!=3D=200);=0A+=09else=0A+=09=09= rel->fallbackfull=20=3D=20false;=0A+=0A=20=09/*=20relkind=20is=20not=20= sent=20*/=0A=20=09rel->relkind=20=3D=200;=0A=20=0Adiff=20--git=20= a/src/backend/replication/logical/relation.c=20= b/src/backend/replication/logical/relation.c=0Aindex=20= 0b1d80b5b0f..28b43e901f3=20100644=0A---=20= a/src/backend/replication/logical/relation.c=0A+++=20= b/src/backend/replication/logical/relation.c=0A@@=20-21,6=20+21,7=20@@=0A= =20#include=20"access/genam.h"=0A=20#include=20"access/table.h"=0A=20= #include=20"catalog/namespace.h"=0A+#include=20= "catalog/pg_publication.h"=0A=20#include=20= "catalog/pg_subscription_rel.h"=0A=20#include=20"executor/executor.h"=0A=20= #include=20"nodes/makefuncs.h"=0A@@=20-209,6=20+210,7=20@@=20= logicalrep_relmap_update(LogicalRepRelation=20*remoterel)=0A=20=09=09= (remoterel->relkind=20=3D=3D=200)=20?=20RELKIND_RELATION=20:=20= remoterel->relkind;=0A=20=0A=20=09entry->remoterel.attkeys=20=3D=20= bms_copy(remoterel->attkeys);=0A+=09entry->remoterel.fallbackfull=20=3D=20= remoterel->fallbackfull;=0A=20=09MemoryContextSwitchTo(oldctx);=0A=20}=0A= =20=0A@@=20-326,7=20+328,10=20@@=20= logicalrep_rel_mark_updatable(LogicalRepRelMapEntry=20*entry)=0A=20=09=09= =20*=20If=20no=20replica=20identity=20index=20and=20no=20PK,=20the=20= published=20table=20must=0A=20=09=09=20*=20have=20replica=20identity=20= FULL.=0A=20=09=09=20*/=0A-=09=09if=20(idkey=20=3D=3D=20NULL=20&&=20= remoterel->replident=20!=3D=20REPLICA_IDENTITY_FULL)=0A+=09=09if=20= (idkey=20=3D=3D=20NULL=20&&=0A+=09=09=09remoterel->replident=20!=3D=20= REPLICA_IDENTITY_FULL=20&&=0A+=09=09=09!(remoterel->replident=20=3D=3D=20= REPLICA_IDENTITY_DEFAULT=20&&=0A+=09=09=09=20=20= remoterel->fallbackfull))=0A=20=09=09=09entry->updatable=20=3D=20false;=0A= =20=09}=0A=20=0A@@=20-713,6=20+718,7=20@@=20= logicalrep_partition_open(LogicalRepRelMapEntry=20*root,=0A=20=09=09=09= entry->remoterel.atttyps[i]=20=3D=20remoterel->atttyps[i];=0A=20=09=09}=0A= =20=09=09entry->remoterel.replident=20=3D=20remoterel->replident;=0A+=09=09= entry->remoterel.fallbackfull=20=3D=20remoterel->fallbackfull;=0A=20=09=09= entry->remoterel.attkeys=20=3D=20bms_copy(remoterel->attkeys);=0A=20=09}=0A= =20=0A@@=20-777,6=20+783,44=20@@=20= logicalrep_partition_open(LogicalRepRelMapEntry=20*root,=0A=20=09return=20= entry;=0A=20}=0A=20=0A+/*=0A+=20*=20logicalrep_identity_is_full=0A+=20*=0A= +=20*=20Check=20whether=20the=20replica=20identity=20of=20the=20relation=20= is=20full=20or=20not.=0A+=20*=20When=20a=20table's=20replica=20identity=20= is=20default,=20but=20there=20is=20no=20primary=20key,=0A+=20*=20if=20= any=20publication=20the=20relation=20is=20in=20has=20fallbackfull=20= enabled,=20we=20consider=0A+=20*=20the=20replica=20identity=20as=20full.=20= This=20function=20should=20only=20be=20called=20on=20the=0A+=20*=20= publisher.=0A+=20*/=0A+bool=0A+logicalrep_identity_is_full(Relation=20= relation)=0A+{=0A+=20=20=20=20Form_pg_class=20relform=20=3D=20= RelationGetForm(relation);=0A+=0A+=20=20=20=20if=20= (relform->relreplident=20=3D=3D=20REPLICA_IDENTITY_FULL)=0A+=20=20=20=20=20= =20=20=20return=20true;=0A+=0A+=20=20=20=20if=20(relform->relreplident=20= =3D=3D=20REPLICA_IDENTITY_DEFAULT=20&&=0A+=20=20=20=20=20=20=20=20= !OidIsValid(RelationGetReplicaIndex(relation)))=0A+=20=20=20=20{=0A+=09=09= /*=20relreplident=20is=20default,=20but=20no=20primary=20key,=20check=20= if=20we=20can=20fallback=20to=20full.=20*/=09=0A+=09=09List=20*pubids=20= =3D=20GetRelationPublications(RelationGetRelid(relation));=0A+=09=09= foreach_oid(pubid,=20pubids)=0A+=09=09{=0A+=09=09=09Publication=20*pub=20= =3D=20GetPublication(pubid);=0A+=0A+=09=09=09if=20(pub->pubfallbackfull)=0A= +=09=09=09{=0A+=09=09=09=09list_free(pubids);=0A+=09=09=09=09return=20= true;=0A+=09=09=09}=0A+=09=09}=0A+=09=09list_free(pubids);=0A+=09}=0A+=0A= +=20=20=20=20return=20false;=0A+}=0A+=0A=20/*=0A=20=20*=20Returns=20the=20= oid=20of=20an=20index=20that=20can=20be=20used=20by=20the=20apply=20= worker=20to=20scan=0A=20=20*=20the=20relation.=0A@@=20-938,7=20+982,9=20= @@=20FindLogicalRepLocalIndex(Relation=20localrel,=20LogicalRepRelation=20= *remoterel,=0A=20=09if=20(OidIsValid(idxoid))=0A=20=09=09return=20= idxoid;=0A=20=0A-=09if=20(remoterel->replident=20=3D=3D=20= REPLICA_IDENTITY_FULL)=0A+=09if=20(remoterel->replident=20=3D=3D=20= REPLICA_IDENTITY_FULL=20||=0A+=09=09(remoterel->replident=20=3D=3D=20= REPLICA_IDENTITY_DEFAULT=20&&=0A+=09=09=20remoterel->fallbackfull))=0A=20= =09{=0A=20=09=09/*=0A=20=09=09=20*=20We=20are=20looking=20for=20one=20= more=20opportunity=20for=20using=20an=20index.=20If=0Adiff=20--git=20= a/src/backend/replication/logical/worker.c=20= b/src/backend/replication/logical/worker.c=0Aindex=20= 32725c48623..ad683eb822d=20100644=0A---=20= a/src/backend/replication/logical/worker.c=0A+++=20= b/src/backend/replication/logical/worker.c=0A@@=20-486,6=20+486,7=20@@=20= static=20XLogRecPtr=20remote_final_lsn=20=3D=20InvalidXLogRecPtr;=0A=20=0A= =20/*=20fields=20valid=20only=20when=20processing=20streamed=20= transaction=20*/=0A=20static=20bool=20in_streamed_transaction=20=3D=20= false;=0A+static=20uint32=20LogicalRepProtoVersion=20=3D=20= LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM;=0A=20=0A=20static=20= TransactionId=20stream_xid=20=3D=20InvalidTransactionId;=0A=20=0A@@=20= -2567,7=20+2568,7=20@@=20apply_handle_relation(StringInfo=20s)=0A=20=09= if=20(handle_streamed_transaction(LOGICAL_REP_MSG_RELATION,=20s))=0A=20=09= =09return;=0A=20=0A-=09rel=20=3D=20logicalrep_read_rel(s);=0A+=09rel=20=3D= =20logicalrep_read_rel(s,=20LogicalRepProtoVersion);=0A=20=09= logicalrep_relmap_update(rel);=0A=20=0A=20=09/*=20Also=20reset=20all=20= entries=20in=20the=20partition=20map=20that=20refer=20to=20remoterel.=20= */=0A@@=20-3050,6=20+3051,24=20@@=20apply_handle_delete(StringInfo=20s)=0A= =20=09/*=20Check=20if=20we=20can=20do=20the=20delete.=20*/=0A=20=09= check_relation_updatable(rel);=0A=20=0A+=09/*=0A+=09=20*=20Before=20= fallbackfull=20was=20added=20as=20an=20option=20to=20publication,=20if=20= a=20table's=0A+=09=20*=20replica=20identity=20is=20default=20but=20= without=20a=20PK,=20it=20cannot=20be=20updated,=20so=0A+=09=20*=20= check_relation_updatable()=20treated=20unexpected=20update=20as=20error.=0A= +=09=20*=0A+=09=20*=20However,=20with=20fallbackfull,=20such=20table=20= can=20be=20updated=20because=20the=20table=0A+=09=20*=20might=20belong=20= to=20anther=20publication=20with=20fallbackfull=20enabled.=20So=20here=0A= +=09=20*=20we=20just=20skip=20the=20update=20if=20the=20table=20in=20= current=20publication=20is=20not=0A+=09=20*=20updatable.=0A+=09=20*/=0A+=09= if=20(!rel->updatable)=0A+=09{=0A+=09=09/*=20XXX:=20should=20close=20= with=20NoLock=20or=20RowExclusiveLock???=20*/=0A+=09=09= logicalrep_rel_close(rel,=20NoLock);=0A+=09=09end_replication_step();=0A= +=09=09return;=0A+=09}=0A+=0A=20=09/*=0A=20=09=20*=20Make=20sure=20that=20= any=20user-supplied=20code=20runs=20as=20the=20table=20owner,=20unless=0A= =20=09=20*=20the=20user=20has=20opted=20out=20of=20that=20behavior.=0A@@=20= -3189,7=20+3208,8=20@@=20FindReplTupleInLocalRel(ApplyExecutionData=20= *edata,=20Relation=20localrel,=0A=20=09*localslot=20=3D=20= table_slot_create(localrel,=20&estate->es_tupleTable);=0A=20=0A=20=09= Assert(OidIsValid(localidxoid)=20||=0A-=09=09=20=20=20= (remoterel->replident=20=3D=3D=20REPLICA_IDENTITY_FULL));=0A+=09=09=20=20= =20(remoterel->replident=20=3D=3D=20REPLICA_IDENTITY_FULL)=20||=0A+=09=09= =20=20=20(remoterel->replident=20=3D=3D=20REPLICA_IDENTITY_DEFAULT=20&&=20= remoterel->fallbackfull));=0A=20=0A=20=09if=20(OidIsValid(localidxoid))=0A= =20=09{=0A@@=20-5523,11=20+5543,15=20@@=20= set_stream_options(WalRcvStreamOptions=20*options,=0A=20=0A=20=09= server_version=20=3D=20walrcv_server_version(LogRepWorkerWalRcvConn);=0A=20= =09options->proto.logical.proto_version=20=3D=0A+=09=09server_version=20= >=3D=20190000=20?=20LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM=20:=0A=20=09= =09server_version=20>=3D=20160000=20?=20= LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM=20:=0A=20=09=09= server_version=20>=3D=20150000=20?=20= LOGICALREP_PROTO_TWOPHASE_VERSION_NUM=20:=0A=20=09=09server_version=20>=3D= =20140000=20?=20LOGICALREP_PROTO_STREAM_VERSION_NUM=20:=0A=20=09=09= LOGICALREP_PROTO_VERSION_NUM;=0A=20=0A+=09/*=20Cache=20the=20chosen=20= protocol=20version=20for=20RELATION=20message=20parsing.=20*/=0A+=09= LogicalRepProtoVersion=20=3D=20options->proto.logical.proto_version;=0A+=0A= =20=09options->proto.logical.publication_names=20=3D=20= MySubscription->publications;=0A=20=09options->proto.logical.binary=20=3D=20= MySubscription->binary;=0A=20=0Adiff=20--git=20= a/src/backend/replication/pgoutput/pgoutput.c=20= b/src/backend/replication/pgoutput/pgoutput.c=0Aindex=20= e016f64e0b3..09344d63a85=20100644=0A---=20= a/src/backend/replication/pgoutput/pgoutput.c=0A+++=20= b/src/backend/replication/pgoutput/pgoutput.c=0A@@=20-145,6=20+145,19=20= @@=20typedef=20struct=20RelationSyncEntry=0A=20=09/*=20are=20we=20= publishing=20this=20rel?=20*/=0A=20=09PublicationActions=20pubactions;=0A= =20=0A+=09/*=20when=20replica=20identity=20is=20default=20but=20no=20pk,=20= should=20we=20fallback=20to=20full?=20*/=0A+=09bool=09=09fallbackfull;=0A= +=0A+=09/*=0A+=09=20*=20when=20replica=20identity=20is=20defaut=20and=20= no=20pk=20and=20fallbackfull=20is=20false,=20we=0A+=09=20*=20should=20= not=20send=20any=20updates/deletes=20for=20this=20relation.=20But=20if=20= the=20table=0A+=09=20*=20belong=20to=20another=20publication=20that=20= has=20falllbackfull=20enabled,=20then=20the=0A+=09=20*=20talbe=20can=20= still=20be=20updated/deleted,=20thus=20still=20has=20WAL=20generated,=20= but=0A+=09=20*=20we=20should=20skip=20sending=20those=20changes=20to=20= downstream.=20This=20flag=20is=20used=0A+=09=20*=20to=20indicate=20that.=0A= +=09=20*/=0A+=09bool=09=09block_update_delete;=0A+=0A=20=09/*=0A=20=09=20= *=20ExprState=20array=20for=20row=20filter.=20Different=20publication=20= actions=20don't=0A=20=09=20*=20allow=20multiple=20expressions=20to=20= always=20be=20combined=20into=20one,=20because=0A@@=20-799,6=20+812,7=20= @@=20send_relation_and_attrs(Relation=20relation,=20TransactionId=20xid,=0A= =20=09=09=09=09=09=09LogicalDecodingContext=20*ctx,=0A=20=09=09=09=09=09=09= RelationSyncEntry=20*relentry)=0A=20{=0A+=09PGOutputData=20*data=20=3D=20= (PGOutputData=20*)=20ctx->output_plugin_private;=0A=20=09TupleDesc=09= desc=20=3D=20RelationGetDescr(relation);=0A=20=09Bitmapset=20=20*columns=20= =3D=20relentry->columns;=0A=20=09PublishGencolsType=20= include_gencols_type=20=3D=20relentry->include_gencols_type;=0A@@=20= -830,7=20+844,9=20@@=20send_relation_and_attrs(Relation=20relation,=20= TransactionId=20xid,=0A=20=0A=20=09OutputPluginPrepareWrite(ctx,=20= false);=0A=20=09logicalrep_write_rel(ctx->out,=20xid,=20relation,=20= columns,=0A-=09=09=09=09=09=09=20include_gencols_type);=0A+=09=09=09=09=09= =09=20include_gencols_type,=0A+=09=09=09=09=09=09=20= relentry->fallbackfull,=0A+=09=09=09=09=09=09=20data->protocol_version);=0A= =20=09OutputPluginWrite(ctx,=20false);=0A=20}=0A=20=0A@@=20-1519,10=20= +1535,14=20@@=20pgoutput_change(LogicalDecodingContext=20*ctx,=20= ReorderBufferTXN=20*txn,=0A=20=09=09case=20REORDER_BUFFER_CHANGE_UPDATE:=0A= =20=09=09=09if=20(!relentry->pubactions.pubupdate)=0A=20=09=09=09=09= return;=0A+=09=09=09if=20(relentry->block_update_delete)=0A+=09=09=09=09= return;=0A=20=09=09=09break;=0A=20=09=09case=20= REORDER_BUFFER_CHANGE_DELETE:=0A=20=09=09=09if=20= (!relentry->pubactions.pubdelete)=0A=20=09=09=09=09return;=0A+=09=09=09= if=20(relentry->block_update_delete)=0A+=09=09=09=09return;=0A=20=0A=20=09= =09=09/*=0A=20=09=09=09=20*=20This=20is=20only=20possible=20if=20deletes=20= are=20allowed=20even=20when=20replica=0A@@=20-2057,6=20+2077,7=20@@=20= get_rel_sync_entry(PGOutputData=20*data,=20Relation=20relation)=0A=20=09= bool=09=09found;=0A=20=09MemoryContext=20oldctx;=0A=20=09Oid=09=09=09= relid=20=3D=20RelationGetRelid(relation);=0A+=09Form_pg_class=20relform=20= =3D=20RelationGetForm(relation);=0A=20=0A=20=09Assert(RelationSyncCache=20= !=3D=20NULL);=0A=20=0A@@=20-2075,6=20+2096,7=20@@=20= get_rel_sync_entry(PGOutputData=20*data,=20Relation=20relation)=0A=20=09=09= entry->streamed_txns=20=3D=20NIL;=0A=20=09=09entry->pubactions.pubinsert=20= =3D=20entry->pubactions.pubupdate=20=3D=0A=20=09=09=09= entry->pubactions.pubdelete=20=3D=20entry->pubactions.pubtruncate=20=3D=20= false;=0A+=09=09entry->fallbackfull=20=3D=20false;=0A=20=09=09= entry->new_slot=20=3D=20NULL;=0A=20=09=09entry->old_slot=20=3D=20NULL;=0A= =20=09=09memset(entry->exprstate,=200,=20sizeof(entry->exprstate));=0A@@=20= -2130,6=20+2152,7=20@@=20get_rel_sync_entry(PGOutputData=20*data,=20= Relation=20relation)=0A=20=09=09entry->pubactions.pubupdate=20=3D=20= false;=0A=20=09=09entry->pubactions.pubdelete=20=3D=20false;=0A=20=09=09= entry->pubactions.pubtruncate=20=3D=20false;=0A+=09=09= entry->fallbackfull=20=3D=20false;=0A=20=0A=20=09=09/*=0A=20=09=09=20*=20= Tuple=20slots=20cleanups.=20(Will=20be=20rebuilt=20later=20if=20needed).=0A= @@=20-2267,6=20+2290,7=20@@=20get_rel_sync_entry(PGOutputData=20*data,=20= Relation=20relation)=0A=20=09=09=09=09entry->pubactions.pubupdate=20|=3D=20= pub->pubactions.pubupdate;=0A=20=09=09=09=09entry->pubactions.pubdelete=20= |=3D=20pub->pubactions.pubdelete;=0A=20=09=09=09=09= entry->pubactions.pubtruncate=20|=3D=20pub->pubactions.pubtruncate;=0A+=09= =09=09=09entry->fallbackfull=20|=3D=20pub->pubfallbackfull;=0A=20=0A=20=09= =09=09=09/*=0A=20=09=09=09=09=20*=20We=20want=20to=20publish=20the=20= changes=20as=20the=20top-most=20ancestor=0A@@=20-2302,6=20+2326,13=20@@=20= get_rel_sync_entry(PGOutputData=20*data,=20Relation=20relation)=0A=20=09=09= =09}=0A=20=09=09}=0A=20=0A+=09=09if=20(!entry->fallbackfull=20&&=0A+=09=09= =09relform->relreplident=20=3D=3D=20REPLICA_IDENTITY_DEFAULT=20&&=0A+=09=09= =09!OidIsValid(RelationGetReplicaIndex(relation)))=0A+=09=09=09= entry->block_update_delete=20=3D=20true;=0A+=09=09else=0A+=09=09=09= entry->block_update_delete=20=3D=20false;=0A+=0A=20=09=09= entry->publish_as_relid=20=3D=20publish_as_relid;=0A=20=0A=20=09=09/*=0A= diff=20--git=20a/src/include/catalog/pg_publication.h=20= b/src/include/catalog/pg_publication.h=0Aindex=20= 368becca899..f58418c2131=20100644=0A---=20= a/src/include/catalog/pg_publication.h=0A+++=20= b/src/include/catalog/pg_publication.h=0A@@=20-66,6=20+66,9=20@@=20= CATALOG(pg_publication,6104,PublicationRelationId)=0A=20=09=20*=20if=20= stored=20generated=20column=20data=20should=20be=20published.=0A=20=09=20= */=0A=20=09char=09=09pubgencols;=0A+=0A+=09/*=20true=20if=20fallbackfull=20= is=20enabled=20*/=0A+=09bool=09=09pubfallbackfull;=0A=20}=20= FormData_pg_publication;=0A=20=0A=20/*=20----------------=0A@@=20-138,6=20= +141,7=20@@=20typedef=20struct=20Publication=0A=20=09bool=09=09= allsequences;=0A=20=09bool=09=09pubviaroot;=0A=20=09PublishGencolsType=20= pubgencols_type;=0A+=09bool=09=09pubfallbackfull;=0A=20=09= PublicationActions=20pubactions;=0A=20}=20Publication;=0A=20=0Adiff=20= --git=20a/src/include/replication/logicalproto.h=20= b/src/include/replication/logicalproto.h=0Aindex=20= 058a955e20c..1898ed2120c=20100644=0A---=20= a/src/include/replication/logicalproto.h=0A+++=20= b/src/include/replication/logicalproto.h=0A@@=20-36,13=20+36,17=20@@=0A=20= =20*=20LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM=20is=20the=20minimum=20= protocol=20version=0A=20=20*=20where=20we=20support=20applying=20large=20= streaming=20transactions=20in=20parallel.=0A=20=20*=20Introduced=20in=20= PG16.=0A+=20*=0A+=20*=20LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM=20is=20= the=20minimum=20protocol=20version=0A+=20*=20that=20includes=20the=20= fallbackfull=20flag=20in=20RELATION=20messages.=0A=20=20*/=0A=20#define=20= LOGICALREP_PROTO_MIN_VERSION_NUM=201=0A=20#define=20= LOGICALREP_PROTO_VERSION_NUM=201=0A=20#define=20= LOGICALREP_PROTO_STREAM_VERSION_NUM=202=0A=20#define=20= LOGICALREP_PROTO_TWOPHASE_VERSION_NUM=203=0A=20#define=20= LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM=204=0A-#define=20= LOGICALREP_PROTO_MAX_VERSION_NUM=20= LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM=0A+#define=20= LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM=205=0A+#define=20= LOGICALREP_PROTO_MAX_VERSION_NUM=20= LOGICALREP_PROTO_FALLBACKFULL_VERSION_NUM=0A=20=0A=20/*=0A=20=20*=20= Logical=20message=20types=0A@@=20-111,6=20+115,7=20@@=20typedef=20struct=20= LogicalRepRelation=0A=20=09char=09=20=20**attnames;=09=09/*=20column=20= names=20*/=0A=20=09Oid=09=09=20=20=20*atttyps;=09=09/*=20column=20types=20= */=0A=20=09char=09=09replident;=09=09/*=20replica=20identity=20*/=0A+=09= bool=09=09fallbackfull;=09/*=20publication=20fallback=20to=20full=20= identity=20*/=0A=20=09char=09=09relkind;=09=09/*=20remote=20relation=20= kind=20*/=0A=20=09Bitmapset=20=20*attkeys;=09=09/*=20Bitmap=20of=20key=20= columns=20*/=0A=20}=20LogicalRepRelation;=0A@@=20-250,8=20+255,10=20@@=20= extern=20void=20logicalrep_write_message(StringInfo=20out,=20= TransactionId=20xid,=20XLogRecP=0A=20=09=09=09=09=09=09=09=09=09=20bool=20= transactional,=20const=20char=20*prefix,=20Size=20sz,=20const=20char=20= *message);=0A=20extern=20void=20logicalrep_write_rel(StringInfo=20out,=20= TransactionId=20xid,=0A=20=09=09=09=09=09=09=09=09=20Relation=20rel,=20= Bitmapset=20*columns,=0A-=09=09=09=09=09=09=09=09=20PublishGencolsType=20= include_gencols_type);=0A-extern=20LogicalRepRelation=20= *logicalrep_read_rel(StringInfo=20in);=0A+=09=09=09=09=09=09=09=09=20= PublishGencolsType=20include_gencols_type,=0A+=09=09=09=09=09=09=09=09=20= bool=20fallbackfull,=20uint32=20proto_version);=0A+extern=20= LogicalRepRelation=20*logicalrep_read_rel(StringInfo=20in,=0A+=09=09=09=09= =09=09=09=09=09=09=09=20=20=20uint32=20proto_version);=0A=20extern=20= void=20logicalrep_write_typ(StringInfo=20out,=20TransactionId=20xid,=0A=20= =09=09=09=09=09=09=09=09=20Oid=20typoid);=0A=20extern=20void=20= logicalrep_read_typ(StringInfo=20in,=20LogicalRepTyp=20*ltyp);=0Adiff=20= --git=20a/src/include/replication/logicalrelation.h=20= b/src/include/replication/logicalrelation.h=0Aindex=20= efe0f9d6031..a4f5ecfddc5=20100644=0A---=20= a/src/include/replication/logicalrelation.h=0A+++=20= b/src/include/replication/logicalrelation.h=0A@@=20-48,6=20+48,7=20@@=20= extern=20LogicalRepRelMapEntry=20= *logicalrep_partition_open(LogicalRepRelMapEntry=20*r=0A=20=09=09=09=09=09= =09=09=09=09=09=09=09=09=09Relation=20partrel,=20AttrMap=20*map);=0A=20= extern=20void=20logicalrep_rel_close(LogicalRepRelMapEntry=20*rel,=0A=20=09= =09=09=09=09=09=09=09=20LOCKMODE=20lockmode);=0A+extern=20bool=20= logicalrep_identity_is_full(Relation=20relation);=0A=20extern=20bool=20= IsIndexUsableForReplicaIdentityFull(Relation=20idxrel,=20AttrMap=20= *attrmap);=0A=20extern=20Oid=09GetRelationIdentityOrPK(Relation=20rel);=0A= =20=0A--=20=0A2.50.1=20(Apple=20Git-155)=0A=0A= --Apple-Mail=_047BEB1B-0A05-4E04-A85C-14ED5C01A4B6--