Hi, Please find attached a patch to enable support for temporary tables in prepared transactions when ON COMMIT DROP has been specified.
The comment in the existing code around this idea reads: * Don't allow PREPARE TRANSACTION if we've accessed a temporary table in * this transaction. [ ... ] * XXX In principle this could be relaxed to allow some useful special * cases, such as a temp table created and dropped all within the * transaction. That seems to require much more bookkeeping though. In the attached patch I have added this paragraph, and of course the implementation of it: * A special case of this situation is using ON COMMIT DROP, where the * call to PreCommit_on_commit_actions() is then responsible for * performing the DROP table within the transaction and before we get * here. Regards, -- dim
commit 7dd834a2fb57ba617d70abf7a23eb5cc84dadca5 Author: Dimitri Fontaine <d...@tapoueh.org> Date: Thu Dec 27 12:22:56 2018 +0100 Add support for on-commit-drop temp tables to prepared transactions. As the bookkeeping is all done within PreCommit_on_commit_actions() we can prepare a transaction that have been accessing temporary tables when all of them as marked ON COMMIT DROP. diff --git a/doc/src/sgml/ref/prepare_transaction.sgml b/doc/src/sgml/ref/prepare_transaction.sgml index d958f7a06f..c1ef0707cf 100644 --- a/doc/src/sgml/ref/prepare_transaction.sgml +++ b/doc/src/sgml/ref/prepare_transaction.sgml @@ -97,8 +97,9 @@ PREPARE TRANSACTION <replaceable class="parameter">transaction_id</replaceable> </para> <para> - It is not currently allowed to <command>PREPARE</command> a transaction that - has executed any operations involving temporary tables, + It is not currently allowed to <command>PREPARE</command> a transaction + that has executed any operations involving temporary tables (except when + all involved temporary tables are <literal>ON COMMIT DROP</literal>), created any cursors <literal>WITH HOLD</literal>, or executed <command>LISTEN</command>, <command>UNLISTEN</command>, or <command>NOTIFY</command>. diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index d967400384..1d7f3017ad 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2272,11 +2272,19 @@ PrepareTransaction(void) * XXX In principle this could be relaxed to allow some useful special * cases, such as a temp table created and dropped all within the * transaction. That seems to require much more bookkeeping though. + * + * A special case of this situation is using ON COMMIT DROP, where the + * call to PreCommit_on_commit_actions() is then responsible for + * performing the DROP table within the transaction and before we get + * here. */ - if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPREL)) + if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPREL) + && !every_on_commit_is_on_commit_drop()) + { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot PREPARE a transaction that has operated on temporary tables"))); + errmsg("cannot PREPARE a transaction that has operated on temporary tables that are not ON COMMIT DROP"))); + } /* * Likewise, don't allow PREPARE after pg_export_snapshot. This could be diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c8c50e8c98..2060f3e68e 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -13139,6 +13139,26 @@ remove_on_commit_action(Oid relid) } } +/* + * Return true when every temp relation known in the current session is marked + * ON COMMIT DROP. This allows to then PREPARE TRANSACTION, for instance, + * because we know that at prepare time the temp table is dropped already. + */ +bool +every_on_commit_is_on_commit_drop() +{ + ListCell *l; + + foreach(l, on_commits) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(l); + + if (oc->oncommit != ONCOMMIT_DROP) + return false; + } + return true; +} + /* * Perform ON COMMIT actions. * diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 2afcd5be44..2f5731a9f6 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -82,6 +82,7 @@ extern void createForeignKeyTriggers(Relation rel, Oid refRelOid, extern void register_on_commit_action(Oid relid, OnCommitAction action); extern void remove_on_commit_action(Oid relid); +extern bool every_on_commit_is_on_commit_drop(void); extern void PreCommit_on_commit_actions(void); extern void AtEOXact_on_commit_actions(bool isCommit); diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out index eb77c18788..a50ca764c5 100644 --- a/src/test/regress/expected/prepared_xacts.out +++ b/src/test/regress/expected/prepared_xacts.out @@ -252,3 +252,95 @@ DROP TABLE pxtest2; DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled ERROR: table "pxtest3" does not exist DROP TABLE pxtest4; +-- we should be able to prepare a transaction with on commit drop temporary +-- tables +begin; +create temp table pxtesttemp(id serial, f1 text) on commit drop; +insert into pxtesttemp(f1) values('regress'), ('temp') returning *; + id | f1 +----+--------- + 1 | regress + 2 | temp +(2 rows) + +prepare transaction 'regress temp'; +rollback prepared 'regress temp'; +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- other kinds of temporary tables are not supported though. +begin; +create temp table pxtesttemp(id serial, f1 text) on commit delete rows; +insert into pxtesttemp(f1) values('regress'), ('temp') returning *; + id | f1 +----+--------- + 1 | regress + 2 | temp +(2 rows) + +prepare transaction 'regress temp'; +ERROR: cannot PREPARE a transaction that has operated on temporary tables +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +begin; +create temp table pxtesttemp1(id serial, f1 text) on commit drop; +create temp table pxtesttemp2(id serial, f1 text) on commit delete rows; +insert into pxtesttemp1(f1) values('regress'), ('temp') returning *; + id | f1 +----+--------- + 1 | regress + 2 | temp +(2 rows) + +prepare transaction 'regress temp'; +ERROR: cannot PREPARE a transaction that has operated on temporary tables +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- Test with two temporary tables on commit drop now +begin; +create temp table pxtesttemp1(id serial, f1 text) on commit drop; +create temp table pxtesttemp2(id serial, f1 text) on commit drop; +insert into pxtesttemp1(f1) values('regress'), ('temp'); +insert into pxtesttemp2(f1) values('regress'), ('temp'); +prepare transaction 'regress temp'; +rollback prepared 'regress temp'; +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- Now test with a temp table that exists in the session, and a temp table +-- that exists only within the prepared transaction. +create temp table pxtesttemp1(id serial, f1 text); +insert into pxtesttemp1(f1) values('regress'), ('temp') returning *; + id | f1 +----+--------- + 1 | regress + 2 | temp +(2 rows) + +begin; +create temp table pxtesttemp2(id serial, f1 text) on commit drop; +insert into pxtesttemp2(f1) values('regress'), ('temp'); +prepare transaction 'regress temp'; +rollback prepared 'regress temp'; +-- cleanup +drop table pxtesttemp1; +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql index d8249a27dc..31ab2c9513 100644 --- a/src/test/regress/sql/prepared_xacts.sql +++ b/src/test/regress/sql/prepared_xacts.sql @@ -156,3 +156,61 @@ SELECT gid FROM pg_prepared_xacts; DROP TABLE pxtest2; DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled DROP TABLE pxtest4; + +-- we should be able to prepare a transaction with on commit drop temporary +-- tables +begin; +create temp table pxtesttemp(id serial, f1 text) on commit drop; +insert into pxtesttemp(f1) values('regress'), ('temp') returning *; +prepare transaction 'regress temp'; +rollback prepared 'regress temp'; + +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + +-- other kinds of temporary tables are not supported though. +begin; +create temp table pxtesttemp(id serial, f1 text) on commit delete rows; +insert into pxtesttemp(f1) values('regress'), ('temp') returning *; +prepare transaction 'regress temp'; + +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + +begin; +create temp table pxtesttemp1(id serial, f1 text) on commit drop; +create temp table pxtesttemp2(id serial, f1 text) on commit delete rows; +insert into pxtesttemp1(f1) values('regress'), ('temp') returning *; +prepare transaction 'regress temp'; + +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + +-- Test with two temporary tables on commit drop now +begin; +create temp table pxtesttemp1(id serial, f1 text) on commit drop; +create temp table pxtesttemp2(id serial, f1 text) on commit drop; +insert into pxtesttemp1(f1) values('regress'), ('temp'); +insert into pxtesttemp2(f1) values('regress'), ('temp'); +prepare transaction 'regress temp'; +rollback prepared 'regress temp'; + +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + +-- Now test with a temp table that exists in the session, and a temp table +-- that exists only within the prepared transaction. +create temp table pxtesttemp1(id serial, f1 text); +insert into pxtesttemp1(f1) values('regress'), ('temp') returning *; + +begin; +create temp table pxtesttemp2(id serial, f1 text) on commit drop; +insert into pxtesttemp2(f1) values('regress'), ('temp'); +prepare transaction 'regress temp'; +rollback prepared 'regress temp'; + +-- cleanup +drop table pxtesttemp1; + +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts;