Hi,

This patch implements support for TRUNCATE statements
in logical replication. The work has mainly done by Simon Riggs then
finished by me. Tests are written by me.

TRUNCATE is treated as a form of DELETE for the purpose of deciding
whether to publish, or not.

The "TRUNCATE behavior when session_replication_role = replica"[1] patch
is required from this patch to work correctly with tables referenced by
foreign keys.

[1] https://commitfest.postgresql.org/16/1447/

Regards,
Marco

-- 
Marco Nenciarini - 2ndQuadrant Italy
PostgreSQL Training, Services and Support
marco.nenciar...@2ndquadrant.it | www.2ndQuadrant.it
diff --git a/contrib/test_decoding/expected/ddl.out 
b/contrib/test_decoding/expected/ddl.out
index 1e22c1eefc..d1feea4909 100644
*** a/contrib/test_decoding/expected/ddl.out
--- b/contrib/test_decoding/expected/ddl.out
***************
*** 543,548 **** UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2;
--- 543,550 ----
  UPDATE table_with_unique_not_null SET id = -id;
  UPDATE table_with_unique_not_null SET id = -id;
  DELETE FROM table_with_unique_not_null WHERE data = 3;
+ TRUNCATE table_with_unique_not_null;
+ TRUNCATE table_with_unique_not_null, table_with_unique_not_null RESTART 
IDENTITY CASCADE;
  -- check toast support
  BEGIN;
  CREATE SEQUENCE toasttable_rand_seq START 79 INCREMENT 1499; -- portable 
"random"
***************
*** 660,665 **** SELECT data FROM 
pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
--- 662,673 ----
   table public.table_with_unique_not_null: DELETE: id[integer]:4
   COMMIT
   BEGIN
+  table public.table_with_unique_not_null: TRUNCATE: (no-flags)
+  COMMIT
+  BEGIN
+  table public.table_with_unique_not_null: TRUNCATE: restart_seqs cascade
+  COMMIT
+  BEGIN
   table public.toasttable: INSERT: id[integer]:1 
toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000'
 rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578
   COMMIT
   BEGIN
***************
*** 668,674 **** SELECT data FROM 
pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
   BEGIN
   table public.toasttable: UPDATE: id[integer]:1 
toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000'
 rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578
   COMMIT
! (103 rows)
  
  INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM 
generate_series(1, 2000) g(i);
  -- update of second column, first column unchanged
--- 676,682 ----
   BEGIN
   table public.toasttable: UPDATE: id[integer]:1 
toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000'
 rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578
   COMMIT
! (109 rows)
  
  INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM 
generate_series(1, 2000) g(i);
  -- update of second column, first column unchanged
diff --git a/contrib/test_decoding/sql/ddl.sqindex 057dae056b..0fb4102db8 100644
*** a/contrib/test_decoding/sql/ddl.sql
--- b/contrib/test_decoding/sql/ddl.sql
***************
*** 333,338 **** UPDATE table_with_unique_not_null SET id = -id;
--- 333,341 ----
  UPDATE table_with_unique_not_null SET id = -id;
  DELETE FROM table_with_unique_not_null WHERE data = 3;
  
+ TRUNCATE table_with_unique_not_null;
+ TRUNCATE table_with_unique_not_null, table_with_unique_not_null RESTART 
IDENTITY CASCADE;
+ 
  -- check toast support
  BEGIN;
  CREATE SEQUENCE toasttable_rand_seq START 79 INCREMENT 1499; -- portable 
"random"
diff --git a/contrib/test_decoding/test_index 135b3b7638..f99f91b9b0 100644
*** a/contrib/test_decoding/test_decoding.c
--- b/contrib/test_decoding/test_decoding.c
***************
*** 466,471 **** pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN 
*txn,
--- 466,485 ----
                                                                        
&change->data.tp.oldtuple->tuple,
                                                                        true);
                        break;
+               case REORDER_BUFFER_CHANGE_TRUNCATE:
+                       appendStringInfoString(ctx->out, " TRUNCATE:");
+ 
+                       if (change->data.truncate_msg.restart_seqs
+                               || change->data.truncate_msg.cascade)
+                       {
+                               if (change->data.truncate_msg.restart_seqs)
+                                       appendStringInfo(ctx->out, " 
restart_seqs");
+                               if (change->data.truncate_msg.cascade)
+                                       appendStringInfo(ctx->out, " cascade");
+                       }
+                       else
+                               appendStringInfoString(ctx->out, " (no-flags)");
+                       break;
                default:
                        Assert(false);
        }
diff --git a/doc/src/sgml/logical-replicatioindex 75551d8ee1..6b64c8ef76 100644
*** a/doc/src/sgml/logical-replication.sgml
--- b/doc/src/sgml/logical-replication.sgml
***************
*** 111,116 ****
--- 111,118 ----
     any combination of <command>INSERT</command>, <command>UPDATE</command>, 
