Skip to content

Commit

Permalink
Allow EXPLAIN in read-only mode
Browse files Browse the repository at this point in the history
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.

(cherry picked from commit 39049fe)
  • Loading branch information
mkindahl authored and timescale-automation committed Feb 4, 2025
1 parent 53aa376 commit 9c59317
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .unreleased/pr_7637
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixes: #7637 Allow EXPLAIN in read-only mode
Thanks: @ikalafat for reporting the error
1 change: 1 addition & 0 deletions src/process_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
77 changes: 77 additions & 0 deletions tsl/test/expected/read_only.out
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tsl/test/shared/expected/extension.out
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
debug_waitpoint_enable(text)
debug_waitpoint_id(text)
debug_waitpoint_release(text)
ts_hypercore_handler(internal)
ts_hypercore_proxy_handler(internal)
ts_now_mock()
add_columnstore_policy(regclass,"any",boolean,interval,timestamp with time zone,text,interval,boolean)
add_compression_policy(regclass,"any",boolean,interval,timestamp with time zone,text,interval,boolean)
Expand Down Expand Up @@ -298,8 +300,6 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
time_bucket_gapfill(smallint,smallint,smallint,smallint)
timescaledb_post_restore()
timescaledb_pre_restore()
ts_hypercore_handler(internal)
ts_hypercore_proxy_handler(internal)
timescaledb_experimental.add_policies(regclass,boolean,"any","any","any","any",boolean)
timescaledb_experimental.alter_policies(regclass,boolean,"any","any","any","any")
timescaledb_experimental.remove_all_policies(regclass,boolean)
Expand Down
52 changes: 52 additions & 0 deletions tsl/test/sql/read_only.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);

0 comments on commit 9c59317

Please sign in to comment.