]> granicus.if.org Git - postgresql/commitdiff
Ignore inherited temp relations from other sessions when truncating
authorMichael Paquier <michael@paquier.xyz>
Thu, 27 Dec 2018 01:17:21 +0000 (10:17 +0900)
committerMichael Paquier <michael@paquier.xyz>
Thu, 27 Dec 2018 01:17:21 +0000 (10:17 +0900)
Inheritance trees can include temporary tables if the parent is
permanent, which makes possible the presence of multiple temporary
children from different sessions.  Trying to issue a TRUNCATE on the
parent in this scenario causes a failure, so similarly to any other
queries just ignore such cases, which makes TRUNCATE work
transparently.

This makes truncation behave similarly to any other DML query working on
the parent table with queries which need to be issues on children.  A
set of isolation tests is added to cover basic cases.

Reported-by: Zhou Digoal
Author: Amit Langote, Michael Paquier
Discussion: https://postgr.es/m/15565-ce67a48d0244436a@postgresql.org
Backpatch-through: 9.4

src/backend/commands/tablecmds.c
src/test/isolation/expected/inherit-temp.out [new file with mode: 0644]
src/test/isolation/isolation_schedule
src/test/isolation/specs/inherit-temp.spec [new file with mode: 0644]

index 78e30017714ff0d9d1c8f7dff0b4c098ecef7efa..a7722052faa1566664496f20c30f4281a6ac310c 100644 (file)
@@ -1021,13 +1021,14 @@ ExecuteTruncate(TruncateStmt *stmt)
                Relation        rel;
                bool            recurse = interpretInhOption(rv->inhOpt);
                Oid                     myrelid;
+               LOCKMODE        lockmode = AccessExclusiveLock;
 
-               rel = heap_openrv(rv, AccessExclusiveLock);
+               rel = heap_openrv(rv, lockmode);
                myrelid = RelationGetRelid(rel);
                /* don't throw error for "TRUNCATE foo, foo" */
                if (list_member_oid(relids, myrelid))
                {
-                       heap_close(rel, AccessExclusiveLock);
+                       heap_close(rel, lockmode);
                        continue;
                }
                truncate_check_rel(rel);
@@ -1039,7 +1040,7 @@ ExecuteTruncate(TruncateStmt *stmt)
                        ListCell   *child;
                        List       *children;
 