and
     <command>DELETE</command>, similar to how triggers are fired by
     particular event types.  By default, all operation types are replicated.
+    <command>TRUNCATE</command> is treated as a form of 
<command>DELETE</command>
+    for the purpose of deciding whether to publish, or not.
    </para>
  
    <para>
***************
*** 364,378 ****
      </para>
     </listitem>
  
-    <listitem>
-     <para>
-      <command>TRUNCATE</command> commands are not replicated.  This can, of
-      course, be worked around by using <command>DELETE</command> instead.  To
-      avoid accidental <command>TRUNCATE</command> invocations, you can revoke
-      the <literal>TRUNCATE</literal> privilege from tables.
-     </para>
-    </listitem>
- 
     <listitem>
      <para>
       Large objects (see <xref linkend="largeobjects"/>) are not replicated.
--- 366,371 ----
diff --git a/doc/src/sgml/protocol.sgml b/doindex 8174e3defa..88cbecfbdd 100644
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
***************
*** 6828,6833 **** TupleData
--- 6828,6874 ----
  </listitem>
  </varlistentry>
  
+ <varlistentry>
+ <term>
+ Truncate
+ </term>
+ <listitem>
+ <para>
+ 
+ <variablelist>
+ <varlistentry>
+ <term>
+         Byte1('T')
+ </term>
+ <listitem>
+ <para>
+                 Identifies the message as a truncate message.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+         Int32
+ </term>
+ <listitem>
+ <para>
+                 ID of the relation corresponding to the ID in the relation
+                 message.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+         Int8
+ </term>
+ <listitem>
+ <para>
+                 Option flags for truncate. Currently can be 0 for no flags,
+                 1 for CASCADE, 2 for RESTART IDENTITY and 3 if both are set.
+ </para>
+ </listitem>
+ </varlistentry>
+ 
  </variablelist>
  </para>
  </listitem>
diff --git a/doc/src/sgml/ref/creindex bfe12d5f41..c5032938a7 100644
*** a/doc/src/sgml/ref/create_publication.sgml
--- b/doc/src/sgml/ref/create_publication.sgml
***************
*** 111,116 **** CREATE PUBLICATION <replaceable 
class="parameter">name</replaceable>
--- 111,121 ----
            and so the default value for this option is
            <literal>'insert, update, delete'</literal>.
           </para>
+          <para>
+            <command>TRUNCATE</command> is treated as a form of
+            <command>DELETE</command> for the purpose of deciding whether
+            to publish, or not.
+          </para>
          </listitem>
         </varlistentry>
        </variablelist>
***************
*** 168,175 **** CREATE PUBLICATION <replaceable 
class="parameter">name</replaceable>
    </para>
  
    <para>
!    <command>TRUNCATE</command> and <acronym>DDL</acronym> operations
!    are not published.
    </para>
   </refsect1>
  
--- 173,179 ----
    </para>
  
    <para>
!    <acronym>DDL</acronym> operations are not published.
    </para>
   </refsect1>
  
diff --git a/src/backend/access/heap/heapam.c bindex 54f1100ffd..2e1cea373d 
100644
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 9134,9139 **** heap_redo(XLogReaderState *record)
--- 9134,9146 ----
                case XLOG_HEAP_UPDATE:
                        heap_xlog_update(record, false);
                        break;
+               case XLOG_HEAP_TRUNCATE:
+                       /*
+                        * TRUNCATE is a no-op because the actions are already 
logged
+                        * as SMGR WAL records. TRUNCATE WAL record only exists 
to allow
+                        * it to be logically decoded precisely.
+                        */
+                       break;
                case XLOG_HEAP_HOT_UPDATE:
                        heap_xlog_update(record, true);
                        break;
diff --git a/src/backend/access/rmgrdesindex 44d2d6333f..a44996379b 100644
*** a/src/backend/access/rmgrdesc/heapdesc.c
--- b/src/backend/access/rmgrdesc/heapdesc.c
***************
*** 75,80 **** heap_desc(StringInfo buf, XLogReaderState *record)
--- 75,93 ----
                                                 xlrec->new_offnum,
                                                 xlrec->new_xmax);
        }
