From 39049feba6a01efd0a9075705ef44443bd62b4d7 Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Thu, 30 Jan 2025 16:53:26 +0100 Subject: [PATCH] Allow EXPLAIN in read-only mode Using `EXPLAIN` when `transaction_read_only` is set to `on` fails with an error. This is fixed by explicitly setting the `check_read_only` flag. --- .unreleased/pr_7637 | 2 + src/process_utility.c | 1 + tsl/test/expected/read_only.out | 77 +++++++++++++++++++++++++++++++++ tsl/test/sql/read_only.sql | 52 ++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 .unreleased/pr_7637 diff --git a/.unreleased/pr_7637 b/.unreleased/pr_7637 new file mode 100644 index 00000000000..d1c0dd47d07 --- /dev/null +++ b/.unreleased/pr_7637 @@ -0,0 +1,2 @@ +Fixes: #7637 Allow EXPLAIN in read-only mode +Thanks: @ikalafat for reporting the error diff --git a/src/process_utility.c b/src/process_utility.c index 239feebbf49..9dc1fb521d8 100644 --- a/src/process_utility.c +++ b/src/process_utility.c @@ -4859,6 +4859,7 @@ process_ddl_command_start(ProcessUtilityArgs *args) handler = preprocess_execute; break; case T_ExplainStmt: + check_read_only = false; handler = process_explain_start; break; diff --git a/tsl/test/expected/read_only.out b/tsl/test/expected/read_only.out index f9057559143..d478f4b3775 100644 --- a/tsl/test/expected/read_only.out +++ b/tsl/test/expected/read_only.out @@ -8,7 +8,84 @@ -- create_hypertable() -- CREATE TABLE test_table(time bigint NOT NULL, device int); +-- EXPLAIN should work in read-only mode, when enabling in transaction. +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (COSTS OFF) SELECT * FROM test_table; + QUERY PLAN +------------------------ + Seq Scan on test_table +(1 row) + +ROLLBACK; +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_table; + QUERY PLAN +------------------------------------------------ + Seq Scan on test_table (actual rows=0 loops=1) +(1 row) + +ROLLBACK; +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (COSTS OFF) INSERT INTO test_table VALUES (1, 1); + QUERY PLAN +---------------------- + Insert on test_table + -> Result +(2 rows) + +ROLLBACK; +-- This should give an error since we are using ANALYZE and a DML. The +-- read-only is checked when executing the actual INSERT statement, +-- not inside our code. +\set ON_ERROR_STOP 0 +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) INSERT INTO test_table VALUES (1, 1); +ERROR: cannot execute INSERT in a read-only transaction +ROLLBACK; +\set ON_ERROR_STOP 1 SET default_transaction_read_only TO on; +-- EXPLAIN should work in read-only mode, even when using the default. +START TRANSACTION; +EXPLAIN (COSTS OFF) SELECT * FROM test_table; + QUERY PLAN +------------------------ + Seq Scan on test_table +(1 row) + +ROLLBACK; +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_table; + QUERY PLAN +------------------------------------------------ + Seq Scan on test_table (actual rows=0 loops=1) +(1 row) + +ROLLBACK; +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (COSTS OFF) INSERT INTO test_table VALUES (1, 1); + QUERY PLAN +---------------------- + Insert on test_table + -> Result +(2 rows) + +ROLLBACK; +-- This should give an error since we are using ANALYZE and a DML. The +-- read-only is checked when executing the actual INSERT statement, +-- not inside our code. +\set ON_ERROR_STOP 0 +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) INSERT INTO test_table VALUES (1, 1); +ERROR: cannot execute INSERT in a read-only transaction +ROLLBACK; +\set ON_ERROR_STOP 1 \set ON_ERROR_STOP 0 SELECT * FROM create_hypertable('test_table', 'time'); ERROR: cannot execute create_hypertable() in a read-only transaction diff --git a/tsl/test/sql/read_only.sql b/tsl/test/sql/read_only.sql index 4c0eec46629..cfcd3fce89e 100644 --- a/tsl/test/sql/read_only.sql +++ b/tsl/test/sql/read_only.sql @@ -12,8 +12,59 @@ -- CREATE TABLE test_table(time bigint NOT NULL, device int); +-- EXPLAIN should work in read-only mode, when enabling in transaction. +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (COSTS OFF) SELECT * FROM test_table; +ROLLBACK; + +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_table; +ROLLBACK; + +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (COSTS OFF) INSERT INTO test_table VALUES (1, 1); +ROLLBACK; + +-- This should give an error since we are using ANALYZE and a DML. The +-- read-only is checked when executing the actual INSERT statement, +-- not inside our code. +\set ON_ERROR_STOP 0 +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) INSERT INTO test_table VALUES (1, 1); +ROLLBACK; +\set ON_ERROR_STOP 1 + SET default_transaction_read_only TO on; +-- EXPLAIN should work in read-only mode, even when using the default. +START TRANSACTION; +EXPLAIN (COSTS OFF) SELECT * FROM test_table; +ROLLBACK; + +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) SELECT * FROM test_table; +ROLLBACK; + +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (COSTS OFF) INSERT INTO test_table VALUES (1, 1); +ROLLBACK; + +-- This should give an error since we are using ANALYZE and a DML. The +-- read-only is checked when executing the actual INSERT statement, +-- not inside our code. +\set ON_ERROR_STOP 0 +START TRANSACTION; +SET transaction_read_only TO on; +EXPLAIN (ANALYZE ON, COSTS OFF, TIMING OFF, SUMMARY OFF) INSERT INTO test_table VALUES (1, 1); +ROLLBACK; +\set ON_ERROR_STOP 1 + \set ON_ERROR_STOP 0 SELECT * FROM create_hypertable('test_table', 'time'); \set ON_ERROR_STOP 1 @@ -213,3 +264,4 @@ SELECT remove_retention_policy('test_table'); SELECT add_job('now','12h'); SELECT alter_job(1,scheduled:=false); SELECT delete_job(1); +