]> granicus.if.org Git - postgresql/commitdiff
Fix dependency handling of partitions and inheritance for ON COMMIT
authorMichael Paquier <michael@paquier.xyz>
Fri, 9 Nov 2018 01:03:22 +0000 (10:03 +0900)
committerMichael Paquier <michael@paquier.xyz>
Fri, 9 Nov 2018 01:03:22 +0000 (10:03 +0900)
This commit fixes a set of issues with ON COMMIT actions when used on
partitioned tables and tables with inheritance children:
- Applying ON COMMIT DROP on a partitioned table with partitions or on a
table with inheritance children caused a failure at commit time, with
complains about the children being already dropped as all relations are
dropped one at the same time.
- Applying ON COMMIT DELETE on a partition relying on a partitioned
table which uses ON COMMIT DROP would cause the partition truncation to
fail as the parent is removed first.

The solution to the first problem is to handle the removal of all the
dependencies in one go instead of dropping relations one-by-one, based
on a suggestion from Álvaro Herrera.  So instead all the relation OIDs
to remove are gathered and then processed in one round of multiple
deletions.

The solution to the second problem is to reorder the actions, with
truncation happening first and relation drop done after.  Even if it
means that a partition could be first truncated, then immediately
dropped if its partitioned table is dropped, this has the merit to keep
the code simple as there is no need to do existence checks on the
relations to drop.

Contrary to a manual TRUNCATE on a partitioned table, ON COMMIT DELETE
does not cascade to its partitions.  The ON COMMIT action defined on
each partition gets the priority.

Author: Michael Paquier
Reviewed-by: Amit Langote, Álvaro Herrera, Robert Haas
Discussion: https://postgr.es/m/68f17907-ec98-1192-f99f-8011400517f5@lab.ntt.co.jp
Backpatch-through: 10

doc/src/sgml/ref/create_table.sgml
src/backend/commands/tablecmds.c
src/test/regress/expected/temp.out
src/test/regress/sql/temp.sql

index 10428f8ff03cc4799a7e1ef0630cde33cf41721a..4b9c8a78017135bbbfe4c1fa0262e71eb3e28955 100644 (file)
@@ -1215,7 +1215,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
           All rows in the temporary table will be deleted at the end
           of each transaction block.  Essentially, an automatic <xref
           linkend="sql-truncate"/> is done
-          at each commit.
+          at each commit.  When used on a partitioned table, this
+          is not cascaded to its partitions.
          </para>
         </listitem>
        </varlistentry>
@@ -1225,7 +1226,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         <listitem>
          <para>
           The temporary table will be dropped at the end of the current
-          transaction block.
+          transaction block.  When used on a partitioned table, this action
+          drops its partitions and when used on tables with inheritance
+          children, it drops the dependent children.
          </para>
         </listitem>
        </varlistentry>
index a0279ae383845942eb5a2e947da799f3736fe2a8..82989158eebdf4b967029bc7a2282ae917fba623 100644 (file)
@@ -13293,6 +13293,7 @@ PreCommit_on_commit_actions(void)
 {
        ListCell   *l;
        List       *oids_to_truncate = NIL;
+       List       *oids_to_drop = NIL;
 
        foreach(l, on_commits)
        {
@@ -13319,36 +13320,66 @@ PreCommit_on_commit_actions(void)
                                        oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
                                break;
                        case ONCOMMIT_DROP:
-                               {
-                                       ObjectAddress object;
-
-                                       object.classId = RelationRelationId;
-                                       object.objectId = oc->relid;
-                                       object.objectSubId = 0;
-
-                                       /*
-                                        * Since this is an automatic drop, rather than one
-                                        * directly initiated by the user, we pass the
-                                        * PERFORM_DELETION_INTERNAL flag.
-                                        */
-                                       performDeletion(&object,
-                                                                       DROP_CASCADE, PERFORM_DELETION_INTERNAL);
-
-                                       /*
-                                        * Note that table deletion will call
-                                        * remove_on_commit_action, so the entry should get marked
-                                        * as deleted.
-                                        */
-                                       Assert(oc->deleting_subid != InvalidSubTransactionId);
-                                       break;
-                               }
+                               oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
+                               break;
                }
        }
+
+       /*
+        * Truncate relations before dropping so that all dependencies between
+        * relations are removed after they are worked on.  Doing it like this
+        * might be a waste as it is possible that a relation being truncated will
+        * be dropped anyway due to its parent being dropped, but this makes the
+        * code more robust because of not having to re-check that the relation
+        * exists at truncation time.
+        */
        if (oids_to_truncate != NIL)
        {
                heap_truncate(oids_to_truncate);
                CommandCounterIncrement();      /* XXX needed? */
        }