+       else if (info == XLOG_HEAP_TRUNCATE)
+       {
+               xl_heap_truncate *xlrec = (xl_heap_truncate *) rec;
+ 
+               if (xlrec->flags & XLH_TRUNCATE_CASCADE)
+                       appendStringInfo(buf, "cascade ");
+               if (xlrec->flags & XLH_TRUNCATE_RESTART_SEQS)
+                       appendStringInfo(buf, "restart_seqs ");
+               appendStringInfo(buf, "nrelids %u nseqrelids %u",
+                                                xlrec->nrelids,
+                                                xlrec->nseqrelids);
+               /* Skip the list of relids and seqrelids */
+       }
        else if (info == XLOG_HEAP_CONFIRM)
        {
                xl_heap_confirm *xlrec = (xl_heap_confirm *) rec;
***************
*** 186,191 **** heap_identify(uint8 info)
--- 199,207 ----
                case XLOG_HEAP_HOT_UPDATE | XLOG_HEAP_INIT_PAGE:
                        id = "HOT_UPDATE+INIT";
                        break;
+               case XLOG_HEAP_TRUNCATE:
+                       id = "TRUNCATE";
+                       break;
                case XLOG_HEAP_CONFIRM:
                        id = "HEAP_CONFIRM";
                        break;
diff --git a/src/backend/commands/tablecmds.cindex 296807849f..e5f7cb320f 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 16,21 ****
--- 16,22 ----
  
  #include "access/genam.h"
  #include "access/heapam.h"
+ #include "access/heapam_xlog.h"
  #include "access/multixact.h"
  #include "access/reloptions.h"
  #include "access/relscan.h"
***************
*** 1249,1259 **** ExecuteTruncate(TruncateStmt *stmt)
  {
        List       *rels = NIL;
        List       *relids = NIL;
!       List       *seq_relids = NIL;
!       EState     *estate;
!       ResultRelInfo *resultRelInfos;
!       ResultRelInfo *resultRelInfo;
!       SubTransactionId mySubid;
        ListCell   *cell;
  
        /*
--- 1250,1256 ----
  {
        List       *rels = NIL;
        List       *relids = NIL;
!       List       *relids_logged = NIL;
        ListCell   *cell;
  
        /*
***************
*** 1277,1282 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1274,1282 ----
                truncate_check_rel(rel);
                rels = lappend(rels, rel);
                relids = lappend_oid(relids, myrelid);
+               /* Log this relation only if needed for logical decoding */
+               if (RelationIsLogicallyLogged(rel))
+                       relids_logged = lappend_oid(relids_logged, myrelid);
  
                if (recurse)
                {
***************
*** 1297,1302 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1297,1305 ----
                                truncate_check_rel(rel);
                                rels = lappend(rels, rel);
                                relids = lappend_oid(relids, childrelid);
+                               /* Log this relation only if needed for logical 
decoding */
+                               if (RelationIsLogicallyLogged(rel))
+                                       relids_logged = 
lappend_oid(relids_logged, childrelid);
                        }
                }
                else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
***************
*** 1306,1312 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1309,1337 ----
                                         errhint("Do not specify the ONLY 
keyword, or use truncate only on the partitions directly.")));
        }
  
+       ExecuteTruncateGuts(rels, relids, relids_logged,
+                                               stmt->behavior, 
stmt->restart_seqs);
+ }
+ 
+ void
+ ExecuteTruncateGuts(List *rels, List *relids, List *relids_logged,
+                                                       DropBehavior behavior, 
bool restart_seqs)
+ {
+       List       *seq_relids = NIL;
+       EState     *estate;
+       ResultRelInfo *resultRelInfos;
+       ResultRelInfo *resultRelInfo;
+       SubTransactionId mySubid;
+       ListCell   *cell;
+       List       *seq_relids_logged = NIL;
+       uint32      nrelids = 0;
+       uint32      nseqrelids = 0;
+       uint32      maxrelids = 2;
+       Oid                *logrelids = NULL;
+ 
        /*
+        * Open, exclusive-lock, and check all the explicitly-specified 
relations
+        *
         * In CASCADE mode, suck in all referencing relations as well.  This
         * requires multiple iterations to find indirectly-dependent relations. 
At
         * each phase, we need to exclusive-lock new rels before looking for 
their
***************
*** 1314,1320 **** ExecuteTruncate(TruncateStmt *stmt)
         * soon as we open it, to avoid a faux pas such as holding lock for a 
long
         * time on a rel we have no permissions for.
         */
!       if (stmt->behavior == DROP_CASCADE)
        {
                for (;;)
                {
--- 1339,1345 ----
         * soon as we open it, to avoid a faux pas such as holding lock for a 
long
         * time on a rel we have no permissions for.
         */
!       if (behavior == DROP_CASCADE)
        {
                for (;;)
                {
***************
*** 1336,1341 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1361,1369 ----
                                truncate_check_rel(rel);
                                rels = lappend(rels, rel);
                                relids = lappend_oid(relids, relid);
+                               /* Log this relation only if needed for logical 
decoding */
+                               if (RelationIsLogicallyLogged(rel))
+                                       relids_logged = 
lappend_oid(relids_logged, relid);
                        }
                }
        }
***************
*** 1355,1361 **** ExecuteTruncate(TruncateStmt *stmt)
  #ifdef USE_ASSERT_CHECKING
                heap_truncate_check_FKs(rels, false);
  #else
!               if (stmt->behavior == DROP_RESTRICT)
                        heap_truncate_check_FKs(rels, false);
  #endif
        }
--- 1383,1389 ----
  #ifdef USE_ASSERT_CHECKING
                heap_truncate_check_FKs(rels, false);
  #else
!               if (behavior == DROP_RESTRICT)
                        heap_truncate_check_FKs(rels, false);
  #endif
        }