-                       children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);
+                       children = find_all_inheritors(myrelid, lockmode, NULL);
 
                        foreach(child, children)
                        {
@@ -1050,6 +1051,22 @@ ExecuteTruncate(TruncateStmt *stmt)
 
                                /* find_all_inheritors already got lock */
                                rel = heap_open(childrelid, NoLock);
+
+                               /*
+                                * It is possible that the parent table has children that are
+                                * temp tables of other backends.  We cannot safely access
+                                * such tables (because of buffering issues), and the best
+                                * thing to do is to silently ignore them.  Note that this
+                                * check is the same as one of the checks done in
+                                * truncate_check_rel() called below, still it is kept
+                                * here for simplicity.
+                                */
+                               if (RELATION_IS_OTHER_TEMP(rel))
+                               {
+                                       heap_close(rel, lockmode);
+                                       continue;
+                               }
+
                                truncate_check_rel(rel);
                                rels = lappend(rels, rel);
                                relids = lappend_oid(relids, childrelid);
diff --git a/src/test/isolation/expected/inherit-temp.out b/src/test/isolation/expected/inherit-temp.out
new file mode 100644 (file)
index 0000000..edfc8f9
--- /dev/null
@@ -0,0 +1,217 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+2              
+5              
+6              
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_update_p s1_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_update_p: UPDATE inh_parent SET a = 11 WHERE a = 1;
+step s1_update_c: UPDATE inh_parent SET a = 13 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+2              
+11             
+4              
+13             
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+4              
+13             
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+2              
+11             
+5              
+6              
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_update_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s2_update_c: UPDATE inh_parent SET a = 15 WHERE a IN (3, 5);
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+2              
+6              
+15             
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+6              
+15             
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_delete_p s1_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_delete_p: DELETE FROM inh_parent WHERE a = 2;
+step s1_delete_c: DELETE FROM inh_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+3              
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+3              
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+5              
+6              
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_delete_c s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s2_delete_c: DELETE FROM inh_parent WHERE a IN (4, 6);
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+2              
+3              
+4              
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+1              
+2              
+5              
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+5              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_truncate_p: TRUNCATE inh_parent;
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+5              
+6              
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s2_truncate_p s1_select_p s1_select_c s2_select_p s2_select_c
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s2_truncate_p: TRUNCATE inh_parent;
+step s1_select_p: SELECT a FROM inh_parent;
+a              
+
+3              
+4              
+step s1_select_c: SELECT a FROM inh_temp_child_s1;
+a              
+
+3              
+4              
+step s2_select_p: SELECT a FROM inh_parent;
+a              
+
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_p s1_commit
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_parent;
+step s2_select_p: SELECT a FROM inh_parent; <waiting ...>
+step s1_commit: COMMIT;
+step s2_select_p: <... completed>
+a              
+
+5              
+6              
+
+starting permutation: s1_insert_p s1_insert_c s2_insert_c s1_begin s1_truncate_p s2_select_c s1_commit
+step s1_insert_p: INSERT INTO inh_parent VALUES (1), (2);
+step s1_insert_c: INSERT INTO inh_temp_child_s1 VALUES (3), (4);
+step s2_insert_c: INSERT INTO inh_temp_child_s2 VALUES (5), (6);
+step s1_begin: BEGIN;
+step s1_truncate_p: TRUNCATE inh_parent;
+step s2_select_c: SELECT a FROM inh_temp_child_s2;
+a              
+
+5              
+6              
+step s1_commit: COMMIT;
index 254adaff42d470dcf9f64e495796b9a3c97e42be..b4a2ade421a636eb590905f958c8be2bfbe4c2a8 100644 (file)
@@ -24,6 +24,7 @@ test: fk-deadlock2
 test: eval-plan-qual
 test: lock-update-delete
 test: lock-update-traversal
+test: inherit-temp
 test: insert-conflict-do-nothing
 test: insert-conflict-do-nothing-2
 test: insert-conflict-do-update
diff --git a/src/test/isolation/specs/inherit-temp.spec b/src/test/isolation/specs/inherit-temp.spec
new file mode 100644 (file)
index 0000000..5cd251d
--- /dev/null
@@ -0,0 +1,78 @@
+# Tests for inheritance trees with temporary relations
+#
+# Inheritance trees are allowed to mix relations with different persistence
+# as long as a persistent child relation does not try to inherit from a
+# temporary parent.  This checks several scenarios with SELECT, INSERT, UPDATE,
+# DELETE and TRUNCATE.  Any temporary relation inheriting from the same
+# persistent parent should be isolated and handled only in its own session.
+
+setup
+{
+  CREATE TABLE inh_parent (a int);
+}
+
+teardown
+{
+  DROP TABLE inh_parent;
+}
+
+# Session 1 executes actions which act directly on both the parent and
+# its child.  Abbreviation "c" is used for queries working on the child
+# and "p" on the parent.
+session "s1"
+setup
+{
+  CREATE TEMPORARY TABLE inh_temp_child_s1 () INHERITS (inh_parent);
+}
+step "s1_begin" { BEGIN; }
+step "s1_truncate_p" { TRUNCATE inh_parent; }
+step "s1_select_p" { SELECT a FROM inh_parent; }
+step "s1_select_c" { SELECT a FROM inh_temp_child_s1; }
+step "s1_insert_p" { INSERT INTO inh_parent VALUES (1), (2); }
+step "s1_insert_c" { INSERT INTO inh_temp_child_s1 VALUES (3), (4); }
+step "s1_update_p" { UPDATE inh_parent SET a = 11 WHERE a = 1; }
+step "s1_update_c" { UPDATE inh_parent SET a = 13 WHERE a IN (3, 5); }
+step "s1_delete_p" { DELETE FROM inh_parent WHERE a = 2; }
+step "s1_delete_c" { DELETE FROM inh_parent WHERE a IN (4, 6); }
+step "s1_commit" { COMMIT; }
+teardown
+{
+  DROP TABLE inh_temp_child_s1;
+}
+
+# Session 2 executes actions on the parent which act only on the child.
+session "s2"
+setup
+{
+  CREATE TEMPORARY TABLE inh_temp_child_s2 () INHERITS (inh_parent);
+}
+step "s2_truncate_p" { TRUNCATE inh_parent; }
+step "s2_select_p" { SELECT a FROM inh_parent; }
+step "s2_select_c" { SELECT a FROM inh_temp_child_s2; }
+step "s2_insert_c" { INSERT INTO inh_temp_child_s2 VALUES (5), (6); }
+step "s2_update_c" { UPDATE inh_parent SET a = 15 WHERE a IN (3, 5); }
+step "s2_delete_c" { DELETE FROM inh_parent WHERE a IN (4, 6); }
+teardown
+{
+  DROP TABLE inh_temp_child_s2;
+}
+
+# Check INSERT behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check UPDATE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_update_p" "s1_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_update_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check DELETE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_delete_p" "s1_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_delete_c" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# Check TRUNCATE behavior across sessions
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s2_truncate_p" "s1_select_p" "s1_select_c" "s2_select_p" "s2_select_c"
+
+# TRUNCATE on a parent tree does not block access to temporary child relation
+# of another session, and blocks when scanning the parent.
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_p" "s1_commit"
+permutation "s1_insert_p" "s1_insert_c" "s2_insert_c" "s1_begin" "s1_truncate_p" "s2_select_c" "s1_commit"