+       if (oids_to_drop != NIL)
+       {
+               ObjectAddresses *targetObjects = new_object_addresses();
+               ListCell   *l;
+
+               foreach(l, oids_to_drop)
+               {
+                       ObjectAddress object;
+
+                       object.classId = RelationRelationId;
+                       object.objectId = lfirst_oid(l);
+                       object.objectSubId = 0;
+
+                       Assert(!object_address_present(&object, targetObjects));
+
+                       add_exact_object_address(&object, targetObjects);
+               }
+
+               /*
+                * Since this is an automatic drop, rather than one directly initiated
+                * by the user, we pass the PERFORM_DELETION_INTERNAL flag.
+                */
+               performMultipleDeletions(targetObjects, DROP_CASCADE,
+                                                                PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY);
+
+#ifdef USE_ASSERT_CHECKING
+
+               /*
+                * Note that table deletion will call remove_on_commit_action, so the
+                * entry should get marked as deleted.
+                */
+               foreach(l, on_commits)
+               {
+                       OnCommitItem *oc = (OnCommitItem *) lfirst(l);
+
+                       if (oc->oncommit != ONCOMMIT_DROP)
+                               continue;
+
+                       Assert(oc->deleting_subid != InvalidSubTransactionId);
+               }
+#endif
+       }
 }
 
 /*
index a769abe9bba51c6305f5601bea1efc1e69110d6f..f018f17ca0a8e702a4f3f4e22e3c8405fa62187c 100644 (file)
@@ -216,3 +216,88 @@ select * from temp_parted_oncommit;
 (0 rows)
 
 drop table temp_parted_oncommit;
+-- Check dependencies between ON COMMIT actions with a partitioned
+-- table and its partitions.  Using ON COMMIT DROP on a parent removes
+-- the whole set.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+  partition by list (a) on commit drop;
+create temp table temp_parted_oncommit_test1
+  partition of temp_parted_oncommit_test
+  for values in (1) on commit delete rows;
+create temp table temp_parted_oncommit_test2
+  partition of temp_parted_oncommit_test
+  for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- no relations remain in this case.
+select relname from pg_class where relname like 'temp_parted_oncommit_test%';
+ relname 
+---------
+(0 rows)
+
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+  partition by list (a) on commit delete rows;
+create temp table temp_parted_oncommit_test1
+  partition of temp_parted_oncommit_test
+  for values in (1) on commit preserve rows;
+create temp table temp_parted_oncommit_test2
+  partition of temp_parted_oncommit_test
+  for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+select * from temp_parted_oncommit_test;
+ a 
+---
+ 1
+(1 row)
+
+-- two relations remain in this case.
+select relname from pg_class where relname like 'temp_parted_oncommit_test%';
+          relname           
+----------------------------
+ temp_parted_oncommit_test
+ temp_parted_oncommit_test1
+(2 rows)
+
+drop table temp_parted_oncommit_test;
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Using ON COMMIT DROP on a parent removes the whole set.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit drop;
+create temp table temp_inh_oncommit_test1 ()
+  inherits(temp_inh_oncommit_test) on commit delete rows;
+insert into temp_inh_oncommit_test1 values (1);
+commit;
+-- no relations remain in this case
+select relname from pg_class where relname like 'temp_inh_oncommit_test%';
+ relname 
+---------
+(0 rows)
+
+-- Data on the parent is removed, and the child goes away.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit delete rows;
+create temp table temp_inh_oncommit_test1 ()
+  inherits(temp_inh_oncommit_test) on commit drop;
+insert into temp_inh_oncommit_test1 values (1);
+insert into temp_inh_oncommit_test values (1);
+commit;
+select * from temp_inh_oncommit_test;
+ a 
+---
+(0 rows)
+
+-- one relation remains
+select relname from pg_class where relname like 'temp_inh_oncommit_test%';
+        relname         
+------------------------
+ temp_inh_oncommit_test
+(1 row)
+
+drop table temp_inh_oncommit_test;
index 1074c7cfac85f23c7eb24ab58c86544d17b67721..1beccc6cebb0f6828a6b75898e99a40ed9bdc681 100644 (file)
@@ -165,3 +165,62 @@ commit;
 -- partitions are emptied by the previous commit
 select * from temp_parted_oncommit;
 drop table temp_parted_oncommit;
+
+-- Check dependencies between ON COMMIT actions with a partitioned
+-- table and its partitions.  Using ON COMMIT DROP on a parent removes
+-- the whole set.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+  partition by list (a) on commit drop;
+create temp table temp_parted_oncommit_test1
+  partition of temp_parted_oncommit_test
+  for values in (1) on commit delete rows;
+create temp table temp_parted_oncommit_test2
+  partition of temp_parted_oncommit_test
+  for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- no relations remain in this case.
+select relname from pg_class where relname like 'temp_parted_oncommit_test%';
+-- Using ON COMMIT DELETE on a partitioned table does not remove
+-- all rows if partitions preserve their data.
+begin;
+create temp table temp_parted_oncommit_test (a int)
+  partition by list (a) on commit delete rows;
+create temp table temp_parted_oncommit_test1
+  partition of temp_parted_oncommit_test
+  for values in (1) on commit preserve rows;
+create temp table temp_parted_oncommit_test2
+  partition of temp_parted_oncommit_test
+  for values in (2) on commit drop;
+insert into temp_parted_oncommit_test values (1), (2);
+commit;
+-- Data from the remaining partition is still here as its rows are
+-- preserved.
+select * from temp_parted_oncommit_test;
+-- two relations remain in this case.
+select relname from pg_class where relname like 'temp_parted_oncommit_test%';
+drop table temp_parted_oncommit_test;
+
+-- Check dependencies between ON COMMIT actions with inheritance trees.
+-- Using ON COMMIT DROP on a parent removes the whole set.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit drop;
+create temp table temp_inh_oncommit_test1 ()
+  inherits(temp_inh_oncommit_test) on commit delete rows;
+insert into temp_inh_oncommit_test1 values (1);
+commit;
+-- no relations remain in this case
+select relname from pg_class where relname like 'temp_inh_oncommit_test%';
+-- Data on the parent is removed, and the child goes away.
+begin;
+create temp table temp_inh_oncommit_test (a int) on commit delete rows;
+create temp table temp_inh_oncommit_test1 ()
+  inherits(temp_inh_oncommit_test) on commit drop;
+insert into temp_inh_oncommit_test1 values (1);
+insert into temp_inh_oncommit_test values (1);
+commit;
+select * from temp_inh_oncommit_test;
+-- one relation remains
+select relname from pg_class where relname like 'temp_inh_oncommit_test%';
+drop table temp_inh_oncommit_test;