***************
*** 1366,1372 **** ExecuteTruncate(TruncateStmt *stmt)
         * We want to do this early since it's pointless to do all the 
truncation
         * work only to fail on sequence permissions.
         */
!       if (stmt->restart_seqs)
        {
                foreach(cell, rels)
                {
--- 1394,1400 ----
         * We want to do this early since it's pointless to do all the 
truncation
         * work only to fail on sequence permissions.
         */
!       if (restart_seqs)
        {
                foreach(cell, rels)
                {
***************
*** 1387,1392 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1415,1424 ----
                                                                   
RelationGetRelationName(seq_rel));
  
                                seq_relids = lappend_oid(seq_relids, seq_relid);
+                               /* Log this relation only if needed for logical 
decoding */
+                               if (RelationIsLogicallyLogged(seq_rel))
+                                       seq_relids_logged = 
lappend_oid(seq_relids_logged,
+                                                                               
                        seq_relid);
  
                                relation_close(seq_rel, NoLock);
                        }
***************
*** 1521,1526 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1553,1612 ----
                ResetSequence(seq_relid);
        }
  
+       /*
+        * Write a WAL record to allow this set of actions to be logically 
decoded.
+        * We could optimize this away when !RelationIsLogicallyLogged(rel)
+        * but that doesn't save much space or time.
+        *
+        * Assemble an array of relids, then an array of seqrelids so we can 
write
+        * a single WAL record for the whole action.
+        */
+       logrelids = palloc(maxrelids * sizeof(Oid));
+       foreach (cell, relids_logged)
+       {
+               nrelids++;
+               if (nrelids > maxrelids)
+               {
+                       maxrelids *= 2;
+                       logrelids = repalloc(logrelids, maxrelids * 
sizeof(Oid));
+               }
+               logrelids[nrelids - 1] = lfirst_oid(cell);
+       }
+ 
+       foreach (cell, seq_relids_logged)
+       {
+               nseqrelids++;
+               if ((nrelids + nseqrelids) > maxrelids)
+               {
+                       maxrelids *= 2;
+                       logrelids = repalloc(logrelids, maxrelids * 
sizeof(Oid));
+               }
+               logrelids[nrelids + nseqrelids - 1] = lfirst_oid(cell);
+       }
+ 
+       if ((nrelids + nseqrelids) > 0)
+       {
+               xl_heap_truncate xlrec;
+ 
+               xlrec.dbId = MyDatabaseId;
+               xlrec.nrelids = nrelids;
+               xlrec.nseqrelids = nseqrelids;
+               xlrec.flags = 0;
+               if (behavior == DROP_CASCADE)
+                       xlrec.flags |= XLH_TRUNCATE_CASCADE;
+               if (restart_seqs)
+                       xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;
+ 
+               XLogBeginInsert();
+               XLogRegisterData((char *) &xlrec, SizeOfHeapTruncate);
+               XLogRegisterData((char *) logrelids,
+                                                       (nrelids + nseqrelids) 
* sizeof(Oid));
+ 
+               XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+ 
+               (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
+       }
+ 
        /*
         * Process all AFTER STATEMENT TRUNCATE triggers.
         */
diff --git a/src/backend/replication/loindex 486fd0c988..e06dcfe62d 100644
*** a/src/backend/replication/logical/decode.c
--- b/src/backend/replication/logical/decode.c
***************
*** 65,70 **** static void DecodeLogicalMsgOp(LogicalDecodingContext *ctx, 
XLogRecordBuffer *bu
--- 65,71 ----
  static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
  static void DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
  static void DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
+ static void DecodeTruncate(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf);
  static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf);
  static void DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf);
  
***************
*** 435,440 **** DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf)
--- 436,446 ----
                                DecodeDelete(ctx, buf);
                        break;
  
