Hi All,

Il 18/01/18 17:48, Simon Riggs ha scritto:
> On 17 January 2018 at 17:07, Petr Jelinek <petr.jeli...@2ndquadrant.com> 
> wrote:
> 
>> Things I am less convinced about:
>>
>> The patch will cascade truncation on downstream if cascade was specified
>> on the upstream, that can potentially be dangerous and we either should
>> not do it and only truncate the tables which were truncated upstream
>> (but without restricting because of FKs), leaving the data inconsistent
>> on downstream (like we do already with DELETE or UPDATE). Or maybe make
>> it into either subscription or publication option so that user can chose
>> the behaviour here as I am sure some people will want it to cascade (but
>> the default should still IMHO be to not cascade as that's safer).
> 
> I agree the default should be to NOT cascade.
> 
> If someone wants cascading as a publication option, that can be added later.
> 

I agree that not replicating the CASCADE option is the best option
according to POLA principle.

>>> +     /* 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;
>>
>> This is somewhat ugly. Perhaps the ExecuteTruncateGuts should track
>> which relations it opened and only close those and the rest should be
>> closed by caller? That should also remove the other ugly part which is
>> that the ExecuteTruncateGuts modifies the input list. What if caller
>> wanted to use those relations it sent as parameter later?
> 
> Agreed
> 

Attached a new version of the patch addressing these issues.

Regards,
Marco

-- 
Marco Nenciarini - 2ndQuadrant Italy
PostgreSQL Training, Services and Support
marco.nenciar...@2ndquadrant.it | www.2ndQuadrant.it
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f2a928b823..180ebd0717 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 1340,1355 **** ExecuteTruncate(TruncateStmt *stmt)
        }
  
        /*
!        * Check foreign key references.  In CASCADE mode, this should be
!        * unnecessary since we just pulled in all the references; but as a
!        * cross-check, do it anyway if in an Assert-enabled build.
         */
  #ifdef USE_ASSERT_CHECKING
-       heap_truncate_check_FKs(rels, false);
- #else
-       if (stmt->behavior == DROP_RESTRICT)
                heap_truncate_check_FKs(rels, false);
  #endif
  
        /*
         * If we are asked to restart sequences, find all the sequences, lock 
them
--- 1340,1363 ----
        }
  
        /*
!        * Suppress foreign key references check if session replication role is
!        * set to REPLICA.
         */
+       if (SessionReplicationRole != SESSION_REPLICATION_ROLE_REPLICA)
+       {
+ 
+               /*
+                * Check foreign key references.  In CASCADE mode, this should 
be
+                * unnecessary since we just pulled in all the references; but 
as a
+                * cross-check, do it anyway if in an Assert-enabled build.
+                */
  #ifdef USE_ASSERT_CHECKING
                heap_truncate_check_FKs(rels, false);
+ #else
+               if (stmt->behavior == DROP_RESTRICT)
+                       heap_truncate_check_FKs(rels, false);
  #endif
+       }
  
        /*
         * If we are asked to restart sequences, find all the sequences, lock 
them
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 0f18afa852..55c4593ed5 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 4c5ed1e6d6..d2a0d7e52e 100644
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
***************
*** 6838,6843 **** TupleData
--- 6838,6889 ----
  </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>
+ </varlistentry>
+ 
  </variablelist>
  
  </sect1>
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 dbc8f2d6c7..f4c6eaf57f 
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 b00c071cb6..15550cdff0 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 180ebd0717..5c498723ad 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"
***************
*** 1248,1258 **** ExecuteTruncate(TruncateStmt *stmt)
  {
        List       *rels = NIL;
        List       *relids = NIL;
!       List       *seq_relids = NIL;
!       EState     *estate;
!       ResultRelInfo *resultRelInfos;
!       ResultRelInfo *resultRelInfo;
!       SubTransactionId mySubid;
        ListCell   *cell;
  
        /*
--- 1249,1255 ----
  {
        List       *rels = NIL;
        List       *relids = NIL;
!       List       *relids_logged = NIL;
        ListCell   *cell;
  
        /*
***************
*** 1276,1281 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1273,1281 ----
                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)
                {
***************
*** 1296,1301 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1296,1304 ----
                                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)
***************
*** 1305,1311 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1308,1336 ----
                                         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, true);
+ }
+ 
+ void
+ ExecuteTruncateGuts(List *rels, List *relids, List *relids_logged,
+                                                       DropBehavior behavior, 
bool restart_seqs, bool closerels)
+ {
+       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
***************
*** 1313,1319 **** 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 (;;)
                {
--- 1338,1344 ----
         * 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 (;;)
                {
***************
*** 1335,1340 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1360,1368 ----
                                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);
                        }
                }
        }
***************
*** 1354,1360 **** 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
        }
--- 1382,1388 ----
  #ifdef USE_ASSERT_CHECKING
                heap_truncate_check_FKs(rels, false);
  #else
!               if (behavior == DROP_RESTRICT)
                        heap_truncate_check_FKs(rels, false);
  #endif
        }
***************
*** 1365,1371 **** 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)
                {
--- 1393,1399 ----
         * 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)
                {
***************
*** 1386,1391 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1414,1423 ----
                                                                   
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);
                        }
***************
*** 1520,1525 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1552,1611 ----
                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.
         */
***************
*** 1538,1548 **** ExecuteTruncate(TruncateStmt *stmt)
        FreeExecutorState(estate);
  
        /* And close the rels (can't do this while EState still holds refs) */
!       foreach(cell, rels)
        {
!               Relation        rel = (Relation) lfirst(cell);
  
!               heap_close(rel, NoLock);
        }
  }
  
--- 1624,1637 ----
        FreeExecutorState(estate);
  
        /* And close the rels (can't do this while EState still holds refs) */
!       if (closerels)
        {
!               foreach(cell, rels)
!               {
!                       Relation        rel = (Relation) lfirst(cell);
  
!                       heap_close(rel, NoLock);
!               }
        }
  }
  
diff --git a/src/backend/replication/loindex 6eb0d5527e..ef5b937807 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);
  
***************
*** 449,454 **** DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf)
--- 450,460 ----
                                DecodeDelete(ctx, buf);
                        break;
  
+               case XLOG_HEAP_TRUNCATE:
+                       if (SnapBuildProcessChange(builder, xid, buf->origptr))
+                               DecodeTruncate(ctx, buf);
+                       break;
+ 
                case XLOG_HEAP_INPLACE:
  
                        /*
***************
*** 825,830 **** DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer 
*buf)
--- 831,876 ----
        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 948343e4ae..2fa6f8393d 
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 c72a611a39..f1d65c1457 
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:
  
                                        /*
***************
*** 2239,2244 **** ReorderBufferSerializeChange(ReorderBuffer *rb, 
ReorderBufferTXN *txn,
--- 2248,2254 ----
                                }
                                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:
***************
*** 2524,2529 **** ReorderBufferRestoreChange(ReorderBuffer *rb, 
ReorderBufferTXN *txn,
--- 2534,2540 ----
                                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 
83c69092ae..7177c543c2 100644
*** a/src/backend/replication/logical/worker.c
--- b/src/backend/replication/logical/worker.c
***************
*** 869,874 **** apply_handle_delete(StringInfo s)
--- 869,925 ----
        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);
+ 
+       /*
+        * Even if we used CASCADE on the upstream master we explicitly
+        * default to replaying changes without further cascading.
+        * This might be later changeable with a user specified option.
+        */
+       cascade = false;
+ 
+       ExecuteTruncateGuts(rels, relids, NULL,
+                                               cascade ? DROP_CASCADE : 
DROP_RESTRICT,
+                                               restart_seqs, false);
+ 
+       logicalrep_rel_close(rel, NoLock);
+ 
+       CommandCounterIncrement();
+ }
+ 
  
  /*
   * Logical replication protocol message dispatcher.
***************
*** 900,905 **** apply_dispatch(StringInfo s)
--- 951,960 ----
                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 40a1ef3c1d..7fbd0a3dca 
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 700e25c36a..c56b200f7d 
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 6545a80222..8daae6efef 100644
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 558,561 **** extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
--- 558,567 ----
  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, bool closerels);
+ 
  #endif                                                        /* EXECUTOR_H  
*/
diff --git a/src/include/replication/lindex 0eb21057c5..b1abb9e36f 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 0970abca52..e8439fac49 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 e0104cd8d0..96a6071b3a 
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');
***************
*** 176,181 **** $result = $node_subscriber->safe_psql('postgres',
--- 176,191 ----
  is($result, qq(1152|1|1100),
        'check replicated inserts after subscription publication change');
  
+ $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');
+ 
+ # truncate should not be replicated
+ $node_publisher->safe_psql('postgres', "TRUNCATE tab_rep");
+ 
+ $node_publisher->wait_for_catchup($appname);
+ 
  $result = $node_subscriber->safe_psql('postgres',
        "SELECT count(*), min(a), max(a) FROM tab_rep");
  is($result, qq(20|-20|-1),
***************
*** 185,191 **** is($result, qq(20|-20|-1),
  $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)"
--- 195,201 ----
  $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)"
***************
*** 204,209 **** $result = $node_subscriber->safe_psql('postgres',
--- 214,282 ----
        "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->wait_for_catchup($appname);
+ 
+ $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->wait_for_catchup($appname);
+ 
+ $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 not cascade on replica
+ $node_publisher->safe_psql('postgres', "TRUNCATE tab_rep CASCADE");
+ 
+ $node_publisher->wait_for_catchup($appname);
+ 
+ $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 on replica');
+ 
+ $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