diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 912144c43e..34b91bb226 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -7998,6 +7998,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) i_tgconstrrelid, i_tgconstrrelname, i_tgenabled, + i_tgisinternal, i_tgdeferrable, i_tginitdeferred, i_tgdef; @@ -8016,18 +8017,63 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) tbinfo->dobj.name); resetPQExpBuffer(query); - if (fout->remoteVersion >= 90000) + if (fout->remoteVersion >= 130000) { /* * NB: think not to use pretty=true in pg_get_triggerdef. It * could result in non-forward-compatible dumps of WHEN clauses * due to under-parenthesization. + * + * NB: We need to see tgisinternal triggers in partitions, in case + * the tgenabled flag has been changed from the parent. */ appendPQExpBuffer(query, - "SELECT tgname, " - "tgfoid::pg_catalog.regproc AS tgfname, " - "pg_catalog.pg_get_triggerdef(oid, false) AS tgdef, " - "tgenabled, tableoid, oid " + "SELECT t.tgname, " + "t.tgfoid::pg_catalog.regproc AS tgfname, " + "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " + "t.tgenabled, t.tableoid, t.oid, t.tgisinternal " + "FROM pg_catalog.pg_trigger t " + "LEFT JOIN pg_catalog.pg_trigger u ON u.oid = t.tgparentid " + "WHERE t.tgrelid = '%u'::pg_catalog.oid " + "AND (NOT t.tgisinternal OR t.tgenabled != u.tgenabled)", + tbinfo->dobj.catId.oid); + } + else if (fout->remoteVersion >= 110000) + { + /* + * NB: We need to see tgisinternal triggers in partitions, in case + * the tgenabled flag has been changed from the parent. No + * tgparentid in version 11-12, so we have to match them via + * pg_depend. + * + * See above about pretty=true in pg_get_triggerdef. + */ + appendPQExpBuffer(query, + "SELECT t.tgname, " + "t.tgfoid::pg_catalog.regproc AS tgfname, " + "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " + "t.tgenabled, t.tableoid, t.oid, t.tgisinternal " + "FROM pg_catalog.pg_trigger t " + "LEFT JOIN pg_catalog.pg_depend AS d ON " + " d.classid = 'pg_catalog.pg_trigger'::pg_catalog.regclass AND " + " d.refclassid = 'pg_catalog.pg_trigger'::pg_catalog.regclass AND " + " d.objid = t.oid " + "LEFT JOIN pg_catalog.pg_trigger AS pt ON pt.oid = refobjid " + "WHERE t.tgrelid = '%u'::pg_catalog.oid " + "AND (NOT t.tgisinternal%s)", + tbinfo->dobj.catId.oid, + tbinfo->ispartition ? + " OR t.tgenabled != pt.tgenabled" : ""); + } + else if (fout->remoteVersion >= 90000) + { + /* See above about pretty=true in pg_get_triggerdef */ + appendPQExpBuffer(query, + "SELECT t.tgname, " + "t.tgfoid::pg_catalog.regproc AS tgfname, " + "pg_catalog.pg_get_triggerdef(t.oid, false) AS tgdef, " + "t.tgenabled, false as tgisinternal, " + "t.tableoid, t.oid " "FROM pg_catalog.pg_trigger t " "WHERE tgrelid = '%u'::pg_catalog.oid " "AND NOT tgisinternal", @@ -8042,6 +8088,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) "SELECT tgname, " "tgfoid::pg_catalog.regproc AS tgfname, " "tgtype, tgnargs, tgargs, tgenabled, " + "false as tgisinternal, " "tgisconstraint, tgconstrname, tgdeferrable, " "tgconstrrelid, tginitdeferred, tableoid, oid, " "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " @@ -8090,6 +8137,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) i_tgconstrrelid = PQfnumber(res, "tgconstrrelid"); i_tgconstrrelname = PQfnumber(res, "tgconstrrelname"); i_tgenabled = PQfnumber(res, "tgenabled"); + i_tgisinternal = PQfnumber(res, "tgisinternal"); i_tgdeferrable = PQfnumber(res, "tgdeferrable"); i_tginitdeferred = PQfnumber(res, "tginitdeferred"); i_tgdef = PQfnumber(res, "tgdef"); @@ -8109,6 +8157,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) tginfo[j].dobj.namespace = tbinfo->dobj.namespace; tginfo[j].tgtable = tbinfo; tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled)); + tginfo[j].tgisinternal = *(PQgetvalue(res, j, i_tgisinternal)) == 't'; if (i_tgdef >= 0) { tginfo[j].tgdef = pg_strdup(PQgetvalue(res, j, i_tgdef)); @@ -17799,7 +17848,40 @@ dumpTrigger(Archive *fout, const TriggerInfo *tginfo) "pg_catalog.pg_trigger", "TRIGGER", trigidentity->data); - if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O') + if (tginfo->tgisinternal) + { + /* + * Triggers marked internal only appear here because their 'tgenabled' + * flag differs from its parent's. The trigger is created already, so + * remove the CREATE and replace it with an ALTER. (Clear out the + * DROP query too, so that pg_dump --create does not cause errors.) + */ + resetPQExpBuffer(query); + resetPQExpBuffer(delqry); + appendPQExpBuffer(query, "\nALTER %sTABLE %s ", + tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "", + fmtQualifiedDumpable(tbinfo)); + switch (tginfo->tgenabled) + { + case 'f': + case 'D': + appendPQExpBufferStr(query, "DISABLE"); + break; + case 't': + case 'O': + appendPQExpBufferStr(query, "ENABLE"); + break; + case 'R': + appendPQExpBufferStr(query, "ENABLE REPLICA"); + break; + case 'A': + appendPQExpBufferStr(query, "ENABLE ALWAYS"); + break; + } + appendPQExpBuffer(query, " TRIGGER %s;\n", + fmtId(tginfo->dobj.name)); + } + else if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O') { appendPQExpBuffer(query, "\nALTER %sTABLE %s ", tbinfo->relkind == RELKIND_FOREIGN_TABLE ? "FOREIGN " : "", diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index efb8c30e71..f5e170e0db 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -425,6 +425,7 @@ typedef struct _triggerInfo Oid tgconstrrelid; char *tgconstrrelname; char tgenabled; + bool tgisinternal; bool tgdeferrable; bool tginitdeferred; char *tgdef; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 448b1be26c..c5d8915be8 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2519,12 +2519,68 @@ my %tests = ( }, }, - # this shouldn't ever get emitted - 'Creation of row-level trigger in partition' => { + 'Disabled trigger on partition is altered' => { + create_order => 93, + create_sql => + 'CREATE TABLE dump_test_second_schema.measurement_y2006m3 + PARTITION OF dump_test.measurement + FOR VALUES FROM (\'2006-03-01\') TO (\'2006-04-01\'); + ALTER TABLE dump_test_second_schema.measurement_y2006m3 DISABLE TRIGGER test_trigger; + CREATE TABLE dump_test_second_schema.measurement_y2006m4 + PARTITION OF dump_test.measurement + FOR VALUES FROM (\'2006-04-01\') TO (\'2006-05-01\'); + ALTER TABLE dump_test_second_schema.measurement_y2006m4 ENABLE REPLICA TRIGGER test_trigger; + CREATE TABLE dump_test_second_schema.measurement_y2006m5 + PARTITION OF dump_test.measurement + FOR VALUES FROM (\'2006-05-01\') TO (\'2006-06-01\'); + ALTER TABLE dump_test_second_schema.measurement_y2006m5 ENABLE ALWAYS TRIGGER test_trigger; + ', regexp => qr/^ - \QCREATE TRIGGER test_trigger AFTER INSERT ON dump_test_second_schema.measurement\E + \QALTER TABLE dump_test_second_schema.measurement_y2006m3 DISABLE TRIGGER test_trigger;\E /xm, - like => {}, + like => { + %full_runs, + section_post_data => 1, + role => 1, + binary_upgrade => 1, + }, + }, + + 'Replica trigger on partition is altered' => { + regexp => qr/^ + \QALTER TABLE dump_test_second_schema.measurement_y2006m4 ENABLE REPLICA TRIGGER test_trigger;\E + /xm, + like => { + %full_runs, + section_post_data => 1, + role => 1, + binary_upgrade => 1, + }, + }, + + 'Always trigger on partition is altered' => { + regexp => qr/^ + \QALTER TABLE dump_test_second_schema.measurement_y2006m5 ENABLE ALWAYS TRIGGER test_trigger;\E + /xm, + like => { + %full_runs, + section_post_data => 1, + role => 1, + binary_upgrade => 1, + }, + }, + + # We should never see the creation of a trigger on a partition + 'Disabled trigger on partition is not created' => { + regexp => qr/CREATE TRIGGER test_trigger.*ON dump_test_second_schema/, + like => {}, + unlike => { %full_runs, %dump_test_schema_runs }, + }, + + # Triggers on partitions should not be dropped individually + 'Triggers on partitions are not dropped' => { + regexp => qr/DROP TRIGGER test_trigger.*ON dump_test_second_schema/, + like => {} }, 'CREATE TABLE test_fourth_table_zero_col' => { @@ -3177,9 +3233,12 @@ my %tests = ( }, 'GRANT SELECT ON TABLE measurement_y2006m2' => { - create_order => 92, - create_sql => 'GRANT SELECT ON - TABLE dump_test_second_schema.measurement_y2006m2 + create_order => 94, + create_sql => 'GRANT SELECT ON TABLE + dump_test_second_schema.measurement_y2006m2, + dump_test_second_schema.measurement_y2006m3, + dump_test_second_schema.measurement_y2006m4, + dump_test_second_schema.measurement_y2006m5 TO regress_dump_test_role;', regexp => qr/^\QGRANT SELECT ON TABLE dump_test_second_schema.measurement_y2006m2 TO regress_dump_test_role;\E/m, diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index d9ce961be2..a64f96e102 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -213,6 +213,8 @@ tmp|f trigger_parted|t trigger_parted_p1|t trigger_parted_p1_1|t +trigger_parted_p2|t +trigger_parted_p2_2|t varchar_tbl|f view_base_table|t -- restore normal output mode diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 42392f8f41..5254447cf8 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -3346,6 +3346,11 @@ create trigger aft_row after insert or update on trigger_parted create table trigger_parted_p1 partition of trigger_parted for values in (1) partition by list (a); create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1); +create table trigger_parted_p2 partition of trigger_parted for values in (2) + partition by list (a); +create table trigger_parted_p2_2 partition of trigger_parted_p2 for values in (2); +alter table only trigger_parted_p2 disable trigger aft_row; +alter table trigger_parted_p2_2 enable always trigger aft_row; -- verify transition table conversion slot's lifetime -- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com create table convslot_test_parent (col1 text primary key); diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 0777c4f50f..7b73ee20a1 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -2502,6 +2502,11 @@ create trigger aft_row after insert or update on trigger_parted create table trigger_parted_p1 partition of trigger_parted for values in (1) partition by list (a); create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1); +create table trigger_parted_p2 partition of trigger_parted for values in (2) + partition by list (a); +create table trigger_parted_p2_2 partition of trigger_parted_p2 for values in (2); +alter table only trigger_parted_p2 disable trigger aft_row; +alter table trigger_parted_p2_2 enable always trigger aft_row; -- verify transition table conversion slot's lifetime -- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com