diff --git a/doc/src/sgml/ref/alter_materialized_view.sgml b/doc/src/sgml/ref/alter_materialized_view.sgml index b0759fc..321ffc9 100644 --- a/doc/src/sgml/ref/alter_materialized_view.sgml +++ b/doc/src/sgml/ref/alter_materialized_view.sgml @@ -44,6 +44,8 @@ ALTER MATERIALIZED VIEW ALL IN TABLESPACE namestorage_parameter [, ... ] ) OWNER TO new_owner SET TABLESPACE new_tablespace + SET TABLE TABLESPACE new_tablespace + SET TOAST TABLESPACE new_tablespace diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index b3a4970..777ba57 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -66,6 +66,8 @@ ALTER TABLE ALL IN TABLESPACE name SET WITH OIDS SET WITHOUT OIDS SET TABLESPACE new_tablespace + SET TABLE TABLESPACE new_tablespace + SET TOAST TABLESPACE new_tablespace SET {LOGGED | UNLOGGED} SET ( storage_parameter = value [, ... ] ) RESET ( storage_parameter [, ... ] ) @@ -501,7 +503,8 @@ ALTER TABLE ALL IN TABLESPACE name SET TABLESPACE - This form changes the table's tablespace to the specified tablespace and + This form changes the table's (and TOAST table's, if any) tablespace to + the specified tablespace and moves the data file(s) associated with the table to the new tablespace. Indexes on the table, if any, are not moved; but they can be moved separately with additional SET TABLESPACE commands. @@ -523,6 +526,31 @@ ALTER TABLE ALL IN TABLESPACE name + SET TABLE TABLESPACE + + + This form changes only table's tablespace (but not associated TOAST + table's tablespace, if any) to the specified tablespace and moves the + data file(s) associated to the new tablespace. See also + + + + + + + SET TOAST TABLESPACE + + + This form changes the TOAST table's tablespace to the specified + tablespace and moves the data file(s) associated with the TOAST table to + the new tablespace. See also + See and + for more information. + + + + + SET {LOGGED | UNLOGGED} diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml index d8c5287..0515967 100644 --- a/doc/src/sgml/storage.sgml +++ b/doc/src/sgml/storage.sgml @@ -571,6 +571,11 @@ The module can be used to examine the information stored in free space maps. + +TOAST table can be moved to a different tablespace with +ALTER TABLE SET TOAST TABLESPACE + + diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index a1efddb..6a06f9e 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -36,9 +36,11 @@ Oid binary_upgrade_next_toast_pg_type_oid = InvalidOid; static void CheckAndCreateToastTable(Oid relOid, Datum reloptions, - LOCKMODE lockmode, bool check); + LOCKMODE lockmode, bool check, + Oid toastTableSpace, bool new_toast); static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, - Datum reloptions, LOCKMODE lockmode, bool check); + Datum reloptions, LOCKMODE lockmode, bool check, + Oid toastTableSpace, bool new_toast); static bool needs_toast_table(Relation rel); @@ -55,32 +57,39 @@ static bool needs_toast_table(Relation rel); * to end with CommandCounterIncrement if it makes any changes. */ void -AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode) +AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, + Oid toastTableSpace, bool new_toast) { - CheckAndCreateToastTable(relOid, reloptions, lockmode, true); + CheckAndCreateToastTable(relOid, reloptions, lockmode, true, + toastTableSpace, new_toast); } void -NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode) +NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, + Oid toastTableSpace) { - CheckAndCreateToastTable(relOid, reloptions, lockmode, false); + CheckAndCreateToastTable(relOid, reloptions, lockmode, false, + toastTableSpace, false); } void -NewRelationCreateToastTable(Oid relOid, Datum reloptions) +NewRelationCreateToastTable(Oid relOid, Datum reloptions, Oid toastTableSpace) { - CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false); + CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false, + toastTableSpace, true); } static void -CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check) +CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check, + Oid toastTableSpace, bool new_toast) { Relation rel; rel = heap_open(relOid, lockmode); /* create_toast_table does all the work */ - (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check); + (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check, + toastTableSpace, new_toast); heap_close(rel, NoLock); } @@ -106,7 +115,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) /* create_toast_table does all the work */ if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0, - AccessExclusiveLock, false)) + AccessExclusiveLock, false, InvalidOid, true)) elog(ERROR, "\"%s\" does not require a toast table", relName); @@ -123,7 +132,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) */ static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, - Datum reloptions, LOCKMODE lockmode, bool check) + Datum reloptions, LOCKMODE lockmode, bool check, + Oid toastTableSpace, bool new_toast) { Oid relOid = RelationGetRelid(rel); HeapTuple reltup; @@ -270,9 +280,16 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, binary_upgrade_next_toast_pg_type_oid = InvalidOid; } + /* + * Use table's tablespace if toastTableSpace is invalid + * and if this is not a TOAST re-creation case. + */ + if (new_toast && !OidIsValid(toastTableSpace)) + toastTableSpace = rel->rd_rel->reltablespace; + toast_relid = heap_create_with_catalog(toast_relname, namespaceid, - rel->rd_rel->reltablespace, + toastTableSpace, toastOid, toast_typid, InvalidOid, @@ -339,7 +356,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, indexInfo, list_make2("chunk_id", "chunk_seq"), BTREE_AM_OID, - rel->rd_rel->reltablespace, + toastTableSpace, collationObjectId, classObjectId, coloptions, (Datum) 0, true, false, false, false, true, false, false, true, false); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index dc1b37c..c5b578b 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -563,6 +563,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) bool swap_toast_by_content; TransactionId frozenXid; MultiXactId cutoffMulti; + Oid toastTablespace; + Relation toastRel; /* Mark the correct index as clustered */ if (OidIsValid(indexOid)) @@ -571,13 +573,27 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); + /* + * Verifiy if a TOASTed relation exists and is a valid relation. + * If true, keep its previous tablespace in memory to rebuild it in + * the same tablespace. + */ + if (OidIsValid(OldHeap->rd_rel->reltoastrelid)) + { + toastRel = relation_open(OldHeap->rd_rel->reltoastrelid, NoLock); + toastTablespace = toastRel->rd_rel->reltablespace; + relation_close(toastRel, NoLock); + } + else + toastTablespace = is_system_catalog ? tableSpace : InvalidOid; + /* Close relcache entry, but keep lock until transaction commit */ heap_close(OldHeap, NoLock); /* Create the transient table that will receive the re-ordered data */ OIDNewHeap = make_new_heap(tableOid, tableSpace, OldHeap->rd_rel->relpersistence, - AccessExclusiveLock); + AccessExclusiveLock, toastTablespace); /* Copy the heap data into the new table in the desired order */ copy_heap_data(OIDNewHeap, tableOid, indexOid, verbose, @@ -606,7 +622,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) */ Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, - LOCKMODE lockmode) + LOCKMODE lockmode, Oid NewToastTableSpace) { TupleDesc OldHeapDesc; char NewHeapName[NAMEDATALEN]; @@ -711,7 +727,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, if (isNull) reloptions = (Datum) 0; - NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode); + NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, + NewToastTableSpace); ReleaseSysCache(tuple); } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index c961429..f26d388 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -403,7 +403,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); - NewRelationCreateToastTable(intoRelationId, toast_options); + NewRelationCreateToastTable(intoRelationId, toast_options, InvalidOid); /* Create the "view" part of a materialized view. */ if (is_matview) diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 92d9032..f9aae7d 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -268,7 +268,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, * will be gone). */ OIDNewHeap = make_new_heap(matviewOid, tableSpace, relpersistence, - ExclusiveLock); + ExclusiveLock, tableSpace); LockRelationOid(OIDNewHeap, AccessExclusiveLock); dest = CreateTransientRelDestReceiver(OIDNewHeap); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 7455020..e8ad43e 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -157,6 +157,7 @@ typedef struct AlteredTableInfo bool new_notnull; /* T if we added new NOT NULL constraints */ int rewrite; /* Reason for forced rewrite, if any */ Oid newTableSpace; /* new tablespace; 0 means no change */ + Oid newToastTableSpace; /* new TOAST tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */ /* Objects to rebuild after completing ALTER TYPE operations */ @@ -397,8 +398,9 @@ static void ATExecClusterOn(Relation rel, const char *indexName, static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); static bool ATPrepChangePersistence(Relation rel, bool toLogged); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, - char *tablespacename, LOCKMODE lockmode); -static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode); + char *tablespacename, LOCKMODE lockmode, AlterTableType subtype); +static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode, Oid newToastTableSpace); +static void ATExecSetToastTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode); static void ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, LOCKMODE lockmode); @@ -425,6 +427,7 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, void *arg); static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, void *arg); +static void RelationIsMoveableToNewTablespace(Relation rel, Oid NewTableSpaceOid); /* ---------------------------------------------------------------- @@ -2807,6 +2810,8 @@ AlterTableGetLockLevel(List *cmds) case AT_AddColumn: /* may rewrite heap, in some cases and visible * to SELECT */ case AT_SetTableSpace: /* must rewrite heap */ + case AT_SetTableTableSpace: /* must rewrite heap */ + case AT_SetToastTableSpace: /* must rewrite heap */ case AT_AlterColumnType: /* must rewrite heap */ case AT_AddOids: /* must rewrite heap */ cmd_lockmode = AccessExclusiveLock; @@ -3247,7 +3252,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_SetTableSpace: /* SET TABLESPACE */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX); /* This command never recurses */ - ATPrepSetTableSpace(tab, rel, cmd->name, lockmode); + ATPrepSetTableSpace(tab, rel, cmd->name, lockmode, cmd->subtype); + pass = AT_PASS_MISC; /* doesn't actually matter */ + break; + case AT_SetTableTableSpace: /* SET TABLE TABLESPACE */ + case AT_SetToastTableSpace: /* SET TOAST TABLESPACE */ + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); + /* This command never recurses */ + ATPrepSetTableSpace(tab, rel, cmd->name, lockmode, cmd->subtype); pass = AT_PASS_MISC; /* doesn't actually matter */ break; case AT_SetRelOptions: /* SET (...) */ @@ -3382,9 +3394,32 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) { AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + Relation toast_rel; + Oid toast_tablespace_oid; + Relation rel; + bool new_toast; + if (tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_MATVIEW) - AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode); + { + rel = relation_open(tab->relid, NoLock); + if (OidIsValid(rel->rd_rel->reltoastrelid)) + { + toast_rel = relation_open(rel->rd_rel->reltoastrelid, NoLock); + toast_tablespace_oid = toast_rel->rd_rel->reltablespace; + relation_close(toast_rel, NoLock); + new_toast = false; + } + else + { + toast_tablespace_oid = InvalidOid; + new_toast = true; + } + relation_close(rel, NoLock); + + AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode, + toast_tablespace_oid, new_toast); + } } } @@ -3523,6 +3558,18 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, * Nothing to do here; Phase 3 does the work */ break; + case AT_SetTableTableSpace: /* SET TABLE TABLESPACE */ + + /* + * Nothing to do here; Phase 3 does the work + */ + break; + case AT_SetToastTableSpace: /* SET TOAST TABLESPACE */ + + /* + * Nothing to do here; Phase 3 does the work + */ + break; case AT_SetRelOptions: /* SET (...) */ case AT_ResetRelOptions: /* RESET (...) */ case AT_ReplaceRelOptions: /* replace entire option list */ @@ -3673,6 +3720,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) Relation OldHeap; Oid OIDNewHeap; Oid NewTableSpace; + Oid NewToastTableSpace; char persistence; OldHeap = heap_open(tab->relid, NoLock); @@ -3712,6 +3760,22 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) else NewTableSpace = OldHeap->rd_rel->reltablespace; + if (tab->newToastTableSpace) + NewToastTableSpace = tab->newToastTableSpace; + else + { + if (OidIsValid(OldHeap->rd_rel->reltoastrelid)) + { + Relation OldToastRel; + + OldToastRel = relation_open(OldHeap->rd_rel->reltoastrelid, NoLock); + NewToastTableSpace = OldToastRel->rd_rel->reltablespace; + relation_close(OldToastRel, NoLock); + } + else + NewToastTableSpace = InvalidOid; + } + /* * Select persistence of transient table (same as original unless * user requested a change) @@ -3752,7 +3816,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) * unlogged anyway. */ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, persistence, - lockmode); + lockmode, NewToastTableSpace); /* * Copy the heap data into the new table with the desired @@ -3790,7 +3854,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) * just do a block-by-block copy. */ if (tab->newTableSpace) - ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode); + ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode, tab->newToastTableSpace); + if (tab->newToastTableSpace) + ATExecSetToastTableSpace(tab->relid, tab->newToastTableSpace, lockmode); } } @@ -8954,33 +9020,64 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode) } /* - * ALTER TABLE SET TABLESPACE + * Check tablespace's permissions & no multiple SET TABLESPACE subcommands. */ static void -ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename, LOCKMODE lockmode) +CheckTableSpaceAlterTable(char *TableSpaceName, Oid TableSpaceOid, + Oid NewTableSpaceOid) { - Oid tablespaceId; - - /* Check that the tablespace exists */ - tablespaceId = get_tablespace_oid(tablespacename, false); - /* Check permissions except when moving to database's default */ - if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace) + if (OidIsValid(TableSpaceOid) && TableSpaceOid != MyDatabaseTableSpace) { AclResult aclresult; - aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE); + aclresult = pg_tablespace_aclcheck(TableSpaceOid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespacename); + aclcheck_error(aclresult, ACL_KIND_TABLESPACE, TableSpaceName); } /* Save info for Phase 3 to do the real work */ - if (OidIsValid(tab->newTableSpace)) + if (OidIsValid(NewTableSpaceOid)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot have multiple SET TABLESPACE subcommands"))); +} - tab->newTableSpace = tablespaceId; +/* + * ALTER TABLE SET [TABLE|TOAST] TABLESPACE + */ +static void +ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename, + LOCKMODE lockmode, AlterTableType subtype) +{ + Oid tablespaceId; + /* Check that the tablespace exists */ + tablespaceId = get_tablespace_oid(tablespacename, false); + switch (subtype) + { + case AT_SetTableSpace: /* SET TABLESPACE */ + CheckTableSpaceAlterTable(tablespacename, tablespaceId, tab->newTableSpace); + tab->newTableSpace = tablespaceId; + tab->newToastTableSpace = tablespaceId; + break; + case AT_SetTableTableSpace: /* SET TABLE TABLESPACE */ + CheckTableSpaceAlterTable(tablespacename, tablespaceId, tab->newTableSpace); + tab->newTableSpace = tablespaceId; + tab->newToastTableSpace = InvalidOid; + break; + case AT_SetToastTableSpace: /* SET TOAST TABLESPACE */ + CheckTableSpaceAlterTable(tablespacename, tablespaceId, tab->newToastTableSpace); + tab->newTableSpace = InvalidOid; + tab->newToastTableSpace = tablespaceId; + if (!rel->rd_rel->reltoastrelid || !OidIsValid(rel->rd_rel->reltoastrelid)) + ereport(NOTICE, + (errmsg("table \"%s\" does not have any TOAST table.", + RelationGetRelationName(rel)))); + break; + default: + /* Should not happen. */ + break; + } } /* @@ -9184,12 +9281,43 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, heap_close(pgclass, RowExclusiveLock); } + +static void +RelationIsMoveableToNewTablespace(Relation rel, Oid NewTableSpaceOid) +{ + /* + * We cannot support moving mapped relations into different tablespaces. + * (In particular this eliminates all shared catalogs.) + */ + if (RelationIsMapped(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move system relation \"%s\"", + RelationGetRelationName(rel)))); + + /* Can't move a non-shared relation into pg_global */ + if (NewTableSpaceOid == GLOBALTABLESPACE_OID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("only shared relations can be placed in pg_global tablespace"))); + + /* + * Don't allow moving temp tables of other backends ... their local buffer + * manager is not going to cope. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move temporary tables of other sessions"))); +} + /* - * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple + * Execute ALTER TABLE SET [TABLE] TABLESPACE for cases where there is no tuple * rewriting to be done, so we just want to copy the data as fast as possible. */ static void -ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) +ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode, + Oid newToastTableSpace) { Relation rel; Oid oldTableSpace; @@ -9223,30 +9351,7 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) return; } - /* - * We cannot support moving mapped relations into different tablespaces. - * (In particular this eliminates all shared catalogs.) - */ - if (RelationIsMapped(rel)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot move system relation \"%s\"", - RelationGetRelationName(rel)))); - - /* Can't move a non-shared relation into pg_global */ - if (newTableSpace == GLOBALTABLESPACE_OID) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("only shared relations can be placed in pg_global tablespace"))); - - /* - * Don't allow moving temp tables of other backends ... their local buffer - * manager is not going to cope. - */ - if (RELATION_IS_OTHER_TEMP(rel)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot move temporary tables of other sessions"))); + RelationIsMoveableToNewTablespace(rel, newTableSpace); reltoastrelid = rel->rd_rel->reltoastrelid; /* Fetch the list of indexes on toast relation if necessary */ @@ -9335,10 +9440,73 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) CommandCounterIncrement(); /* Move associated toast relation and/or indexes, too */ + if (OidIsValid(newToastTableSpace)) + { + if (OidIsValid(reltoastrelid)) + ATExecSetTableSpace(reltoastrelid, newToastTableSpace, lockmode, + InvalidOid); + + foreach(lc, reltoastidxids) + ATExecSetTableSpace(lfirst_oid(lc), newToastTableSpace, lockmode, + InvalidOid); + } + + /* Clean up */ + list_free(reltoastidxids); +} + +/* + * Execute ALTER TABLE SET TOAST TABLESPACE + */ +static void +ATExecSetToastTableSpace(Oid tableOid, Oid newToastTableSpace, LOCKMODE lockmode) +{ + Relation rel; + Oid oldToastTableSpace; + Oid reltoastrelid; + List *reltoastidxids = NIL; + ListCell *lc; + Relation relToast; + + /* + * Need lock here in case we are recursing to toast table or index + */ + rel = relation_open(tableOid, lockmode); + + /* + * Need to know old TOAST tablespace + */ + reltoastrelid = rel->rd_rel->reltoastrelid; if (OidIsValid(reltoastrelid)) - ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode); + { + relToast = relation_open(reltoastrelid, NoLock); + + oldToastTableSpace = relToast->rd_rel->reltablespace; + if (newToastTableSpace == oldToastTableSpace || + (newToastTableSpace == MyDatabaseTableSpace && oldToastTableSpace == 0)) + { + relation_close(rel, NoLock); + relation_close(relToast, NoLock); + return; + } + + reltoastidxids = RelationGetIndexList(relToast); + relation_close(relToast, NoLock); + } + else + { + relation_close(rel, NoLock); + return; + } + + RelationIsMoveableToNewTablespace(rel, newToastTableSpace); + relation_close(rel, NoLock); + + ATExecSetTableSpace(reltoastrelid, newToastTableSpace, lockmode, InvalidOid); + foreach(lc, reltoastidxids) - ATExecSetTableSpace(lfirst_oid(lc), newTableSpace, lockmode); + ATExecSetTableSpace(lfirst_oid(lc), newToastTableSpace, lockmode, + InvalidOid); /* Clean up */ list_free(reltoastidxids); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 581f7a1..79f4ec4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -612,7 +612,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP - TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P + TO TOAST TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED @@ -2275,6 +2275,22 @@ alter_table_cmd: n->name = $3; $$ = (Node *)n; } + /* ALTER TABLE SET TOAST TABLESPACE */ + | SET TOAST TABLESPACE name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetToastTableSpace; + n->name = $4; + $$ = (Node *)n; + } + /* ALTER TABLE SET TABLE TABLESPACE */ + | SET TABLE TABLESPACE name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetTableTableSpace; + n->name = $4; + $$ = (Node *)n; + } /* ALTER TABLE SET (...) */ | SET reloptions { @@ -13414,6 +13430,7 @@ unreserved_keyword: | TEMPLATE | TEMPORARY | TEXT_P + | TOAST | TRANSACTION | TRIGGER | TRUNCATE diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 6d26986..a6859cf 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -952,7 +952,8 @@ ProcessUtilitySlow(Node *parsetree, toast_options, true); - NewRelationCreateToastTable(relOid, toast_options); + NewRelationCreateToastTable(relOid, toast_options, + InvalidOid); } else if (IsA(stmt, CreateForeignTableStmt)) { diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 2b53c72..9b84b12 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4577,6 +4577,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) int i_owning_tab; int i_owning_col; int i_reltablespace; + int i_reltoasttablespace; int i_reloptions; int i_checkoption; int i_toastreloptions; @@ -4631,7 +4632,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, " - "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " + "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = tc.reltablespace) AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4752,7 +4754,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " - "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " + "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4791,7 +4794,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " - "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " + "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4829,7 +4833,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " - "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " + "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4867,7 +4872,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "array_to_string(c.reloptions, ', ') AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4905,7 +4911,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "d.refobjsubid AS owning_col, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4942,7 +4949,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "d.refobjsubid AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -4975,7 +4983,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL::int4 AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class " "WHERE relkind IN ('%c', '%c', '%c') " "ORDER BY oid", @@ -5003,7 +5012,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL::int4 AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class " "WHERE relkind IN ('%c', '%c', '%c') " "ORDER BY oid", @@ -5041,7 +5051,8 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "NULL::int4 AS owning_col, " "NULL AS reltablespace, " "NULL AS reloptions, " - "NULL AS toast_reloptions " + "NULL AS toast_reloptions, " + "NULL AS reltoasttablespace " "FROM pg_class c " "WHERE relkind IN ('%c', '%c') " "ORDER BY oid", @@ -5096,6 +5107,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) i_checkoption = PQfnumber(res, "checkoption"); i_toastreloptions = PQfnumber(res, "toast_reloptions"); i_reloftype = PQfnumber(res, "reloftype"); + i_reltoasttablespace = PQfnumber(res, "reltoasttablespace"); if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300) { @@ -5162,6 +5174,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) else tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption)); tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions)); + tblinfo[i].reltoasttablespace = pg_strdup(PQgetvalue(res, i, i_reltoasttablespace)); /* other fields were zeroed above */ @@ -13774,6 +13787,16 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) appendPQExpBuffer(q, " AS\n%s\n WITH NO DATA;\n", result->data); destroyPQExpBuffer(result); + + /* Change TOAST tablespace */ + if (strlen(tbinfo->reltoasttablespace) > 0) + { + appendPQExpBuffer(q, "ALTER MATERIALIZED VIEW %s ", + fmtId(tbinfo->dobj.name)); + appendPQExpBuffer(q, "SET TOAST TABLESPACE %s;\n", + tbinfo->reltoasttablespace); + } + } else appendPQExpBufferStr(q, ";\n"); @@ -14047,6 +14070,15 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) } } + /* Change TOAST tablespace */ + if (strlen(tbinfo->reltoasttablespace) > 0 && tbinfo->relkind == RELKIND_RELATION) + { + appendPQExpBuffer(q, "ALTER TABLE %s ", + fmtId(tbinfo->dobj.name)); + appendPQExpBuffer(q, "SET TOAST TABLESPACE %s;\n", + tbinfo->reltoasttablespace); + } + if (dopt->binary_upgrade) binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index f42c42d..6cb7362 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -204,6 +204,7 @@ typedef struct _tableInfo bool relispopulated; /* relation is populated */ bool relreplident; /* replica identifier */ char *reltablespace; /* relation tablespace */ + char *reltoasttablespace; /* TOAST relation tablespace */ char *reloptions; /* options specified by WITH (...) */ char *checkoption; /* WITH CHECK OPTION */ char *toast_reloptions; /* WITH options for the TOAST table */ diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index c44e447..2048f00 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -31,6 +31,7 @@ static bool describeOneTableDetails(const char *schemaname, bool verbose); static void add_tablespace_footer(printTableContent *const cont, char relkind, Oid tablespace, const bool newline); +static void add_toasttablespace_footer(printTableContent *const cont, Oid toasttablespace); static void add_role_attribute(PQExpBuffer buf, const char *const str); static bool listTSParsersVerbose(const char *pattern); static bool describeOneTSParser(const char *oid, const char *nspname, @@ -1230,6 +1231,7 @@ describeOneTableDetails(const char *schemaname, bool rowsecurity; bool hasoids; Oid tablespace; + Oid toasttablespace; char *reloptions; char *reloftype; char relpersistence; @@ -1254,7 +1256,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "c.relhastriggers, c.relrowsecurity, c.relhasoids, " - "%s, c.reltablespace, " + "%s, c.reltablespace, tc.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence, c.relreplident\n" "FROM pg_catalog.pg_class c\n " @@ -1271,7 +1273,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "c.relhastriggers, false, c.relhasoids, " - "%s, c.reltablespace, " + "%s, c.reltablespace, tc.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence, c.relreplident\n" "FROM pg_catalog.pg_class c\n " @@ -1288,7 +1290,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "c.relhastriggers, false, c.relhasoids, " - "%s, c.reltablespace, " + "%s, c.reltablespace, tc.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence\n" "FROM pg_catalog.pg_class c\n " @@ -1305,7 +1307,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "c.relhastriggers, false, c.relhasoids, " - "%s, c.reltablespace, " + "%s, c.reltablespace, tc.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END\n" "FROM pg_catalog.pg_class c\n " "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" @@ -1321,7 +1323,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "c.relhastriggers, false, c.relhasoids, " - "%s, c.reltablespace\n" + "%s, c.reltablespace, tc.reltablespace\n" "FROM pg_catalog.pg_class c\n " "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" "WHERE c.oid = '%s';", @@ -1336,7 +1338,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " "reltriggers <> 0, false, relhasoids, " - "%s, reltablespace\n" + "%s, reltablespace, ''\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", (verbose ? "pg_catalog.array_to_string(reloptions, E', ')" : "''"), @@ -1347,7 +1349,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " "reltriggers <> 0, false, relhasoids, " - "'', reltablespace\n" + "'', reltablespace, ''\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", oid); } @@ -1356,7 +1358,7 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " "reltriggers <> 0, false, relhasoids, " - "'', ''\n" + "'', '', ''\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", oid); } @@ -1384,13 +1386,15 @@ describeOneTableDetails(const char *schemaname, pg_strdup(PQgetvalue(res, 0, 7)) : NULL; tableinfo.tablespace = (pset.sversion >= 80000) ? atooid(PQgetvalue(res, 0, 8)) : 0; + tableinfo.toasttablespace = (pset.sversion >= 80400) ? + atooid(PQgetvalue(res, 0, 9)) : 0; tableinfo.reloftype = (pset.sversion >= 90000 && - strcmp(PQgetvalue(res, 0, 9), "") != 0) ? - pg_strdup(PQgetvalue(res, 0, 9)) : NULL; + strcmp(PQgetvalue(res, 0, 10), "") != 0) ? + pg_strdup(PQgetvalue(res, 0, 10)) : NULL; tableinfo.relpersistence = (pset.sversion >= 90100) ? - *(PQgetvalue(res, 0, 10)) : 0; + *(PQgetvalue(res, 0, 11)) : 0; tableinfo.relreplident = (pset.sversion >= 90400) ? - *(PQgetvalue(res, 0, 11)) : 'd'; + *(PQgetvalue(res, 0, 12)) : 'd'; PQclear(res); res = NULL; @@ -1771,6 +1775,7 @@ describeOneTableDetails(const char *schemaname, printTableAddFooter(&cont, tmpbuf.data); add_tablespace_footer(&cont, tableinfo.relkind, tableinfo.tablespace, true); + add_toasttablespace_footer(&cont, tableinfo.toasttablespace); } PQclear(result); @@ -2520,6 +2525,7 @@ describeOneTableDetails(const char *schemaname, /* Tablespace info */ add_tablespace_footer(&cont, tableinfo.relkind, tableinfo.tablespace, true); + add_toasttablespace_footer(&cont, tableinfo.toasttablespace); } /* reloptions, if verbose */ @@ -2628,6 +2634,37 @@ add_tablespace_footer(printTableContent *const cont, char relkind, } /* + * Add a TOAST tablespace description to a footer. + */ +static void +add_toasttablespace_footer(printTableContent *const cont, Oid toasttablespace) +{ + if (toasttablespace != 0) + { + PGresult *result = NULL; + PQExpBufferData buf; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT spcname FROM pg_catalog.pg_tablespace\n" + "WHERE oid = '%u';", toasttablespace); + result = PSQLexec(buf.data); + if (!result) + return; + /* Should always be the case, but.... */ + if (PQntuples(result) > 0) + { + /* Add the TOAST tablespace as a new footer */ + printfPQExpBuffer(&buf, _("TOAST Tablespace: \"%s\""), + PQgetvalue(result, 0, 0)); + printTableAddFooter(cont, buf.data); + } + PQclear(result); + termPQExpBuffer(&buf); + } +} + +/* * \du or \dg * * Describes roles. Any schema portion of the pattern is ignored. diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index e39a07c..5015476 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1396,11 +1396,37 @@ psql_completion(const char *text, int start, int end) pg_strcasecmp(prev2_wd, "VIEW") == 0) { static const char *const list_ALTERMATVIEW[] = - {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL}; + {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", NULL}; + + COMPLETE_WITH_LIST(list_ALTERMATVIEW); + } + /* ALTER MATERIALIZED VIEW SET */ + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 && + pg_strcasecmp(prev3_wd, "VIEW") == 0 && + pg_strcasecmp(prev_wd, "SET") == 0) + { + static const char *const list_ALTERMATVIEW[] = + {"SCHEMA", "TOAST TABLESPACE", "TABLESPACE", "TABLE TABLESPACE", NULL}; + + COMPLETE_WITH_LIST(list_ALTERMATVIEW); + } + /* ALTER MATERIALIZED VIEW SET (TABLE|TOAST) */ + else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 && + pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 && + pg_strcasecmp(prev4_wd, "VIEW") == 0 && + pg_strcasecmp(prev2_wd, "SET") == 0 && + (pg_strcasecmp(prev_wd, "TABLE") == 0 || + pg_strcasecmp(prev_wd, "TOAST") == 0)) + { + static const char *const list_ALTERMATVIEW[] = + {"TABLESPACE", NULL}; COMPLETE_WITH_LIST(list_ALTERMATVIEW); } + + /* ALTER POLICY ON */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "POLICY") == 0) @@ -1743,7 +1769,7 @@ psql_completion(const char *text, int start, int end) pg_strcasecmp(prev_wd, "SET") == 0) { static const char *const list_TABLESET[] = - {"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL}; + {"(", "LOGGED", "SCHEMA", "TABLESPACE", "TABLE TABLESPACE", "TOAST TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL}; COMPLETE_WITH_LIST(list_TABLESET); } @@ -1752,6 +1778,26 @@ psql_completion(const char *text, int start, int end) pg_strcasecmp(prev2_wd, "SET") == 0 && pg_strcasecmp(prev_wd, "TABLESPACE") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); + /* If we have ALTER TABLE SET TABLE provide TABLESPACE */ + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "TABLE") == 0) + { + static const char *const list_TABLETABLESPACE[] = + {"TABLESPACE", NULL}; + COMPLETE_WITH_LIST(list_TABLETABLESPACE); + } + /* If we have ALTER TABLE SET TOAST provide TABLESPACE */ + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "TOAST") == 0) + { + static const char *const list_TOASTTABLESPACE[] = + {"TABLESPACE", NULL}; + COMPLETE_WITH_LIST(list_TOASTTABLESPACE); + } /* If we have TABLE SET WITHOUT provide CLUSTER or OIDS */ else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && pg_strcasecmp(prev2_wd, "SET") == 0 && diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h index cba4ae7..39f1975 100644 --- a/src/include/catalog/toasting.h +++ b/src/include/catalog/toasting.h @@ -19,11 +19,12 @@ /* * toasting.c prototypes */ -extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions); +extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions, + Oid toastTableSpace); extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions, - LOCKMODE lockmode); + LOCKMODE lockmode, Oid toastTableSpace); extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions, - LOCKMODE lockmode); + LOCKMODE lockmode, Oid toastTableSpace, bool new_toast); extern void BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index 098d09b..bcccd87 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -26,7 +26,7 @@ extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, - LOCKMODE lockmode); + LOCKMODE lockmode, Oid NewToastTableSpace); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ac13302..8a34ba6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1325,6 +1325,8 @@ typedef enum AlterTableType AT_AddOidsRecurse, /* internal to commands/tablecmds.c */ AT_DropOids, /* SET WITHOUT OIDS */ AT_SetTableSpace, /* SET TABLESPACE */ + AT_SetTableTableSpace, /* SET TABLE TABLESPACE */ + AT_SetToastTableSpace, /* SET TOAST TABLESPACE */ AT_SetRelOptions, /* SET (...) -- AM specific parameters */ AT_ResetRelOptions, /* RESET (...) -- AM specific parameters */ AT_ReplaceRelOptions, /* replace reloption list in its entirety */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 7c243ec..ce907f0 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -375,6 +375,7 @@ PG_KEYWORD("then", THEN, RESERVED_KEYWORD) PG_KEYWORD("time", TIME, COL_NAME_KEYWORD) PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD) PG_KEYWORD("to", TO, RESERVED_KEYWORD) +PG_KEYWORD("toast", TOAST, UNRESERVED_KEYWORD) PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD) PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD) PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD) diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index 110eb80..49ea024 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -125,7 +125,9 @@ submake-contrib-spi: .PHONY: tablespace-setup tablespace-setup: rm -rf ./testtablespace + rm -rf ./testtablespace2 mkdir ./testtablespace + mkdir ./testtablespace2 ## @@ -176,4 +178,5 @@ clean distclean maintainer-clean: clean-lib # things created by various check targets rm -f $(output_files) $(input_files) rm -rf testtablespace + rm -rf testtablespace2 rm -rf $(pg_regress_clean_files) diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source index 75ec689..f05b95f 100644 --- a/src/test/regress/input/tablespace.source +++ b/src/test/regress/input/tablespace.source @@ -21,7 +21,7 @@ ALTER TABLESPACE testspace RESET (random_page_cost, seq_page_cost); -- ok CREATE SCHEMA testschema; -- try a table -CREATE TABLE testschema.foo (i int) TABLESPACE testspace; +CREATE TABLE testschema.foo (i int, label varchar) TABLESPACE testspace; SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c where c.reltablespace = t.oid AND c.relname = 'foo'; @@ -64,6 +64,38 @@ CREATE TABLE bar (i int) TABLESPACE nosuchspace; -- Fail, not empty DROP TABLESPACE testspace; +-- ALTER TABLE SET TOAST TABLESPACE +CREATE TABLESPACE testspace2 LOCATION '@testtablespace2@'; +ALTER TABLE testschema.foo SET TOAST TABLESPACE nosuchspace; +ALTER TABLE testschema.foo SET TABLE TABLESPACE nosuchspace; +ALTER TABLE testschema.foo SET TOAST TABLESPACE testspace; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo'; + +ALTER TABLE testschema.foo SET TOAST TABLESPACE testspace2; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo'; + +ALTER TABLE testschema.foo SET TABLESPACE testspace2; +ALTER TABLE testschema.foo SET TABLE TABLESPACE testspace; +SELECT spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c WHERE c.reltablespace = t.oid AND c.relname = 'foo'; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo'; + +CREATE TABLE testschema.foo2 (id SERIAL PRIMARY KEY, l text) TABLESPACE testspace2; +ALTER TABLE testschema.foo2 SET TOAST TABLESPACE pg_default; +CREATE MATERIALIZED VIEW testschema.foo_mv AS SELECT * FROM testschema.foo; +ALTER MATERIALIZED VIEW testschema.foo_mv SET TOAST TABLESPACE testspace2; +ALTER MATERIALIZED VIEW testschema.foo_mv SET TABLE TABLESPACE testspace; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo_mv'; + +SELECT spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c WHERE c.reltablespace = t.oid AND c.relname = 'foo_mv'; + +ALTER MATERIALIZED VIEW testschema.foo_mv SET TABLESPACE pg_default; +INSERT INTO testschema.foo2 (l) VALUES ('foo'); +UPDATE testschema.foo2 SET l = l||l; +CLUSTER testschema.foo2 USING foo2_pkey; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo2'; + +SELECT spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c WHERE c.reltablespace = t.oid AND c.relname = 'foo2'; + CREATE ROLE tablespace_testuser1 login; CREATE ROLE tablespace_testuser2 login; @@ -85,6 +117,7 @@ ALTER TABLE ALL IN TABLESPACE testspace_renamed SET TABLESPACE pg_default; DROP TABLESPACE testspace_renamed; DROP SCHEMA testschema CASCADE; +DROP TABLESPACE testspace2; DROP ROLE tablespace_testuser1; DROP ROLE tablespace_testuser2; diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source index ca60650..96cb111 100644 --- a/src/test/regress/output/tablespace.source +++ b/src/test/regress/output/tablespace.source @@ -23,7 +23,7 @@ ALTER TABLESPACE testspace RESET (random_page_cost, seq_page_cost); -- ok -- create a schema we can use CREATE SCHEMA testschema; -- try a table -CREATE TABLE testschema.foo (i int) TABLESPACE testspace; +CREATE TABLE testschema.foo (i int, label varchar) TABLESPACE testspace; SELECT relname, spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c where c.reltablespace = t.oid AND c.relname = 'foo'; relname | spcname @@ -85,6 +85,72 @@ ERROR: tablespace "nosuchspace" does not exist -- Fail, not empty DROP TABLESPACE testspace; ERROR: tablespace "testspace" is not empty +-- ALTER TABLE SET TOAST TABLESPACE +CREATE TABLESPACE testspace2 LOCATION '@testtablespace2@'; +ALTER TABLE testschema.foo SET TOAST TABLESPACE nosuchspace; +ERROR: tablespace "nosuchspace" does not exist +ALTER TABLE testschema.foo SET TABLE TABLESPACE nosuchspace; +ERROR: tablespace "nosuchspace" does not exist +ALTER TABLE testschema.foo SET TOAST TABLESPACE testspace; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo'; + spcname +----------- + testspace +(1 row) + +ALTER TABLE testschema.foo SET TOAST TABLESPACE testspace2; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo'; + spcname +------------ + testspace2 +(1 row) + +ALTER TABLE testschema.foo SET TABLESPACE testspace2; +ALTER TABLE testschema.foo SET TABLE TABLESPACE testspace; +SELECT spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c WHERE c.reltablespace = t.oid AND c.relname = 'foo'; + spcname +----------- + testspace +(1 row) + +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo'; + spcname +------------ + testspace2 +(1 row) + +CREATE TABLE testschema.foo2 (id SERIAL PRIMARY KEY, l text) TABLESPACE testspace2; +ALTER TABLE testschema.foo2 SET TOAST TABLESPACE pg_default; +CREATE MATERIALIZED VIEW testschema.foo_mv AS SELECT * FROM testschema.foo; +ALTER MATERIALIZED VIEW testschema.foo_mv SET TOAST TABLESPACE testspace2; +ALTER MATERIALIZED VIEW testschema.foo_mv SET TABLE TABLESPACE testspace; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo_mv'; + spcname +------------ + testspace2 +(1 row) + +SELECT spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c WHERE c.reltablespace = t.oid AND c.relname = 'foo_mv'; + spcname +----------- + testspace +(1 row) + +ALTER MATERIALIZED VIEW testschema.foo_mv SET TABLESPACE pg_default; +INSERT INTO testschema.foo2 (l) VALUES ('foo'); +UPDATE testschema.foo2 SET l = l||l; +CLUSTER testschema.foo2 USING foo2_pkey; +SELECT spcname FROM pg_class c JOIN pg_class d ON (c.reltoastrelid=d.oid) JOIN pg_tablespace ON (d.reltablespace = pg_tablespace.oid) WHERE c.relname = 'foo2'; + spcname +--------- +(0 rows) + +SELECT spcname FROM pg_catalog.pg_tablespace t, pg_catalog.pg_class c WHERE c.reltablespace = t.oid AND c.relname = 'foo2'; + spcname +------------ + testspace2 +(1 row) + CREATE ROLE tablespace_testuser1 login; CREATE ROLE tablespace_testuser2 login; ALTER TABLESPACE testspace OWNER TO tablespace_testuser1; @@ -101,10 +167,13 @@ NOTICE: no matching relations in tablespace "testspace_renamed" found -- Should succeed DROP TABLESPACE testspace_renamed; DROP SCHEMA testschema CASCADE; -NOTICE: drop cascades to 4 other objects +NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to table testschema.foo drop cascades to table testschema.asselect drop cascades to table testschema.asexecute drop cascades to table testschema.atable +drop cascades to table testschema.foo2 +drop cascades to materialized view testschema.foo_mv +DROP TABLESPACE testspace2; DROP ROLE tablespace_testuser1; DROP ROLE tablespace_testuser2; diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 3af0e57..89ce698 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -495,6 +495,7 @@ static void convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, char *suffix) { char testtablespace[MAXPGPATH]; + char testtablespace2[MAXPGPATH]; char indir[MAXPGPATH]; struct stat st; int ret; @@ -521,6 +522,7 @@ convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, c exit(2); snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir); + snprintf(testtablespace2, MAXPGPATH, "%s/testtablespace2", outputdir); #ifdef WIN32 @@ -542,6 +544,9 @@ convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, c exit(2); } make_directory(testtablespace); + if (directory_exists(testtablespace2)) + rmtree(testtablespace2, true); + make_directory(testtablespace2); #endif /* finally loop on each file and do the replacement */ @@ -587,6 +592,7 @@ convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, c replace_string(line, "@abs_srcdir@", inputdir); replace_string(line, "@abs_builddir@", outputdir); replace_string(line, "@testtablespace@", testtablespace); + replace_string(line, "@testtablespace2@", testtablespace2); replace_string(line, "@libdir@", dlpath); replace_string(line, "@DLSUFFIX@", DLSUFFIX); fputs(line, outfile);