diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index dcfbb362c73..a59581cad9a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -703,7 +703,8 @@ static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode); static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode, bool copyIndexes); -static bool ATSetTableSpaceCopyIndexes(AlterTableUtilityContext *context); +static bool ATSetTableSpaceCopyIndexes(AlterTableStmt *parsetree, List *wqueue, + AlterTableUtilityContext *context); static void ATExecSetTableSpaceNewIndexRelfilenumber(Oid indexOid, LOCKMODE lockmode); static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace); static void ATExecSetRelOptions(Relation rel, List *defList, @@ -6102,7 +6103,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, */ if (tab->newTableSpace) ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode, - ATSetTableSpaceCopyIndexes(context)); + ATSetTableSpaceCopyIndexes(parsetree, *wqueue, + context)); } /* @@ -17641,18 +17643,29 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode, * Does ALTER TABLE SET TABLESPACE need to give the table's indexes new files? * * Only if something can still modify the table in the same transaction after - * the move. Nothing can when the ALTER is a top-level statement outside any - * transaction block and nothing runs after it before the commit: no - * ddl_command_end event trigger, and no further statement of an - * extended-protocol pipeline, which would otherwise share the transaction - * until the next Sync. To rule out the latter, force the commit right after - * the ALTER, as PreventInTransactionBlock does. + * the move. Rather than listing every place where user code can run after + * it -- the validation of other tables in the work queue, such as an + * inheritance child getting a new CHECK constraint, foreign key validation, + * sql_drop and table_rewrite event triggers, and more -- the files are kept + * only for the one case where none of them exists: a plain + * "ALTER TABLE ... SET TABLESPACE" of a single table, with no other + * subcommand, issued as a top-level statement outside any transaction block. + * What can still run after it is a ddl_command_end event trigger, and a + * further statement of an extended-protocol pipeline, which would share the + * transaction until the next Sync. Rule out the former by looking for such + * triggers, and the latter by forcing the commit right after the ALTER, as + * PreventInTransactionBlock does. */ static bool -ATSetTableSpaceCopyIndexes(AlterTableUtilityContext *context) +ATSetTableSpaceCopyIndexes(AlterTableStmt *parsetree, List *wqueue, + AlterTableUtilityContext *context) { if (context == NULL || IsInTransactionBlock(context->isTopLevel)) return true; + if (parsetree == NULL || list_length(parsetree->cmds) != 1 || + castNode(AlterTableCmd, linitial(parsetree->cmds))->subtype != AT_SetTableSpace || + list_length(wqueue) != 1) + return true; if (EventCacheLookup(EVT_DDLCommandEnd) != NIL) return true; diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out index ccc43061aad..aec469aee9f 100644 --- a/src/test/regress/expected/tablespace.out +++ b/src/test/regress/expected/tablespace.out @@ -1071,6 +1071,39 @@ SELECT count(*) FROM tbspace_subxact; RESET enable_seqscan; RESET enable_bitmapscan; DROP TABLE tbspace_subxact; +-- Combined with another subcommand, the ALTER can run user code after the +-- move, such as a CHECK constraint validated on an inheritance child after +-- the parent was moved, so the indexes get new files even outside a +-- transaction block. +CREATE TABLE tbspace_combined (a int); +INSERT INTO tbspace_combined SELECT generate_series(1, 100); +CREATE INDEX tbspace_combined_idx ON tbspace_combined (a); +CREATE TABLE tbspace_combined_child () INHERITS (tbspace_combined); +INSERT INTO tbspace_combined_child VALUES (5000); +CREATE FUNCTION tbspace_combined_chk(v int) RETURNS bool LANGUAGE plpgsql AS $$ +BEGIN + IF v < 5000 THEN RETURN true; END IF; + INSERT INTO tbspace_combined SELECT -generate_series(1, 50); + RAISE EXCEPTION 'abort after writing'; +END $$; +ALTER TABLE tbspace_combined SET TABLESPACE regress_tblspace, + ADD CONSTRAINT tbspace_combined_k CHECK (tbspace_combined_chk(a)); +ERROR: abort after writing +CONTEXT: PL/pgSQL function tbspace_combined_chk(integer) line 5 at RAISE +DROP TABLE tbspace_combined_child; +INSERT INTO tbspace_combined SELECT generate_series(101, 300); +SET enable_seqscan = off; +SET enable_bitmapscan = off; +SELECT count(*) FROM tbspace_combined WHERE a < 0; + count +------- + 0 +(1 row) + +RESET enable_seqscan; +RESET enable_bitmapscan; +DROP TABLE tbspace_combined; +DROP FUNCTION tbspace_combined_chk(int); ALTER TABLESPACE regress_tblspace RENAME TO regress_tblspace_renamed; ALTER TABLE ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; ALTER INDEX ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql index 8fc94894491..52cf5d5a09e 100644 --- a/src/test/regress/sql/tablespace.sql +++ b/src/test/regress/sql/tablespace.sql @@ -495,6 +495,32 @@ SELECT count(*) FROM tbspace_subxact; RESET enable_seqscan; RESET enable_bitmapscan; DROP TABLE tbspace_subxact; +-- Combined with another subcommand, the ALTER can run user code after the +-- move, such as a CHECK constraint validated on an inheritance child after +-- the parent was moved, so the indexes get new files even outside a +-- transaction block. +CREATE TABLE tbspace_combined (a int); +INSERT INTO tbspace_combined SELECT generate_series(1, 100); +CREATE INDEX tbspace_combined_idx ON tbspace_combined (a); +CREATE TABLE tbspace_combined_child () INHERITS (tbspace_combined); +INSERT INTO tbspace_combined_child VALUES (5000); +CREATE FUNCTION tbspace_combined_chk(v int) RETURNS bool LANGUAGE plpgsql AS $$ +BEGIN + IF v < 5000 THEN RETURN true; END IF; + INSERT INTO tbspace_combined SELECT -generate_series(1, 50); + RAISE EXCEPTION 'abort after writing'; +END $$; +ALTER TABLE tbspace_combined SET TABLESPACE regress_tblspace, + ADD CONSTRAINT tbspace_combined_k CHECK (tbspace_combined_chk(a)); +DROP TABLE tbspace_combined_child; +INSERT INTO tbspace_combined SELECT generate_series(101, 300); +SET enable_seqscan = off; +SET enable_bitmapscan = off; +SELECT count(*) FROM tbspace_combined WHERE a < 0; +RESET enable_seqscan; +RESET enable_bitmapscan; +DROP TABLE tbspace_combined; +DROP FUNCTION tbspace_combined_chk(int); ALTER TABLESPACE regress_tblspace RENAME TO regress_tblspace_renamed;