summaryrefslogtreecommitdiff
path: root/src/test/isolation
diff options
context:
space:
mode:
authorMichael Paquier2018-08-10 16:26:59 +0000
committerMichael Paquier2018-08-10 16:26:59 +0000
commitf841ceb26d701e7f5f48190bf7290c400cba30a2 (patch)
tree6744ff440333ec7dd2ac1329511323c677525c6d /src/test/isolation
parent2b13702d5c7e879bdd0587a80d5cfaeb5206f4ea (diff)
Improve TRUNCATE by avoiding early lock queue
A caller of TRUNCATE could previously queue for an access exclusive lock on a relation it may not have permission to truncate, potentially interfering with users authorized to work on it. This can be very intrusive depending on the lock attempted to be taken. For example, pg_authid could be blocked, preventing any authentication attempt to happen on a PostgreSQL instance. This commit fixes the case of TRUNCATE so as RangeVarGetRelidExtended is used with a callback doing the necessary ACL checks at an earlier stage, avoiding lock queuing issues, so as an immediate failure happens for unprivileged users instead of waiting on a lock that would not be taken. This is rather similar to the type of work done in cbe24a6 for CLUSTER, and the code of TRUNCATE is this time refactored so as there is no user-facing changes. As the commit for CLUSTER, no back-patch is done. Reported-by: Lloyd Albin, Jeremy Schneider Author: Michael Paquier Reviewed by: Nathan Bossart, Kyotaro Horiguchi Discussion: https://postgr.es/m/152512087100.19803.12733865831237526317@wrigleys.postgresql.org Discussion: https://postgr.es/m/20180806165816.GA19883@paquier.xyz
Diffstat (limited to 'src/test/isolation')
-rw-r--r--src/test/isolation/expected/truncate-conflict.out99
-rw-r--r--src/test/isolation/isolation_schedule1
-rw-r--r--src/test/isolation/specs/truncate-conflict.spec38
3 files changed, 138 insertions, 0 deletions
diff --git a/src/test/isolation/expected/truncate-conflict.out b/src/test/isolation/expected/truncate-conflict.out
new file mode 100644
index 00000000000..2c10f8d40d4
--- /dev/null
+++ b/src/test/isolation/expected/truncate-conflict.out
@@ -0,0 +1,99 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_tab_lookup s2_auth s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s2_truncate s1_tab_lookup s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_tab_lookup s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_auth s2_truncate s1_begin s1_tab_lookup s1_commit s2_reset
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+ERROR: permission denied for table truncate_tab
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s1_tab_lookup s2_grant s2_auth s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_truncate: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s2_truncate s1_tab_lookup s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_grant s2_auth s1_tab_lookup s2_truncate s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s2_truncate: TRUNCATE truncate_tab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_truncate: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s2_grant s2_auth s2_truncate s1_begin s1_tab_lookup s1_commit s2_reset
+step s2_grant: GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict;
+step s2_auth: SET ROLE regress_truncate_conflict;
+step s2_truncate: TRUNCATE truncate_tab;
+step s1_begin: BEGIN;
+step s1_tab_lookup: SELECT count(*) >= 0 FROM truncate_tab;
+?column?
+
+t
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index d5594e80e23..48ae7407399 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -76,3 +76,4 @@ test: partition-key-update-2
test: partition-key-update-3
test: partition-key-update-4
test: plpgsql-toast
+test: truncate-conflict
diff --git a/src/test/isolation/specs/truncate-conflict.spec b/src/test/isolation/specs/truncate-conflict.spec
new file mode 100644
index 00000000000..3c1b1d1b348
--- /dev/null
+++ b/src/test/isolation/specs/truncate-conflict.spec
@@ -0,0 +1,38 @@
+# Tests for locking conflicts with TRUNCATE commands.
+
+setup
+{
+ CREATE ROLE regress_truncate_conflict;
+ CREATE TABLE truncate_tab (a int);
+}
+
+teardown
+{
+ DROP TABLE truncate_tab;
+ DROP ROLE regress_truncate_conflict;
+}
+
+session "s1"
+step "s1_begin" { BEGIN; }
+step "s1_tab_lookup" { SELECT count(*) >= 0 FROM truncate_tab; }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+step "s2_grant" { GRANT TRUNCATE ON truncate_tab TO regress_truncate_conflict; }
+step "s2_auth" { SET ROLE regress_truncate_conflict; }
+step "s2_truncate" { TRUNCATE truncate_tab; }
+step "s2_reset" { RESET ROLE; }
+
+# The role doesn't have privileges to truncate the table, so TRUNCATE should
+# immediately fail without waiting for a lock.
+permutation "s1_begin" "s1_tab_lookup" "s2_auth" "s2_truncate" "s1_commit" "s2_reset"
+permutation "s1_begin" "s2_auth" "s2_truncate" "s1_tab_lookup" "s1_commit" "s2_reset"
+permutation "s1_begin" "s2_auth" "s1_tab_lookup" "s2_truncate" "s1_commit" "s2_reset"
+permutation "s2_auth" "s2_truncate" "s1_begin" "s1_tab_lookup" "s1_commit" "s2_reset"
+
+# The role has privileges to truncate the table, TRUNCATE will block if
+# another session holds a lock on the table and succeed in all cases.
+permutation "s1_begin" "s1_tab_lookup" "s2_grant" "s2_auth" "s2_truncate" "s1_commit" "s2_reset"
+permutation "s1_begin" "s2_grant" "s2_auth" "s2_truncate" "s1_tab_lookup" "s1_commit" "s2_reset"
+permutation "s1_begin" "s2_grant" "s2_auth" "s1_tab_lookup" "s2_truncate" "s1_commit" "s2_reset"
+permutation "s2_grant" "s2_auth" "s2_truncate" "s1_begin" "s1_tab_lookup" "s1_commit" "s2_reset"