+               case XLOG_HEAP_TRUNCATE:
+                       if (SnapBuildProcessChange(builder, xid, buf->origptr))
+                               DecodeTruncate(ctx, buf);
+                       break;
+ 
                case XLOG_HEAP_INPLACE:
  
                        /*
***************
*** 805,810 **** DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf)
--- 811,856 ----
        ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, 
change);
  }
  
+ /*
+  * Parse XLOG_HEAP_TRUNCATE from wal
+  */
+ static void
+ DecodeTruncate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
+ {
+       XLogReaderState *r = buf->record;
+       xl_heap_truncate *xlrec;
+       ReorderBufferChange *change;
+       int     i;
+ 
+       xlrec = (xl_heap_truncate *) XLogRecGetData(r);
+ 
+       /* only interested in our database */
+       if (xlrec->dbId != ctx->slot->data.database)
+               return;
+ 
+       /* output plugin doesn't look for this origin, no need to queue */
+       if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
+               return;
+ 
+       change = ReorderBufferGetChange(ctx->reorder);
+       change->action = REORDER_BUFFER_CHANGE_TRUNCATE;
+       change->origin_id = XLogRecGetOrigin(r);
+       if (xlrec->flags & XLH_TRUNCATE_CASCADE)
+               change->data.truncate_msg.cascade = true;
+       if (xlrec->flags & XLH_TRUNCATE_RESTART_SEQS)
+               change->data.truncate_msg.restart_seqs = true;
+ 
+       /*
+        * Queue up one change per relation, ignoring sequences for now
+        */
+       for (i = 0; i < xlrec->nrelids; i++)
+       {
+               change->data.truncate_msg.relid = xlrec->relids[i];
+               ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r),
+                                                                buf->origptr, 
change);
+       }
+ }
+ 
  /*
   * Decode XLOG_HEAP2_MULTI_INSERT_insert record into multiple tuplebufs.
   *
diff --git a/src/backend/replication/logical/prindex 9b126b2957..8ffa486e8c 
100644
*** a/src/backend/replication/logical/proto.c
--- b/src/backend/replication/logical/proto.c
***************
*** 26,31 ****
--- 26,34 ----
   */
  #define LOGICALREP_IS_REPLICA_IDENTITY 1
  
+ #define TRUNCATE_CASCADE              (1<<0)
+ #define TRUNCATE_RESTART_SEQS (1<<1)
+ 
  static void logicalrep_write_attrs(StringInfo out, Relation rel);
  static void logicalrep_write_tuple(StringInfo out, Relation rel,
                                           HeapTuple tuple);
***************
*** 292,297 **** logicalrep_read_delete(StringInfo in, LogicalRepTupleData 
*oldtup)
--- 295,342 ----
        return relid;
  }
  
+ /*
+  * Write TRUNCATE to the output stream.
+  */
+ void
+ logicalrep_write_truncate(StringInfo out, Relation rel,
+                                                 bool cascade, bool 
restart_seqs)
+ {
+       uint8 flags = 0;
+ 
+       pq_sendbyte(out, 'T');          /* action TRUNCATE */
+ 
+       /* use Oid as relation identifier */
+       pq_sendint32(out, RelationGetRelid(rel));
+ 
+       /* encode and send truncate flags */
+       if (cascade)
+               flags |= TRUNCATE_CASCADE;
+       if (restart_seqs)
+               flags |= TRUNCATE_RESTART_SEQS;
+       pq_sendint8(out, flags);
+ }
+ 
+ /*
+  * Read TRUNCATE from stream.
+  */
+ LogicalRepRelId
+ logicalrep_read_truncate(StringInfo in, bool *cascade, bool *restart_seqs)
+ {
+       LogicalRepRelId relid;
+       uint8 flags;
+ 
+       /* read the relation id */
+       relid = pq_getmsgint(in, 4);
+ 
+       /* read and decode truncate flags */
+       flags = pq_getmsgint(in, 1);
+       *cascade = (flags & TRUNCATE_CASCADE) > 0;
+       *restart_seqs = (flags & TRUNCATE_RESTART_SEQS) > 0;
+ 
+       return relid;
+ }
+ 
  /*
   * Write relation description to the output stream.
   */
diff --git a/src/backend/replication/logical/rindex 5ac391dbda..4eefa53681 
100644
*** a/src/backend/replication/logical/reorderbuffer.c
--- b/src/backend/replication/logical/reorderbuffer.c
***************
*** 403,408 **** ReorderBufferReturnChange(ReorderBuffer *rb, 
ReorderBufferChange *change)
--- 403,410 ----
                case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
                case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
                        break;
+               case REORDER_BUFFER_CHANGE_TRUNCATE:
+                       break;
        }
  
        pfree(change);
***************
*** 1342,1347 **** ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
--- 1344,1356 ----
  
                        switch (change->action)
                        {
+                               case REORDER_BUFFER_CHANGE_TRUNCATE:
+                                       reloid = 
change->data.truncate_msg.relid;
+                                       relation = 
RelationIdGetRelation(reloid);
+                                       rb->apply_change(rb, txn, relation, 
change);
+                                       RelationClose(relation);
+                                       break;
+ 
                                case 
REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
  
                                        /*
***************
*** 2224,2229 **** ReorderBufferSerializeChange(ReorderBuffer *rb, 
ReorderBufferTXN *txn,
--- 2233,2239 ----
                                }
                                break;
                        }
+               case REORDER_BUFFER_CHANGE_TRUNCATE:
                case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
                case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
                case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
***************
*** 2509,2514 **** ReorderBufferRestoreChange(ReorderBuffer *rb, 
ReorderBufferTXN *txn,
--- 2519,2525 ----
                                break;
                        }
                        /* the base struct contains all the data, easy peasy */
+               case REORDER_BUFFER_CHANGE_TRUNCATE:
                case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
                case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
                case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
diff --git a/src/backend/replication/logical/worker.c index 
fa5d9bb120..17e5b4b150 100644
*** a/src/backend/replication/logical/worker.c
--- b/src/backend/replication/logical/worker.c
***************
*** 869,874 **** apply_handle_delete(StringInfo s)
--- 869,922 ----
        CommandCounterIncrement();
  }
  
+ /*
+  * Handle TRUNCATE message.
+  *
+  * TODO: FDW support
+  */
+ static void
+ apply_handle_truncate(StringInfo s)
+ {
+       LogicalRepRelMapEntry *rel;
+       LogicalRepRelId relid;
+       bool     cascade = false;
+       bool     restart_seqs = false;
+       List    *rels = NIL;
+       List    *relids = NIL;
+ 
+       ensure_transaction();
+ 
+       relid = logicalrep_read_truncate(s, &cascade, &restart_seqs);
+       rel = logicalrep_rel_open(relid, RowExclusiveLock);
+       if (!should_apply_changes_for_rel(rel))
+       {
+               /*
+                * The relation can't become interesting in the middle of the
+                * transaction so it's safe to unlock it.
+                */
+               logicalrep_rel_close(rel, RowExclusiveLock);
+               return;
+       }
+ 
+       /* Check if we can do the truncate. */
+       check_relation_updatable(rel);
+ 
+       rels = lappend(rels, rel->localrel);
+       relids = lappend_oid(relids, rel->localreloid);
+ 
+       ExecuteTruncateGuts(rels, relids, NULL,
+                                               cascade ? DROP_CASCADE : 
DROP_RESTRICT,
+                                               restart_seqs);
+ 
+       /* logicalrep_rel_close call not needed, because ExecuteTruncateGuts
+        * already closes the relations. Setting localrel to NULL in the map 
entry
+        * is still needed.
+        */
+       rel->localrel = NULL;
+ 
+       CommandCounterIncrement();
+ }
+ 
  
  /*
   * Logical replication protocol message dispatcher.
***************
*** 900,905 **** apply_dispatch(StringInfo s)
--- 948,957 ----
                case 'D':
                        apply_handle_delete(s);
                        break;
+                       /* TRUNCATE */
+               case 'T':
+                       apply_handle_truncate(s);
+                       break;
                        /* RELATION */
                case 'R':
                        apply_handle_relation(s);
diff --git a/src/backend/replication/pgoutput/pindex 550b156e2d..5bd5af98c5 
100644
*** a/src/backend/replication/pgoutput/pgoutput.c
--- b/src/backend/replication/pgoutput/pgoutput.c
***************
*** 275,280 **** pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN 
*txn,
--- 275,281 ----
                        if (!relentry->pubactions.pubupdate)
                                return;
                        break;
+               case REORDER_BUFFER_CHANGE_TRUNCATE:
                case REORDER_BUFFER_CHANGE_DELETE:
                        if (!relentry->pubactions.pubdelete)
                                return;
***************
*** 352,357 **** pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN 
*txn,
--- 353,367 ----
                        else
                                elog(DEBUG1, "didn't send DELETE change because 
of missing oldtuple");
                        break;
+               case REORDER_BUFFER_CHANGE_TRUNCATE:
+                       {
+                               OutputPluginPrepareWrite(ctx, true);
+                               logicalrep_write_truncate(ctx->out, relation,
+                                                                               
  change->data.truncate_msg.cascade,
+                                                                               
  change->data.truncate_msg.restart_seqs);
+                               OutputPluginWrite(ctx, true);
+                       }
+                       break;
                default:
                        Assert(false);
        }
diff --git a/src/include/access/heapam_xlog.h b/srindex 38f7f63984..88b5791e7c 
100644
*** a/src/include/access/heapam_xlog.h
--- b/src/include/access/heapam_xlog.h
***************
*** 32,38 ****
  #define XLOG_HEAP_INSERT              0x00
  #define XLOG_HEAP_DELETE              0x10
  #define XLOG_HEAP_UPDATE              0x20
! /* 0x030 is free, was XLOG_HEAP_MOVE */
  #define XLOG_HEAP_HOT_UPDATE  0x40
  #define XLOG_HEAP_CONFIRM             0x50
  #define XLOG_HEAP_LOCK                        0x60
--- 32,38 ----
  #define XLOG_HEAP_INSERT              0x00
  #define XLOG_HEAP_DELETE              0x10
  #define XLOG_HEAP_UPDATE              0x20
! #define XLOG_HEAP_TRUNCATE            0x30
  #define XLOG_HEAP_HOT_UPDATE  0x40
  #define XLOG_HEAP_CONFIRM             0x50
  #define XLOG_HEAP_LOCK                        0x60
***************
*** 109,114 **** typedef struct xl_heap_delete
--- 109,136 ----
  
  #define SizeOfHeapDelete      (offsetof(xl_heap_delete, flags) + 
sizeof(uint8))
  
+ /*
+  * xl_heap_delete flag values, 8 bits are available.
+  */
+ #define XLH_TRUNCATE_CASCADE                                  (1<<0)
+ #define XLH_TRUNCATE_RESTART_SEQS                             (1<<1)
+ 
+ /*
+  * For truncate we list all truncated relids in an array, followed by all
+  * sequence relids that need to be restarted, if any.
+  * All rels are always within the same database, so we just list dbid once.
+  */
+ typedef struct xl_heap_truncate
+ {
+       Oid                     dbId;
+       uint32          nrelids;
+       uint32          nseqrelids;
+       uint8           flags;
+       Oid relids[FLEXIBLE_ARRAY_MEMBER];
+ } xl_heap_truncate;
+ 
+ #define SizeOfHeapTruncate    (offsetof(xl_heap_truncate, relids))
+ 
  /*
   * We don't store the whole fixed part (HeapTupleHeaderData) of an inserted
   * or updated tuple in WAL; we can save a few bytes by reconstructing the
diff --git a/src/include/executor/execuindex 2cc74da0ba..027def882e 100644
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 553,556 **** extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
--- 553,562 ----
  extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
                                                 const char *relname);
  
+ /*
+  * prototype for tablecmds.c accessible for logical apply
+  */
+ void ExecuteTruncateGuts(List *rels, List *relids, List *relids_logged,
+                                                               DropBehavior 
behavior, bool restart_seqs);
+ 
  #endif                                                        /* EXECUTOR_H  
*/
diff --git a/src/include/replication/lindex a9736e1bf6..bd5e579980 100644
*** a/src/include/replication/logicalproto.h
--- b/src/include/replication/logicalproto.h
***************
*** 98,103 **** extern void logicalrep_write_delete(StringInfo out, Relation 
rel,
--- 98,107 ----
                                                HeapTuple oldtuple);
  extern LogicalRepRelId logicalrep_read_delete(StringInfo in,
                                           LogicalRepTupleData *oldtup);
+ extern void logicalrep_write_truncate(StringInfo out, Relation rel,
+                                               bool cascade, bool 
restart_seqs);
+ extern LogicalRepRelId logicalrep_read_truncate(StringInfo in,
+                                               bool *cascade, bool 
*restart_seqs);
  extern void logicalrep_write_rel(StringInfo out, Relation rel);
  extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
  extern void logicalrep_write_typ(StringInfo out, Oid typoid);
diff --git a/src/include/replication/reorderbindex b18ce5a9df..5a29e1ab5b 100644
*** a/src/include/replication/reorderbuffer.h
--- b/src/include/replication/reorderbuffer.h
***************
*** 59,65 **** enum ReorderBufferChangeType
        REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
        REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID,
        REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT,
!       REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM
  };
  
  /*
--- 59,66 ----
        REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
        REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID,
        REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT,
!       REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM,
!       REORDER_BUFFER_CHANGE_TRUNCATE
  };
  
  /*
***************
*** 128,134 **** typedef struct ReorderBufferChange
                        CommandId       cmax;
                        CommandId       combocid;
                }                       tuplecid;
!       }                       data;
  
        /*
         * While in use this is how a change is linked into a transactions,
--- 129,146 ----
                        CommandId       cmax;
                        CommandId       combocid;
                }                       tuplecid;
! 
!               /*
!                * Truncate data for REORDER_BUFFER_CHANGE_TRUNCATE representing
!                * one relation to be truncated.
!                */
!               struct
!               {
!                       Oid                     relid;
!                       bool            cascade;
!                       bool            restart_seqs;
!               }       truncate_msg;
!       }       data;
  
        /*
         * While in use this is how a change is linked into a transactions,
diff --git a/src/test/subscription/t/001_rep_cindex 0136c79d4b..974e88821c 
100644
*** a/src/test/subscription/t/001_rep_changes.pl
--- b/src/test/subscription/t/001_rep_changes.pl
***************
*** 3,9 **** use strict;
  use warnings;
  use PostgresNode;
  use TestLib;
! use Test::More tests => 16;
  
  # Initialize publisher node
  my $node_publisher = get_new_node('publisher');
--- 3,9 ----
  use warnings;
  use PostgresNode;
  use TestLib;
! use Test::More tests => 24;
  
  # Initialize publisher node
  my $node_publisher = get_new_node('publisher');
***************
*** 189,199 **** $result = $node_subscriber->safe_psql('postgres',
  is($result, qq(20|-20|-1),
        'check changes skipped after subscription publication change');
  
  # check alter publication (relcache invalidation etc)
  $node_publisher->safe_psql('postgres',
        "ALTER PUBLICATION tap_pub_ins_only SET (publish = 'insert, delete')");
  $node_publisher->safe_psql('postgres',
!       "ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_full");
  $node_publisher->safe_psql('postgres', "DELETE FROM tab_ins WHERE a > 0");
  $node_subscriber->safe_psql('postgres',
        "ALTER SUBSCRIPTION tap_sub REFRESH PUBLICATION WITH (copy_data = 
false)"
--- 189,210 ----
  is($result, qq(20|-20|-1),
        'check changes skipped after subscription publication change');
  
+ # truncate should not be replicated
+ $node_publisher->safe_psql('postgres', "TRUNCATE tab_rep");
+ 
+ $node_publisher->poll_query_until('postgres', $caughtup_query)
+   or die "Timed out while waiting for subscriber to catch up";
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT count(*), min(a), max(a) FROM tab_rep");
+ is($result, qq(20|-20|-1),
+       'check changes skipped after subscription publication change');
+ 
  # check alter publication (relcache invalidation etc)
  $node_publisher->safe_psql('postgres',
        "ALTER PUBLICATION tap_pub_ins_only SET (publish = 'insert, delete')");
  $node_publisher->safe_psql('postgres',
!       "ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_full, tab_rep");
  $node_publisher->safe_psql('postgres', "DELETE FROM tab_ins WHERE a > 0");
  $node_subscriber->safe_psql('postgres',
        "ALTER SUBSCRIPTION tap_sub REFRESH PUBLICATION WITH (copy_data = 
false)"
***************
*** 213,218 **** $result = $node_subscriber->safe_psql('postgres',
--- 224,295 ----
        "SELECT count(*), min(a), max(a) FROM tab_full");
  is($result, qq(21|0|100), 'check replicated insert after alter publication');
  
+ # add a sequence and a foreign key to check cascade and restart
+ # identity truncate options
+ $node_subscriber->safe_psql('postgres',
+       "CREATE TABLE tab_notrep_fk (a int REFERENCES tab_rep(a))"
+ );
+ $node_subscriber->safe_psql('postgres', "INSERT INTO tab_notrep_fk VALUES 
(-1)");
+ $node_subscriber->safe_psql('postgres',
+       "CREATE SEQUENCE seq_notrep OWNED BY tab_rep.a"
+ );
+ $node_subscriber->safe_psql('postgres',
+       "ALTER SEQUENCE seq_notrep START 101"
+ );
+ 
+ # truncate should now be replicated
+ $node_publisher->safe_psql('postgres', "TRUNCATE tab_rep");
+ 
+ $node_publisher->poll_query_until('postgres', $caughtup_query)
+   or die "Timed out while waiting for subscriber to catch up";
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT count(*), min(a), max(a) FROM tab_rep");
+ is($result, qq(0||),
+       'check replicated truncate after alter publication');
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT count(*), min(a), max(a) FROM tab_notrep_fk");
+ is($result, qq(1|-1|-1),
+       'check replicated truncate does not cascade');
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT nextval('seq_notrep')");
+ is($result, qq(1),
+       'check replicated truncate does not restart identities');
+ 
+ # should restart identity
+ $node_publisher->safe_psql('postgres', "TRUNCATE tab_rep RESTART IDENTITY");
+ 
+ $node_publisher->poll_query_until('postgres', $caughtup_query)
+   or die "Timed out while waiting for subscriber to catch up";
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT count(*), min(a), max(a) FROM tab_notrep_fk");
+ is($result, qq(1|-1|-1),
+       'check replicated truncate does not cascade');
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT nextval('seq_notrep')");
+ is($result, qq(101),
+       'check replicated truncate restart identities');
+ 
+ # should cascade
+ $node_publisher->safe_psql('postgres', "TRUNCATE tab_rep CASCADE");
+ 
+ $node_publisher->poll_query_until('postgres', $caughtup_query)
+   or die "Timed out while waiting for subscriber to catch up";
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT count(*), min(a), max(a) FROM tab_notrep_fk");
+ is($result, qq(0||),
+       'check replicated truncate cascade');
+ 
+ $result = $node_subscriber->safe_psql('postgres',
+       "SELECT nextval('seq_notrep')");
+ is($result, qq(102),
+       'check replicated truncate does not restart identities');
+ 
  # check restart on rename
  $oldpid = $node_publisher->safe_psql('postgres',
        "SELECT pid FROM pg_stat_replication WHERE application_name = 
'$appname';"

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to